diff options
58 files changed, 1698 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="CompilerConfiguration"> +    <bytecodeTargetLevel target="21" /> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="deploymentTargetSelector"> +    <selectionStates> +      <SelectionState runConfigName="app"> +        <option name="selectionMode" value="DROPDOWN" /> +      </SelectionState> +    </selectionStates> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7b3006b --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="GradleMigrationSettings" migrationVersion="1" /> +  <component name="GradleSettings"> +    <option name="linkedExternalProjectsSettings"> +      <GradleProjectSettings> +        <option name="testRunner" value="CHOOSE_PER_TEST" /> +        <option name="externalProjectPath" value="$PROJECT_DIR$" /> +        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> +        <option name="modules"> +          <set> +            <option value="$PROJECT_DIR$" /> +            <option value="$PROJECT_DIR$/app" /> +          </set> +        </option> +        <option name="resolveExternalAnnotations" value="false" /> +      </GradleProjectSettings> +    </option> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..cde3e19 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,57 @@ +<component name="InspectionProjectProfileManager"> +  <profile version="1.0"> +    <option name="myName" value="Project Default" /> +    <inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +    <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true"> +      <option name="composableFile" value="true" /> +      <option name="previewFile" value="true" /> +    </inspection_tool> +  </profile> +</component>
\ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..6d0ee1c --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="KotlinJpsPluginSettings"> +    <option name="version" value="2.0.0" /> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="ProjectMigrations"> +    <option name="MigrateToGradleLocalJavaHome"> +      <set> +        <option value="$PROJECT_DIR$" /> +      </set> +    </option> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="ExternalStorageConfigurationManager" enabled="true" /> +  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> +    <output url="file://$PROJECT_DIR$/build/classes" /> +  </component> +  <component name="ProjectType"> +    <option name="id" value="Android" /> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="RunConfigurationProducerService"> +    <option name="ignoredProducers"> +      <set> +        <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" /> +        <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" /> +        <option value="com.intellij.execution.junit.PatternConfigurationProducer" /> +        <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" /> +        <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" /> +        <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" /> +        <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" /> +        <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" /> +      </set> +    </option> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="VcsDirectoryMappings"> +    <mapping directory="$PROJECT_DIR$" vcs="Git" /> +  </component> +</project>
\ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build
\ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..6848831 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,60 @@ +plugins { +    alias(libs.plugins.android.application) +    alias(libs.plugins.kotlin.android) +    alias(libs.plugins.kotlin.compose) +} + +android { +    namespace = "com.a404m.calculator" +    compileSdk = 35 + +    defaultConfig { +        applicationId = "com.a404m.calculator" +        minSdk = 21 +        targetSdk = 35 +        versionCode = 2 +        versionName = "0.1.0" + +        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" +    } + +    buildTypes { +        release { +            isMinifyEnabled = true +            isShrinkResources = true +            proguardFiles( +                getDefaultProguardFile("proguard-android-optimize.txt"), +                "proguard-rules.pro" +            ) +        } +    } +    compileOptions { +        sourceCompatibility = JavaVersion.VERSION_11 +        targetCompatibility = JavaVersion.VERSION_11 +    } +    kotlinOptions { +        jvmTarget = "11" +    } +    buildFeatures { +        compose = true +    } +} + +dependencies { + +    implementation(libs.androidx.core.ktx) +    implementation(libs.androidx.lifecycle.runtime.ktx) +    implementation(libs.androidx.activity.compose) +    implementation(platform(libs.androidx.compose.bom)) +    implementation(libs.androidx.ui) +    implementation(libs.androidx.ui.graphics) +    implementation(libs.androidx.ui.tooling.preview) +    implementation(libs.androidx.material3) +    testImplementation(libs.junit) +    androidTestImplementation(libs.androidx.junit) +    androidTestImplementation(libs.androidx.espresso.core) +    androidTestImplementation(platform(libs.androidx.compose.bom)) +    androidTestImplementation(libs.androidx.ui.test.junit4) +    debugImplementation(libs.androidx.ui.tooling) +    debugImplementation(libs.androidx.ui.test.manifest) +}
\ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +#   http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +#   public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile
\ No newline at end of file diff --git a/app/release/app-release.apk b/app/release/app-release.apk Binary files differnew file mode 100644 index 0000000..4763c61 --- /dev/null +++ b/app/release/app-release.apk diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm Binary files differnew file mode 100644 index 0000000..2df2b65 --- /dev/null +++ b/app/release/baselineProfiles/0/app-release.dm diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm Binary files differnew file mode 100644 index 0000000..829ee1a --- /dev/null +++ b/app/release/baselineProfiles/1/app-release.dm diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..4b3565e --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ +  "version": 3, +  "artifactType": { +    "type": "APK", +    "kind": "Directory" +  }, +  "applicationId": "com.a404m.calculator", +  "variantName": "release", +  "elements": [ +    { +      "type": "SINGLE", +      "filters": [], +      "attributes": [], +      "versionCode": 2, +      "versionName": "0.1.0", +      "outputFile": "app-release.apk" +    } +  ], +  "elementType": "File", +  "baselineProfiles": [ +    { +      "minApi": 28, +      "maxApi": 30, +      "baselineProfiles": [ +        "baselineProfiles/1/app-release.dm" +      ] +    }, +    { +      "minApi": 31, +      "maxApi": 2147483647, +      "baselineProfiles": [ +        "baselineProfiles/0/app-release.dm" +      ] +    } +  ], +  "minSdkVersionForDexing": 21 +}
\ No newline at end of file diff --git a/app/src/androidTest/java/com/a404m/calculator/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/a404m/calculator/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..785fe9a --- /dev/null +++ b/app/src/androidTest/java/com/a404m/calculator/ExampleInstrumentedTest.kt @@ -0,0 +1,27 @@ +package com.a404m.calculator + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { +    @Test +    fun useAppContext() { +        // Context of the app under test. +        val appContext = InstrumentationRegistry.getInstrumentation().targetContext +        assertEquals( +            "com.a404m.calculator", +            appContext.packageName +        ) +    } +}
\ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8d7bca4 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" +    xmlns:tools="http://schemas.android.com/tools"> + +    <application +        android:allowBackup="true" +        android:dataExtractionRules="@xml/data_extraction_rules" +        android:fullBackupContent="@xml/backup_rules" +        android:icon="@mipmap/ic_launcher" +        android:label="@string/app_name" +        android:roundIcon="@mipmap/ic_launcher_round" +        android:supportsRtl="true" +        android:theme="@style/Theme.Calculator" +        tools:targetApi="31"> +        <activity +            android:name=".MainActivity" +            android:exported="true" +            android:label="@string/app_name" +            android:theme="@style/Theme.Calculator"> +            <intent-filter> +                <action android:name="android.intent.action.MAIN" /> + +                <category android:name="android.intent.category.LAUNCHER" /> +            </intent-filter> +        </activity> +    </application> + +</manifest>
\ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png Binary files differnew file mode 100644 index 0000000..2d94637 --- /dev/null +++ b/app/src/main/ic_launcher-playstore.png diff --git a/app/src/main/java/com/a404m/calculator/MainActivity.kt b/app/src/main/java/com/a404m/calculator/MainActivity.kt new file mode 100644 index 0000000..9a40e9b --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/MainActivity.kt @@ -0,0 +1,27 @@ +package com.a404m.calculator + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.a404m.calculator.ui.page.HomePage +import com.a404m.calculator.ui.theme.CalculatorTheme + +class MainActivity : ComponentActivity() { +    override fun onCreate(savedInstanceState: Bundle?) { +        super.onCreate(savedInstanceState) +        enableEdgeToEdge() +        setContent { +            CalculatorTheme { +                HomePage() +            } +        } +    } +}
\ No newline at end of file diff --git a/app/src/main/java/com/a404m/calculator/MathHelper.kt b/app/src/main/java/com/a404m/calculator/MathHelper.kt new file mode 100644 index 0000000..fa6cfc7 --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/MathHelper.kt @@ -0,0 +1,309 @@ +package com.a404m.calculator + +import android.util.Log +import com.a404m.calculator.MathHelper.Operator.Kind +import java.nio.charset.UnsupportedCharsetException + +object MathHelper { +    private val operatorStrings = arrayOf( +        '+', +        '-', +        '×', +        '÷', +        '%', +    ) +    private val numberStrings = arrayOf( +        '0', +        '1', +        '2', +        '3', +        '4', +        '5', +        '6', +        '7', +        '8', +        '9', +        '.', +    ) + +    private val plus = Operator( +        '+', +        Kind.PREFIX, +        { +            Operand(it.first().value) +        } +    ) +    private val minus = Operator( +        '-', +        Kind.PREFIX, +        { +            Operand(-it.first().value) +        } +    ) +    private val add = Operator( +        '+', +        Kind.INFIX, +        { +            Operand(it.first().value+it.last().value) +        } +    ) +    private val sub = Operator( +        '-', +        Kind.INFIX, +        { +            Operand(it.first().value-it.last().value) +        } +    ) +    private val mul = Operator( +        '×', +        Kind.INFIX, +        { +            Operand(it.first().value*it.last().value) +        } +    ) +    private val div = Operator( +        '÷', +        Kind.INFIX, +        { +            Operand(it.first().value/it.last().value) +        } +    ) +    private val rem = Operator( +        '%', +        Kind.INFIX, +        { +            Operand(it.first().value%it.last().value) +        } +    ) +    private val operatorOrders = arrayOf( +        arrayOf( +            plus, +            minus, +        ), +        arrayOf( +            mul, +            div, +            rem, +        ), +        arrayOf( +            add, +            sub, +        ) +    ) +    private val operators = arrayOf( +        plus, +        minus, +        add, +        sub, +        mul, +        div, +        rem, +    ) + +    fun eval(expression: String): String { +        return try { +            parse(lex(expression)).eval().toString() +        } catch (e: Exception) { +            e.message ?: "Exception" +        } +    } + +    private fun parse(lexed: ArrayList<Any>): Operator { +        for (order in operatorOrders) { +            if (lexed.size == 1) { +                break +            } +            for (i in lexed.indices) { +                val item = lexed[i] +                if (item is BasicOperator) { +                    var op = item.toOperator( +                        lexed, +                        i +                    ) +                    if (op in order) { +                        op = op.cloneWithoutOperands() +                        Log.d( +                            "A404M", +                            "parse: $op" +                        ) +                        lexed[i] = op +                        when (op.kind) { +                            Kind.NONE -> {} +                            Kind.INFIX -> { +                                op.operands.add(lexed.removeAt(i - 1)) +                                op.operands.add(lexed.removeAt(i)) +                            } + +                            Kind.PREFIX -> { +                                op.operands.add(lexed.removeAt(i + 1)) +                            } + +                            Kind.POSTFIX -> { +                                op.operands.add(lexed.removeAt(i - 1)) +                            } +                        } +                        break +                    } +                } +            } +        } +        return if (lexed.size != 1) { +            throw UnsupportedOperationException("lexed.size = ${lexed.size}: $lexed") +        } else if (lexed.first() is Operator) { +            lexed.first() as Operator +        } else if (lexed.first() is Operand) { +            plus.cloneWithoutOperands().cloneWithOperator(arrayListOf(lexed.first())) +        } else { +            throw UnsupportedOperationException("unsupported type ${lexed.first().javaClass}") +        } +    } + +    private fun lex(expression: String): ArrayList<Any> { +        val items = arrayListOf<Any>() + +        var i = 0 +        while (i < expression.length) { +            when (val c = expression[i]) { +                in numberStrings -> { +                    val start = i +                    while (++i < expression.length && expression[i] in numberStrings); +                    items.add( +                        Operand( +                            value = expression.substring( +                                start, +                                i +                            ).toDouble() +                        ) +                    ) +                    --i +                } + +                in operatorStrings -> { +                    items.add( +                        BasicOperator(c) +                    ) +                } + +                else -> throw UnsupportedCharsetException(c.toString()) +            } +            ++i +        } + +        return items +    } + +    private class Operator( +        val operator: Char, +        val kind: Kind, +        val operate: (operands:List<Operand>)->Operand, +        val operands: ArrayList<Any> = arrayListOf(), +    ) { +        fun cloneWithoutOperands():Operator{ +            return Operator( +                operator, +                kind, +                operate, +            ) +        } + +        fun cloneWithOperator(operands: ArrayList<Any>): Operator { +            return Operator( +                operator, +                kind, +                operate, +                operands, +            ) +        } + +        fun eval(): Operand { +            Log.d( +                "A404M", +                "eval: $operator $operands" +            ) +            if(operandSize() != operands.size){ +                throw UnsupportedOperationException("Not enough operands") +            } + +            val evaledOperands = operands.map { +                if(it is Operator){ +                    it.eval() +                }else{ +                    it as Operand +                } +            } + +            return operate(evaledOperands) +        } + +        fun operandSize(): Int { +            return when (kind) { +                Kind.INFIX -> 2 +                Kind.PREFIX, Kind.POSTFIX -> 1 +                Kind.NONE -> 0 +            } +        } + +        override fun toString(): String { +            return "$operator $kind $operands" +        } + +        enum class Kind { +            NONE, +            INFIX, +            PREFIX, +            POSTFIX, +        } +    } + +    private class BasicOperator( +        val operator: Char, +    ) { +        fun toOperator( +            items: List<Any>, +            i: Int +        ): Operator { +            val left = items.getOrElse( +                i - 1, +                { Any() }, +            ) is Operand + +            val right = items.getOrElse( +                i + 1, +                { Any() }, +            ) is Operand + +            val k:Kind + +            if (left) { +                if (right) { +                    k = Kind.INFIX +                } else { +                    k = Kind.POSTFIX +                } +            } else if (right) { +                k = Kind.PREFIX +            } else { +                k = Kind.NONE +            } + +            return operators.first { +                it.operator == operator && it.kind == k +            } +        } + +        override fun toString(): String { +            return operator.toString(); +        } +    } + +    private class Operand( +        val value: Double, +    ){ +        override fun toString(): String { +            if(value % 1 == 0.0){ +                return "%.0f".format(value) +            } +            return value.toString() +        } +    } +}
\ No newline at end of file diff --git a/app/src/main/java/com/a404m/calculator/model/CalcButtonModel.kt b/app/src/main/java/com/a404m/calculator/model/CalcButtonModel.kt new file mode 100644 index 0000000..435acef --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/model/CalcButtonModel.kt @@ -0,0 +1,139 @@ +package com.a404m.calculator.model + +import com.a404m.calculator.MathHelper + +data class CalcButtonModel( +    val text: String, +    val weight:Float = 1F, +    val op: (expression: String) -> String, +) { +    companion object { +        val K_0 = CalcButtonModel( +            text = "0", +            op = { +                it + '0' +            } +        ) +        val K_1 = CalcButtonModel( +            text = "1", +            op = { +                it + '1' +            } +        ) +        val K_2 = CalcButtonModel( +            text = "2", +            op = { +                it + '2' +            } +        ) +        val K_3 = CalcButtonModel( +            text = "3", +            op = { +                it + '3' +            } +        ) +        val K_4 = CalcButtonModel( +            text = "4", +            op = { +                it + '4' +            } +        ) +        val K_5 = CalcButtonModel( +            text = "5", +            op = { +                it + '5' +            } +        ) +        val K_6 = CalcButtonModel( +            text = "6", +            op = { +                it + '6' +            } +        ) +        val K_7 = CalcButtonModel( +            text = "7", +            op = { +                it + '7' +            } +        ) +        val K_8 = CalcButtonModel( +            text = "8", +            op = { +                it + '8' +            } +        ) +        val K_9 = CalcButtonModel( +            text = "9", +            op = { +                it + '9' +            } +        ) +        val K_PLUS = CalcButtonModel( +            text = "+", +            op = { +                it + '+' +            } +        ) +        val K_MINUS = CalcButtonModel( +            text = "-", +            op = { +                it + '-' +            } +        ) +        val K_MULTIPLY = CalcButtonModel( +            text = "×", +            op = { +                it + '×' +            } +        ) +        val K_DIVISION = CalcButtonModel( +            text = "÷", +            op = { +                it + '÷' +            } +        ) +        val K_REMINDER = CalcButtonModel( +            text = "%", +            op = { +                it + '%' +            } +        ) +        val K_EQUAL = CalcButtonModel( +            text = "=", +            op = { +                MathHelper.eval(it) +            } +        ) +        val K_DOT = CalcButtonModel( +            text = ".", +            op = { +                it + '.' +            } +        ) +        val K_C = CalcButtonModel( +            text = "C", +            op = { +                "" +            } +        ) +        val K_BACKSPACE = CalcButtonModel( +            text = "⌫", +            op = { +                if (it.length <= 1) { +                    "" +                } else { +                    it.substring( +                        0, +                        it.length - 1, +                    ) +                } +            } +        ) +        val K_SWITCH_MODE = CalcButtonModel( +            text = "\u2026", +            op = { +                it +            } +        ) +    } +} diff --git a/app/src/main/java/com/a404m/calculator/ui/page/Home.kt b/app/src/main/java/com/a404m/calculator/ui/page/Home.kt new file mode 100644 index 0000000..ba9a21e --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/ui/page/Home.kt @@ -0,0 +1,120 @@ +package com.a404m.calculator.ui.page + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.a404m.calculator.model.CalcButtonModel +import com.a404m.calculator.ui.theme.CalculatorTheme +import com.a404m.calculator.ui.util.CalcGrid + +@Composable +fun HomePage(modifier: Modifier = Modifier) { +    var expression by rememberSaveable { +        mutableStateOf("") +    } +    Scaffold( +        modifier = modifier, +    ) { paddingValues -> +        Column( +            modifier = Modifier +                .padding(paddingValues) +                .fillMaxSize(), +            verticalArrangement = Arrangement.Bottom, +        ) { +            Text( +                modifier = Modifier.padding(8.dp).fillMaxWidth(), +                text = expression, +                style = MaterialTheme.typography.titleLarge, +                textAlign = TextAlign.Right, +                fontSize = 35.sp, +            ) +            HorizontalDivider(modifier = Modifier.padding(horizontal = 4.dp)) +            CalcGrid( +                modifier = Modifier +                    .padding(4.dp), +                columns = 4, +                items = arrayOf( +                    CalcButtonModel.K_C, +                    CalcButtonModel.K_BACKSPACE, +                    CalcButtonModel.K_REMINDER, +                    CalcButtonModel.K_DIVISION, +                    CalcButtonModel.K_7, +                    CalcButtonModel.K_8, +                    CalcButtonModel.K_9, +                    CalcButtonModel.K_MULTIPLY, +                    CalcButtonModel.K_4, +                    CalcButtonModel.K_5, +                    CalcButtonModel.K_6, +                    CalcButtonModel.K_MINUS, +                    CalcButtonModel.K_1, +                    CalcButtonModel.K_2, +                    CalcButtonModel.K_3, +                    CalcButtonModel.K_PLUS, +                    CalcButtonModel.K_0, +                    CalcButtonModel.K_DOT, +                    CalcButtonModel.K_SWITCH_MODE, +                    CalcButtonModel.K_EQUAL, +                ), +            ) { +                val backColor = MaterialTheme.colorScheme.primary +                Box( +                    modifier = Modifier +                        .padding(4.dp) +                        .height(70.dp) +                        .weight(it.weight) +                        .clip( +                            shape = RoundedCornerShape(10.dp), +                        ) +                        .background( +                            color = backColor, +                            shape = RoundedCornerShape(10.dp), +                        ) +                        .clickable { +                            expression = it.op(expression) +                        }, +                    contentAlignment = Alignment.Center, +                ) { +                    Text( +                        text = it.text, +                        style = MaterialTheme.typography.titleLarge, +                        color = MaterialTheme.colorScheme.contentColorFor(backColor) +                    ) +                } +            } +        } +    } +} + +@Preview +@Composable +private fun HomePagePreview() { +    CalculatorTheme { +        HomePage( +            modifier = Modifier.fillMaxSize(), +        ) +    } +}
\ No newline at end of file diff --git a/app/src/main/java/com/a404m/calculator/ui/theme/Color.kt b/app/src/main/java/com/a404m/calculator/ui/theme/Color.kt new file mode 100644 index 0000000..6a4fd22 --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.a404m.calculator.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260)
\ No newline at end of file diff --git a/app/src/main/java/com/a404m/calculator/ui/theme/Theme.kt b/app/src/main/java/com/a404m/calculator/ui/theme/Theme.kt new file mode 100644 index 0000000..20d6120 --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.a404m.calculator.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( +    primary = Purple80, +    secondary = PurpleGrey80, +    tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( +    primary = Purple40, +    secondary = PurpleGrey40, +    tertiary = Pink40 + +    /* Other default colors to override +    background = Color(0xFFFFFBFE), +    surface = Color(0xFFFFFBFE), +    onPrimary = Color.White, +    onSecondary = Color.White, +    onTertiary = Color.White, +    onBackground = Color(0xFF1C1B1F), +    onSurface = Color(0xFF1C1B1F), +    */ +) + +@Composable +fun CalculatorTheme( +    darkTheme: Boolean = isSystemInDarkTheme(), +    // Dynamic color is available on Android 12+ +    dynamicColor: Boolean = true, +    content: @Composable () -> Unit +) { +    val colorScheme = when { +        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { +            val context = LocalContext.current +            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) +        } + +        darkTheme -> DarkColorScheme +        else -> LightColorScheme +    } + +    MaterialTheme( +        colorScheme = colorScheme, +        typography = Typography, +        content = content +    ) +}
\ No newline at end of file diff --git a/app/src/main/java/com/a404m/calculator/ui/theme/Type.kt b/app/src/main/java/com/a404m/calculator/ui/theme/Type.kt new file mode 100644 index 0000000..42ec494 --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.a404m.calculator.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( +    bodyLarge = TextStyle( +        fontFamily = FontFamily.Default, +        fontWeight = FontWeight.Normal, +        fontSize = 16.sp, +        lineHeight = 24.sp, +        letterSpacing = 0.5.sp +    ) +    /* Other default text styles to override +    titleLarge = TextStyle( +        fontFamily = FontFamily.Default, +        fontWeight = FontWeight.Normal, +        fontSize = 22.sp, +        lineHeight = 28.sp, +        letterSpacing = 0.sp +    ), +    labelSmall = TextStyle( +        fontFamily = FontFamily.Default, +        fontWeight = FontWeight.Medium, +        fontSize = 11.sp, +        lineHeight = 16.sp, +        letterSpacing = 0.5.sp +    ) +    */ +)
\ No newline at end of file diff --git a/app/src/main/java/com/a404m/calculator/ui/util/Container.kt b/app/src/main/java/com/a404m/calculator/ui/util/Container.kt new file mode 100644 index 0000000..ba27afe --- /dev/null +++ b/app/src/main/java/com/a404m/calculator/ui/util/Container.kt @@ -0,0 +1,31 @@ +package com.a404m.calculator.ui.util + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun <T> CalcGrid( +    modifier: Modifier = Modifier, +    columns: Int, +    items: Array<T>, +    builder: @Composable (T) -> Unit, +) { +    Column( +        modifier = modifier, +    ) { +        var index = 0 +        while (index != items.size) { +            Row { +                for (j in 0 until columns) { +                    if(index == items.size){ +                        break +                    } +                    builder(items[index]) +                    ++index +                } +            } +        } +    } +}
\ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" +    android:width="108dp" +    android:height="108dp" +    android:viewportWidth="108" +    android:viewportHeight="108"> +    <path +        android:fillColor="#3DDC84" +        android:pathData="M0,0h108v108h-108z" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M9,0L9,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,0L19,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M29,0L29,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M39,0L39,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M49,0L49,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M59,0L59,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M69,0L69,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M79,0L79,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M89,0L89,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M99,0L99,108" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,9L108,9" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,19L108,19" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,29L108,29" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,39L108,39" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,49L108,49" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,59L108,59" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,69L108,69" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,79L108,79" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,89L108,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,99L108,99" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,29L89,29" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,39L89,39" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,49L89,49" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,59L89,59" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,69L89,69" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,79L89,79" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M29,19L29,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M39,19L39,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M49,19L49,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M59,19L59,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M69,19L69,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M79,19L79,89" +        android:strokeWidth="0.8" +        android:strokeColor="#33FFFFFF" /> +</vector> diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..c5eec94 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" +    android:width="108dp" +    android:height="108dp" +    android:viewportWidth="87" +    android:viewportHeight="167.625"> +  <group android:scaleX="0.23874721" +      android:scaleY="0.46" +      android:translateX="33.1145" +      android:translateY="45.25875"> +    <group android:translateY="133.66406"> +      <path android:pathData="M6.1875,-36.84375L80.515625,-36.84375L80.515625,-24.75L6.1875,-24.75L6.1875,-36.84375ZM6.1875,-65.390625L80.515625,-65.390625L80.515625,-53.4375L6.1875,-53.4375L6.1875,-65.390625Z" +          android:fillColor="#A437DB"/> +    </group> +  </group> +</vector>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> +    <background android:drawable="@color/ic_launcher_background"/> +    <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> +    <background android:drawable="@color/ic_launcher_background"/> +    <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000..ee62035 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000..6452b49 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000..8f60d53 --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000..3cd3848 --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000..9cb323b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000..6941df4 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000..eb514e1 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000..749d7c1 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000..e404ad0 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000..f7f408b --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> +    <color name="purple_200">#FFBB86FC</color> +    <color name="purple_500">#FF6200EE</color> +    <color name="purple_700">#FF3700B3</color> +    <color name="teal_200">#FF03DAC5</color> +    <color name="teal_700">#FF018786</color> +    <color name="black">#FF000000</color> +    <color name="white">#FFFFFFFF</color> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..3376515 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> +    <color name="ic_launcher_background">#35185A</color> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..665ca8d --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> +    <string name="app_name">Calculator</string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..f244678 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + +    <style name="Theme.Calculator" parent="android:Theme.Material.Light.NoActionBar" /> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +   Sample backup rules file; uncomment and customize as necessary. +   See https://developer.android.com/guide/topics/data/autobackup +   for details. +   Note: This file is ignored for devices older that API 31 +   See https://developer.android.com/about/versions/12/backup-restore +--> +<full-backup-content> +    <!-- +   <include domain="sharedpref" path="."/> +   <exclude domain="sharedpref" path="device.xml"/> +--> +</full-backup-content>
\ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +   Sample data extraction rules file; uncomment and customize as necessary. +   See https://developer.android.com/about/versions/12/backup-restore#xml-changes +   for details. +--> +<data-extraction-rules> +    <cloud-backup> +        <!-- TODO: Use <include> and <exclude> to control what is backed up. +        <include .../> +        <exclude .../> +        --> +    </cloud-backup> +    <!-- +    <device-transfer> +        <include .../> +        <exclude .../> +    </device-transfer> +    --> +</data-extraction-rules>
\ No newline at end of file diff --git a/app/src/test/java/com/a404m/calculator/ExampleUnitTest.kt b/app/src/test/java/com/a404m/calculator/ExampleUnitTest.kt new file mode 100644 index 0000000..4e8caf3 --- /dev/null +++ b/app/src/test/java/com/a404m/calculator/ExampleUnitTest.kt @@ -0,0 +1,20 @@ +package com.a404m.calculator + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { +    @Test +    fun addition_isCorrect() { +        assertEquals( +            4, +            2 + 2 +        ) +    } +}
\ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..952b930 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,6 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { +    alias(libs.plugins.android.application) apply false +    alias(libs.plugins.kotlin.android) apply false +    alias(libs.plugins.kotlin.compose) apply false +}
\ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..20e2a01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true
\ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..8fb3aa8 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,32 @@ +[versions] +agp = "8.7.2" +kotlin = "2.0.0" +coreKtx = "1.15.0" +junit = "4.13.2" +junitVersion = "1.2.1" +espressoCore = "3.6.1" +lifecycleRuntimeKtx = "2.8.7" +activityCompose = "1.9.3" +composeBom = "2024.04.01" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..e708b1c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1fb2651 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Dec 17 03:59:24 IRST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#      https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +##  Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do +    ls=`ls -ld "$PRG"` +    link=`expr "$ls" : '.*-> \(.*\)$'` +    if expr "$link" : '/.*' > /dev/null; then +        PRG="$link" +    else +        PRG=`dirname "$PRG"`"/$link" +    fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { +    echo "$*" +} + +die () { +    echo +    echo "$*" +    echo +    exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in +  CYGWIN* ) +    cygwin=true +    ;; +  Darwin* ) +    darwin=true +    ;; +  MINGW* ) +    msys=true +    ;; +  NONSTOP* ) +    nonstop=true +    ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then +    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +        # IBM's JDK on AIX uses strange locations for the executables +        JAVACMD="$JAVA_HOME/jre/sh/java" +    else +        JAVACMD="$JAVA_HOME/bin/java" +    fi +    if [ ! -x "$JAVACMD" ] ; then +        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +    fi +else +    JAVACMD="java" +    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then +    MAX_FD_LIMIT=`ulimit -H -n` +    if [ $? -eq 0 ] ; then +        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then +            MAX_FD="$MAX_FD_LIMIT" +        fi +        ulimit -n $MAX_FD +        if [ $? -ne 0 ] ; then +            warn "Could not set maximum file descriptor limit: $MAX_FD" +        fi +    else +        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" +    fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then +    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then +    APP_HOME=`cygpath --path --mixed "$APP_HOME"` +    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + +    JAVACMD=`cygpath --unix "$JAVACMD"` + +    # We build the pattern for arguments to be converted via cygpath +    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` +    SEP="" +    for dir in $ROOTDIRSRAW ; do +        ROOTDIRS="$ROOTDIRS$SEP$dir" +        SEP="|" +    done +    OURCYGPATTERN="(^($ROOTDIRS))" +    # Add a user-defined pattern to the cygpath arguments +    if [ "$GRADLE_CYGPATTERN" != "" ] ; then +        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" +    fi +    # Now convert the arguments - kludge to limit ourselves to /bin/sh +    i=0 +    for arg in "$@" ; do +        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` +        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option + +        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition +            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` +        else +            eval `echo args$i`="\"$arg\"" +        fi +        i=`expr $i + 1` +    done +    case $i in +        0) set -- ;; +        1) set -- "$args0" ;; +        2) set -- "$args0" "$args1" ;; +        3) set -- "$args0" "$args1" "$args2" ;; +        4) set -- "$args0" "$args1" "$args2" "$args3" ;; +        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; +        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; +        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; +        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; +        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +    esac +fi + +# Escape application args +save () { +    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done +    echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem
 +@rem Copyright 2015 the original author or authors.
 +@rem
 +@rem Licensed under the Apache License, Version 2.0 (the "License");
 +@rem you may not use this file except in compliance with the License.
 +@rem You may obtain a copy of the License at
 +@rem
 +@rem      https://www.apache.org/licenses/LICENSE-2.0
 +@rem
 +@rem Unless required by applicable law or agreed to in writing, software
 +@rem distributed under the License is distributed on an "AS IS" BASIS,
 +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 +@rem See the License for the specific language governing permissions and
 +@rem limitations under the License.
 +@rem
 +
 +@if "%DEBUG%" == "" @echo off
 +@rem ##########################################################################
 +@rem
 +@rem  Gradle startup script for Windows
 +@rem
 +@rem ##########################################################################
 +
 +@rem Set local scope for the variables with windows NT shell
 +if "%OS%"=="Windows_NT" setlocal
 +
 +set DIRNAME=%~dp0
 +if "%DIRNAME%" == "" set DIRNAME=.
 +set APP_BASE_NAME=%~n0
 +set APP_HOME=%DIRNAME%
 +
 +@rem Resolve any "." and ".." in APP_HOME to make it shorter.
 +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
 +
 +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
 +
 +@rem Find java.exe
 +if defined JAVA_HOME goto findJavaFromJavaHome
 +
 +set JAVA_EXE=java.exe
 +%JAVA_EXE% -version >NUL 2>&1
 +if "%ERRORLEVEL%" == "0" goto execute
 +
 +echo.
 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 +echo.
 +echo Please set the JAVA_HOME variable in your environment to match the
 +echo location of your Java installation.
 +
 +goto fail
 +
 +:findJavaFromJavaHome
 +set JAVA_HOME=%JAVA_HOME:"=%
 +set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 +
 +if exist "%JAVA_EXE%" goto execute
 +
 +echo.
 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
 +echo.
 +echo Please set the JAVA_HOME variable in your environment to match the
 +echo location of your Java installation.
 +
 +goto fail
 +
 +:execute
 +@rem Setup the command line
 +
 +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 +
 +
 +@rem Execute Gradle
 +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
 +
 +:end
 +@rem End local scope for the variables with windows NT shell
 +if "%ERRORLEVEL%"=="0" goto mainEnd
 +
 +:fail
 +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
 +rem the _cmd.exe /c_ return code!
 +if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
 +exit /b 1
 +
 +:mainEnd
 +if "%OS%"=="Windows_NT" endlocal
 +
 +:omega
 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..bdb5011 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +pluginManagement { +    repositories { +        google { +            content { +                includeGroupByRegex("com\\.android.*") +                includeGroupByRegex("com\\.google.*") +                includeGroupByRegex("androidx.*") +            } +        } +        mavenCentral() +        gradlePluginPortal() +    } +} +dependencyResolutionManagement { +    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) +    repositories { +        google() +        mavenCentral() +    } +} + +rootProject.name = "Calculator" +include(":app") + 
\ No newline at end of file  |