├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml └── misc.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── anafthdev │ │ └── calcc │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── anafthdev │ │ │ └── calcc │ │ │ ├── CalcCApplication.kt │ │ │ ├── MainActivity.kt │ │ │ ├── data │ │ │ ├── Calc.kt │ │ │ ├── CalcCAction.kt │ │ │ ├── CalcCAdvancedButton.kt │ │ │ ├── CalcCButtons.kt │ │ │ ├── CalcCNumber.kt │ │ │ ├── CalcCOperation.kt │ │ │ ├── Operations.kt │ │ │ └── Parser.kt │ │ │ ├── foundation │ │ │ ├── di │ │ │ │ ├── DiName.kt │ │ │ │ └── DispatcherModule.kt │ │ │ ├── extension │ │ │ │ ├── AnyExt.kt │ │ │ │ ├── CollectionExt.kt │ │ │ │ ├── DoubleExt.kt │ │ │ │ └── StringExt.kt │ │ │ ├── viewmodel │ │ │ │ └── StatefulViewModel.kt │ │ │ └── window │ │ │ │ └── Dimen.kt │ │ │ └── ui │ │ │ ├── main │ │ │ ├── MainAction.kt │ │ │ ├── MainScreen.kt │ │ │ ├── MainState.kt │ │ │ ├── MainViewModel.kt │ │ │ ├── component │ │ │ │ ├── CalcCAdvancedButton.kt │ │ │ │ └── CalcCButtons.kt │ │ │ ├── di │ │ │ │ └── MainModule.kt │ │ │ └── environment │ │ │ │ ├── IMainEnvironment.kt │ │ │ │ └── MainEnvironment.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_app_icon.xml │ │ ├── ic_delete.xml │ │ ├── ic_launcher_background.xml │ │ └── splash_drawable.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-ldpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── anafthdev │ └── calcc │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── img_1.jpg ├── img_2.jpg ├── img_3.jpg └── img_4.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calculator-Compose 2 | 3 | Calculator with Jetpack Compose 4 | 5 | Math expression evaluator: [exp4j](https://www.objecthunter.net/exp4j/) 6 | 7 | **Preview** 8 | 9 |

10 | 11 | 12 | 13 | 14 |

15 | 16 | ## Architecture 17 | * [Mercury architecture](https://github.com/wisnukurniawan/Compose-ToDo/blob/main/doc/architecture.md) 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'dagger.hilt.android.plugin' 5 | id "kotlin-kapt" 6 | } 7 | 8 | android { 9 | compileSdk 32 10 | 11 | defaultConfig { 12 | applicationId "com.anafthdev.calcc" 13 | minSdk 24 14 | targetSdk 32 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | } 23 | 24 | buildTypes { 25 | debug { 26 | minifyEnabled false 27 | kotlinOptions { 28 | freeCompilerArgs += [ 29 | '-Xopt-in=kotlin.RequiresOptIn' 30 | ] 31 | } 32 | } 33 | 34 | release { 35 | minifyEnabled true 36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 37 | kotlinOptions { 38 | freeCompilerArgs += [ 39 | '-Xopt-in=kotlin.RequiresOptIn' 40 | ] 41 | } 42 | } 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_1_8 46 | targetCompatibility JavaVersion.VERSION_1_8 47 | } 48 | kotlinOptions { 49 | jvmTarget = '1.8' 50 | } 51 | buildFeatures { 52 | compose true 53 | } 54 | composeOptions { 55 | kotlinCompilerExtensionVersion compose_version 56 | } 57 | packagingOptions { 58 | resources { 59 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 60 | } 61 | } 62 | } 63 | 64 | dependencies { 65 | 66 | implementation 'androidx.core:core-ktx:1.8.0' 67 | implementation 'androidx.activity:activity-compose:1.4.0' 68 | implementation "androidx.compose.ui:ui:$compose_version" 69 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 70 | implementation "androidx.compose.ui:ui-util:1.2.0-beta03" 71 | implementation "androidx.navigation:navigation-compose:2.5.0-rc01" 72 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' 73 | implementation "androidx.hilt:hilt-navigation-compose:1.0.0" 74 | 75 | // Material Design 76 | implementation 'com.google.android.material:material:1.6.1' 77 | implementation "androidx.compose.material:material:1.2.0-beta03" 78 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 79 | implementation "androidx.compose.material3:material3:1.0.0-alpha13" 80 | 81 | // Dependency Injection 82 | implementation 'com.google.dagger:hilt-android:2.39.1' 83 | implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03' 84 | kapt 'androidx.hilt:hilt-compiler:1.0.0' 85 | kapt 'com.google.dagger:hilt-compiler:2.39.1' 86 | kapt 'com.google.dagger:hilt-android-compiler:2.39.1' 87 | 88 | // Room 89 | implementation 'androidx.room:room-runtime:2.4.2' 90 | implementation 'androidx.room:room-ktx:2.4.2' 91 | kapt 'androidx.room:room-compiler:2.4.2' 92 | 93 | // Accompanist 94 | implementation "com.google.accompanist:accompanist-systemuicontroller:0.23.1" 95 | 96 | // Other 97 | implementation 'com.intuit.sdp:sdp-android:1.0.6' 98 | implementation 'com.jakewharton.timber:timber:5.0.1' 99 | implementation 'net.objecthunter:exp4j:0.4.8' 100 | 101 | testImplementation 'junit:junit:4.13.2' 102 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 103 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 104 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 105 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 106 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/anafthdev/calcc/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.anafthdev.calcc", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/CalcCApplication.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class CalcCApplication: Application() { 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Surface 8 | import androidx.compose.runtime.CompositionLocalProvider 9 | import com.anafthdev.calcc.foundation.window.LocalComponentSize 10 | import com.anafthdev.calcc.foundation.window.getComponentSize 11 | import com.anafthdev.calcc.ui.main.MainScreen 12 | import com.anafthdev.calcc.ui.theme.CalcCTheme 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import timber.log.Timber 15 | 16 | @AndroidEntryPoint 17 | class MainActivity : ComponentActivity() { 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | if (BuildConfig.DEBUG) { 21 | Timber.plant(object : Timber.DebugTree() { 22 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 23 | super.log(priority, "DEBUG_$tag", message, t) 24 | } 25 | }) 26 | } 27 | 28 | setContent { 29 | CalcCTheme { 30 | Surface( 31 | color = MaterialTheme.colorScheme.background 32 | ) { 33 | CompositionLocalProvider( 34 | LocalComponentSize provides getComponentSize() 35 | ) { 36 | MainScreen() 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/Calc.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | interface Calc { 4 | 5 | val symbol: String 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/CalcCAction.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | sealed class CalcCAction { 4 | 5 | data class Clear(override val symbol: String = "C"): CalcCAction(), Calc 6 | 7 | data class Delete(override val symbol: String = "<-"): CalcCAction(), Calc 8 | 9 | data class Calculate(override val symbol: String = "="): CalcCAction(), Calc 10 | 11 | data class Decimal( 12 | override val symbol: String = ",", 13 | val operatorSymbol: String = "." 14 | ): CalcCAction(), Calc 15 | 16 | data class Percent( 17 | override val symbol: String = "%", 18 | val operatorSymbol: String = "!|" 19 | ): CalcCAction(), Calc 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/CalcCAdvancedButton.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | sealed class CalcCAdvancedButton { 4 | 5 | data class Rad(override val symbol: String = "RAD"): CalcCAdvancedButton(), Calc 6 | 7 | data class Deg(override val symbol: String = "DEG"): CalcCAdvancedButton(), Calc 8 | 9 | data class Inverse(override val symbol: String = "INV"): CalcCAdvancedButton(), Calc 10 | 11 | data class OpenParenthesis(override val symbol: String = "("): CalcCAdvancedButton(), Calc 12 | 13 | data class CloseParenthesis(override val symbol: String = ")"): CalcCAdvancedButton(), Calc 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/CalcCButtons.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | object CalcCButtons { 4 | 5 | val buttons = listOf( 6 | CalcCAction.Clear(), 7 | CalcCAction.Percent(), 8 | CalcCAction.Delete(), 9 | CalcCOperation.Div(), 10 | 11 | CalcCNumber.Seven(), 12 | CalcCNumber.Eight(), 13 | CalcCNumber.Nine(), 14 | CalcCOperation.Mul(), 15 | 16 | CalcCNumber.Four(), 17 | CalcCNumber.Five(), 18 | CalcCNumber.Six(), 19 | CalcCOperation.Sub(), 20 | 21 | CalcCNumber.One(), 22 | CalcCNumber.Two(), 23 | CalcCNumber.Three(), 24 | CalcCOperation.Add(), 25 | 26 | CalcCNumber.DoubleZero(), 27 | CalcCNumber.Zero(), 28 | CalcCAction.Decimal(), 29 | CalcCAction.Calculate() 30 | 31 | ) 32 | 33 | val advancedButtons: List> = listOf( 34 | CalcCOperation.Sin() to CalcCOperation.ArcSin(), 35 | CalcCOperation.Cos() to CalcCOperation.ArcCos(), 36 | CalcCOperation.Tan() to CalcCOperation.ArcTan(), 37 | CalcCOperation.Log() to CalcCOperation.InvLog(), 38 | CalcCOperation.NaturalLogarithm() to CalcCOperation.InvNaturalLogarithm(), 39 | 40 | CalcCAdvancedButton.OpenParenthesis() to CalcCAdvancedButton.OpenParenthesis(), 41 | CalcCAdvancedButton.CloseParenthesis() to CalcCAdvancedButton.CloseParenthesis(), 42 | CalcCOperation.Pow() to CalcCOperation.Pow(), 43 | CalcCOperation.Sqrt() to CalcCOperation.InvSqrt(), 44 | CalcCOperation.Factorial() to CalcCOperation.Factorial(), 45 | 46 | CalcCOperation.Pi() to CalcCOperation.Pi(), 47 | CalcCOperation.Exp() to CalcCOperation.Exp(), 48 | CalcCAdvancedButton.Inverse() to CalcCAdvancedButton.Inverse(), 49 | CalcCAdvancedButton.Rad() to CalcCAdvancedButton.Rad(), 50 | CalcCAdvancedButton.Deg() to CalcCAdvancedButton.Deg() 51 | ) 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/CalcCNumber.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | sealed class CalcCNumber { 4 | data class DoubleZero(override val symbol: String = "00"): CalcCNumber(), Calc 5 | data class Zero(override val symbol: String = "0"): CalcCNumber(), Calc 6 | data class One(override val symbol: String = "1"): CalcCNumber(), Calc 7 | data class Two(override val symbol: String = "2"): CalcCNumber(), Calc 8 | data class Three(override val symbol: String = "3"): CalcCNumber(), Calc 9 | data class Four(override val symbol: String = "4"): CalcCNumber(), Calc 10 | data class Five(override val symbol: String = "5"): CalcCNumber(), Calc 11 | data class Six(override val symbol: String = "6"): CalcCNumber(), Calc 12 | data class Seven(override val symbol: String = "7"): CalcCNumber(), Calc 13 | data class Eight(override val symbol: String = "8"): CalcCNumber(), Calc 14 | data class Nine(override val symbol: String = "9"): CalcCNumber(), Calc 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/CalcCOperation.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | sealed class CalcCOperation { 4 | 5 | data class Pi(override val symbol: String = "π"): CalcCOperation(), Calc 6 | 7 | data class Div( 8 | override val symbol: String = "÷", 9 | val operatorSymbol: String = "/" 10 | ): CalcCOperation(), Calc 11 | 12 | data class Mul( 13 | override val symbol: String = "x", 14 | val operatorSymbol: String = "/" 15 | ): CalcCOperation(), Calc 16 | 17 | data class Add(override val symbol: String = "+"): CalcCOperation(), Calc 18 | 19 | data class Sub(override val symbol: String = "-"): CalcCOperation(), Calc 20 | 21 | data class Sin(override val symbol: String = "sin"): CalcCOperation(), Calc 22 | 23 | data class Cos(override val symbol: String = "cos"): CalcCOperation(), Calc 24 | 25 | data class Tan(override val symbol: String = "tan"): CalcCOperation(), Calc 26 | 27 | data class Log(override val symbol: String = "log"): CalcCOperation(), Calc 28 | 29 | data class Pow(override val symbol: String = "^"): CalcCOperation(), Calc 30 | 31 | data class Exp(override val symbol: String = "e"): CalcCOperation(), Calc 32 | 33 | data class Sqrt(override val symbol: String = "√"): CalcCOperation(), Calc 34 | 35 | data class Factorial(override val symbol: String = "!"): CalcCOperation(), Calc 36 | 37 | data class NaturalLogarithm(override val symbol: String = "ln"): CalcCOperation(), Calc 38 | 39 | 40 | 41 | data class ArcSin( 42 | override val symbol: String = "sin -1", 43 | val operatorSymbol: String = Operations.INV_SIN.first 44 | ): CalcCOperation(), Calc 45 | 46 | data class ArcCos( 47 | override val symbol: String = "cos -1", 48 | val operatorSymbol: String = Operations.INV_COS.first 49 | ): CalcCOperation(), Calc 50 | 51 | data class ArcTan( 52 | override val symbol: String = "tan -1", 53 | val operatorSymbol: String = Operations.INV_TAN.first 54 | ): CalcCOperation(), Calc 55 | 56 | data class InvLog( 57 | override val symbol: String = "10 x", 58 | val operatorSymbol: String = "10^" 59 | ): CalcCOperation(), Calc 60 | 61 | data class InvSqrt( 62 | override val symbol: String = "x 2", 63 | val operatorSymbol: String = "#" 64 | ): CalcCOperation(), Calc 65 | 66 | data class InvNaturalLogarithm( 67 | override val symbol: String = "e x", 68 | val operatorSymbol: String = "e^" 69 | ): CalcCOperation(), Calc 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/Operations.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | object Operations { 4 | 5 | val LOG_10 = "log" to "log10" 6 | 7 | val DECIMAL = "," to "." 8 | 9 | val PERCENT = "%" to "!|" 10 | 11 | val DIVISION = "÷" to "/" 12 | 13 | val MULTIPLICATION = "x" to "*" 14 | 15 | val INV_SIN = "ˠ" to "asin" 16 | 17 | val INV_COS = "〥" to "acos" 18 | 19 | val INV_TAN = "ˁ" to "atan" 20 | 21 | val INV_SIN_DEGREES = "¤" to "ASIN" 22 | 23 | val INV_COS_DEGREES = "≭" to "ACOS" 24 | 25 | val INV_TAN_DEGREES = "⥿" to "ATAN" 26 | 27 | val SIN_DEGREES = "sin" to "Sin" 28 | 29 | val COS_DEGREES = "cos" to "Cos" 30 | 31 | val TAN_DEGREES = "tan" to "Tan" 32 | 33 | val values = listOf( 34 | LOG_10, 35 | DECIMAL, 36 | PERCENT, 37 | DIVISION, 38 | MULTIPLICATION, 39 | INV_SIN, 40 | INV_COS, 41 | INV_TAN, 42 | INV_SIN_DEGREES, 43 | INV_COS_DEGREES, 44 | INV_TAN_DEGREES, 45 | SIN_DEGREES, 46 | COS_DEGREES, 47 | TAN_DEGREES 48 | ) 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/data/Parser.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.data 2 | 3 | import com.anafthdev.calcc.foundation.extension.find 4 | import com.anafthdev.calcc.foundation.extension.indexAll 5 | import com.anafthdev.calcc.foundation.extension.replaceAB 6 | import java.lang.Exception 7 | 8 | object Parser { 9 | 10 | fun parsePowTwo(exp: String): String { 11 | var result = exp 12 | val reversedExp = exp.reversed().replaceAB("(", ")") 13 | val hashtags = reversedExp.indexAll('#').map { it + 1 } 14 | val powResult = arrayListOf>() 15 | 16 | hashtags.forEach { i -> 17 | var hashtagV = "" 18 | 19 | if (reversedExp[i] == '(') { 20 | var totalOpenBrackets = 1 21 | var totalCloseBrackets = 0 22 | val closeBrackets = reversedExp.find(')', i, reversedExp.length) 23 | 24 | for (c in reversedExp.substring(i).also { println(it) }) { 25 | if (c == '(') { 26 | totalOpenBrackets++ 27 | } else if (c == ')') { 28 | totalCloseBrackets++ 29 | if (totalOpenBrackets == totalCloseBrackets) break 30 | } 31 | } 32 | 33 | if (closeBrackets.isNotEmpty()) { 34 | hashtagV += "${reversedExp.substring(i, closeBrackets[totalCloseBrackets-1] + 1)})".also { 35 | println(it) 36 | } 37 | } 38 | } else { 39 | for (c in reversedExp.substring(i)) { 40 | if (c.isDigit()) { 41 | hashtagV += c 42 | } else break 43 | } 44 | } 45 | 46 | hashtagV = hashtagV.reversed().replaceAB("(", ")") 47 | 48 | powResult.add( 49 | "$hashtagV#" to "twopow$hashtagV" 50 | ) 51 | } 52 | 53 | powResult.forEach { 54 | result = result.replace(it.first, it.second) 55 | } 56 | 57 | return result 58 | } 59 | 60 | fun parseSqrt(exp: String): String { 61 | return try { 62 | var result = exp 63 | val sqrtIndex = result.indexAll('√').map { it + 1 } 64 | val sqrtList = arrayListOf() 65 | 66 | sqrtIndex.forEach { i -> 67 | var sqrtV = "sqrt(" 68 | 69 | if (result[i] == '(') { 70 | var totalOpenBrackets = 0 71 | val closeBrackets = result.find(')', i, result.length) 72 | 73 | for (c in result.substring(i)) { 74 | if (c == '(') { 75 | totalOpenBrackets++ 76 | } else if (c == ')') { 77 | break 78 | } 79 | } 80 | 81 | if (closeBrackets.isNotEmpty()) { 82 | sqrtV += "${result.substring(i, closeBrackets[totalOpenBrackets - 1] + 1)})" 83 | } 84 | } else { 85 | val subResult = result.substring(i) 86 | for ((iC, c) in subResult.withIndex()) { 87 | if (c.isDigit()) { 88 | sqrtV += c 89 | if (iC == subResult.length-1) sqrtV += ')' 90 | } else { 91 | sqrtV += ')' 92 | break 93 | } 94 | } 95 | } 96 | 97 | sqrtList.add( 98 | sqrtV 99 | ) 100 | } 101 | 102 | sqrtList.forEach { s -> 103 | result = result.replace( 104 | oldValue = "√" + s.replace("sqrt(", "").let { 105 | it.substring(0, it.length - 1) 106 | }, 107 | newValue = s 108 | ) 109 | } 110 | 111 | return result 112 | } catch (e: Exception) { 113 | exp 114 | } 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/di/DiName.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.di 2 | 3 | object DiName { 4 | 5 | const val DISPATCHER_IO = "Dispatcher.IO" 6 | 7 | const val DISPATCHER_MAIN = "Dispatcher.Main" 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/di/DispatcherModule.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.di 2 | 3 | import com.anafthdev.calcc.foundation.di.DiName 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import kotlinx.coroutines.CoroutineDispatcher 9 | import kotlinx.coroutines.Dispatchers 10 | import javax.inject.Named 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object DispatcherModule { 15 | 16 | @Provides 17 | @Named(DiName.DISPATCHER_IO) 18 | fun provideDispatcherIo(): CoroutineDispatcher { 19 | return Dispatchers.IO 20 | } 21 | 22 | @Provides 23 | @Named(DiName.DISPATCHER_MAIN) 24 | fun provideDispatcherMain(): CoroutineDispatcher { 25 | return Dispatchers.Main 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/extension/AnyExt.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.extension 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.widget.Toast 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.platform.LocalContext 8 | 9 | fun Any?.toast(context: Context, length: Int = Toast.LENGTH_SHORT) = 10 | Toast.makeText(context, this.toString(), length).show() 11 | 12 | @SuppressLint("ComposableNaming") 13 | @Composable 14 | fun Any?.toast(length: Int = Toast.LENGTH_SHORT) = 15 | Toast.makeText(LocalContext.current, this.toString(), length).show() 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/extension/CollectionExt.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.extension 2 | 3 | /** 4 | * example: 5 | * a = [1, 2, 3, 4, 5] 6 | * a.split(3) 7 | * 8 | * return Pair(2, [[1, 2, 3], [4, 5, null]]) 9 | * 10 | * @author kafri8889 11 | */ 12 | fun Collection.split(n: Int): Pair>> { 13 | val result = arrayListOf>() 14 | 15 | var splitSize = 0 16 | var counter = 0 17 | 18 | for (i in indices) { 19 | counter++ 20 | 21 | if (counter == n) { 22 | splitSize++ 23 | counter = 0 24 | } 25 | 26 | if ((i == size - 1) and (counter != 0)) splitSize++ 27 | } 28 | 29 | repeat(splitSize) { i -> 30 | val tList = arrayListOf() 31 | val range = i.plus(1).times(n).minus(n)..i.plus(1).times(n) 32 | 33 | repeat(n) { index -> 34 | tList.add( 35 | elementAtOrNull(range.first + index) 36 | ) 37 | } 38 | 39 | result.add(tList) 40 | } 41 | 42 | return splitSize to result 43 | } 44 | 45 | /** 46 | * Returns a list element from given collection 47 | * @author kafri8889 48 | */ 49 | inline fun Collection.getBy(selector: (T) -> U): List { 50 | val result = ArrayList() 51 | for (v in this) { result.add(selector(v)) } 52 | return result 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/extension/DoubleExt.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.extension 2 | 3 | fun Double.isInt(): Boolean { 4 | return (this % 1.0) == 0.0 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/extension/StringExt.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.extension 2 | 3 | inline fun String.ifNotBlank(defaultValue: () -> String): String = 4 | if (isNotBlank()) defaultValue() else this 5 | 6 | /** 7 | * ex: 8 | * strings = ["ab", "cd"] 9 | * "cd".equalsOr(strings) => true 10 | * "ab".equalsOr(strings) => true 11 | * "ef".equalsOr(strings) => false 12 | */ 13 | fun String.equalsOr(strings: List, ignoreCase: Boolean = false): Boolean { 14 | strings.forEach { 15 | if (contentEquals(it, ignoreCase)) return true 16 | } 17 | 18 | return false 19 | } 20 | 21 | /** 22 | * replace char, A to B, B to A 23 | * @author kafri8889 24 | */ 25 | fun String.replaceAB(a: String, b: String): String { 26 | val temp = replace(b, "~|~") 27 | return temp.replace(a, b).replace("~|~", a) 28 | } 29 | 30 | /** 31 | * @author kafri8889 32 | */ 33 | fun String.indexAll(char: Char): List { 34 | val result = arrayListOf() 35 | 36 | forEachIndexed { i, c -> 37 | if (c == char) result.add(i) 38 | } 39 | 40 | return result 41 | } 42 | 43 | /** 44 | * @author kafri8889 45 | */ 46 | fun String.find(char: Char, from: Int, to: Int): List { 47 | val result = arrayListOf() 48 | 49 | substring(from, to).forEachIndexed { i, c -> 50 | if (c == char) result.add(i + from) 51 | } 52 | 53 | return result 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/viewmodel/StatefulViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import kotlinx.coroutines.channels.Channel 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | import kotlinx.coroutines.flow.StateFlow 7 | import kotlinx.coroutines.flow.asStateFlow 8 | import kotlinx.coroutines.flow.receiveAsFlow 9 | 10 | /** 11 | * State: A type that describes the data your feature needs to perform its logic and render its UI. 12 | * The state is persistence during feature lifetime. 13 | * 14 | * Effect: Similar to state but it is not persistence such as navigation, show toast, show snackbar. 15 | 16 | * Action: A type that represents all of the actions that cause the state of the application to 17 | * change such as user actions, notifications, event sources and more. 18 | 19 | * Environment: A type that holds any dependencies the feature needs, such as 20 | * API clients, analytics clients, etc to decouple between UI layer and Data layer. 21 | */ 22 | abstract class StatefulViewModel( 23 | initialState: STATE, 24 | protected val environment: ENVIRONMENT 25 | ): ViewModel() { 26 | 27 | private val _state = MutableStateFlow(initialState) 28 | val state: StateFlow = _state.asStateFlow() 29 | 30 | private val _effect = Channel(Channel.BUFFERED) 31 | val effect = _effect.receiveAsFlow() 32 | 33 | protected suspend fun setEffect(newEffect: EFFECT) { 34 | _effect.send(newEffect) 35 | } 36 | 37 | protected suspend fun setState(newState: STATE.() -> STATE) { 38 | _state.emit(stateValue().newState()) 39 | } 40 | 41 | private fun stateValue(): STATE { 42 | return state.value 43 | } 44 | 45 | abstract fun dispatch(action: ACTION) 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/foundation/window/Dimen.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.foundation.window 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.ProvidableCompositionLocal 5 | import androidx.compose.runtime.compositionLocalOf 6 | import androidx.compose.ui.platform.LocalDensity 7 | import androidx.compose.ui.unit.DpSize 8 | import androidx.compose.ui.unit.dp 9 | 10 | val LocalComponentSize: ProvidableCompositionLocal = compositionLocalOf { 11 | ComponentSizeMDPI() 12 | } 13 | 14 | interface ComponentSize { 15 | val calcCButton: DpSize 16 | } 17 | 18 | class ComponentSizeLDPI( 19 | override val calcCButton: DpSize = DpSize(44.dp, 44.dp) 20 | ): ComponentSize 21 | 22 | class ComponentSizeMDPI( 23 | override val calcCButton: DpSize = DpSize(48.dp, 48.dp) 24 | ): ComponentSize 25 | 26 | class ComponentSizeHDPI( 27 | override val calcCButton: DpSize = DpSize(52.dp, 52.dp) 28 | ): ComponentSize 29 | 30 | class ComponentSizeXHDPI( 31 | override val calcCButton: DpSize = DpSize(56.dp, 56.dp) 32 | ): ComponentSize 33 | 34 | class ComponentSizeXXHDPI( 35 | override val calcCButton: DpSize = DpSize(60.dp, 60.dp) 36 | ): ComponentSize 37 | 38 | class ComponentSizeXXXHDPI( 39 | override val calcCButton: DpSize = DpSize(64.dp, 64.dp) 40 | ): ComponentSize 41 | 42 | @Composable 43 | fun getComponentSize(): ComponentSize { 44 | return when (DPI.getDPI(LocalDensity.current.density)) { 45 | DPI.LDPI -> ComponentSizeLDPI() 46 | DPI.MDPI -> ComponentSizeMDPI() 47 | DPI.HDPI -> ComponentSizeHDPI() 48 | DPI.XHDPI -> ComponentSizeXHDPI() 49 | DPI.XXHDPI -> ComponentSizeXXHDPI() 50 | DPI.XXXHDPI -> ComponentSizeXXXHDPI() 51 | } 52 | } 53 | 54 | sealed class DPI(val density: Float) { 55 | object LDPI: DPI(0.75f) 56 | object MDPI: DPI(1f) 57 | object HDPI: DPI(1.5f) 58 | object XHDPI: DPI(2f) 59 | object XXHDPI: DPI(3f) 60 | object XXXHDPI: DPI(4f) 61 | 62 | companion object { 63 | fun getDPI(density: Float): DPI { 64 | return when (density) { 65 | in 0f..0.75f -> LDPI 66 | in 0.71f..1f -> MDPI 67 | in 1.1f..1.5f -> HDPI 68 | in 1.51f..2f -> XHDPI 69 | in 2.1f..3f -> XXHDPI 70 | in 3.1f..4f -> XXXHDPI 71 | else -> MDPI 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/MainAction.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main 2 | 3 | sealed class MainAction { 4 | data class UpdateExpression(val expression: String): MainAction() 5 | data class UpdateUseDegOrRad(val useDeg: Boolean): MainAction() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.animation.AnimatedVisibility 5 | import androidx.compose.animation.expandVertically 6 | import androidx.compose.animation.shrinkVertically 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.horizontalScroll 9 | import androidx.compose.foundation.layout.* 10 | import androidx.compose.foundation.rememberScrollState 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.rounded.ArrowDropDown 13 | import androidx.compose.material3.* 14 | import androidx.compose.runtime.* 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.draw.rotate 18 | import androidx.compose.ui.graphics.Brush 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.platform.LocalConfiguration 21 | import androidx.compose.ui.text.AnnotatedString 22 | import androidx.compose.ui.text.buildAnnotatedString 23 | import androidx.compose.ui.text.withStyle 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.zIndex 26 | import androidx.hilt.navigation.compose.hiltViewModel 27 | import com.anafthdev.calcc.data.Calc 28 | import com.anafthdev.calcc.data.Operations 29 | import com.anafthdev.calcc.ui.main.component.CalcCAdvancedButton 30 | import com.anafthdev.calcc.ui.main.component.CalcCButtons 31 | import com.anafthdev.calcc.ui.theme.superscriptSpanStyle 32 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 33 | 34 | @OptIn(ExperimentalMaterial3Api::class) 35 | @Composable 36 | fun MainScreen() { 37 | 38 | val backgroundColorScheme = MaterialTheme.colorScheme.background 39 | 40 | val config = LocalConfiguration.current 41 | 42 | val mainViewModel = hiltViewModel() 43 | 44 | val state by mainViewModel.state.collectAsState() 45 | 46 | val expression = state.expression 47 | val calculationResult = state.calculationResult 48 | val isInverse = state.isInverse 49 | val useDeg = state.useDeg 50 | 51 | val systemUiController = rememberSystemUiController() 52 | val expressionTextScrollState = rememberScrollState() 53 | 54 | var isAdvancedButtonExpanded by remember { mutableStateOf(false) } 55 | 56 | LaunchedEffect(expression) { 57 | expressionTextScrollState.animateScrollTo(0) 58 | } 59 | 60 | SideEffect { 61 | systemUiController.setSystemBarsColor( 62 | color = backgroundColorScheme 63 | ) 64 | } 65 | 66 | Column( 67 | modifier = Modifier 68 | .fillMaxSize() 69 | ) { 70 | Box( 71 | contentAlignment = Alignment.CenterEnd, 72 | modifier = Modifier 73 | .fillMaxWidth() 74 | .height( 75 | config.screenHeightDp 76 | .div(4) 77 | .dp 78 | ) 79 | ) { 80 | Box( 81 | modifier = Modifier 82 | .fillMaxHeight() 83 | .width( 84 | config.screenWidthDp 85 | .div(9) 86 | .dp 87 | ) 88 | .background( 89 | brush = Brush.horizontalGradient( 90 | listOf( 91 | Color.Transparent, 92 | backgroundColorScheme 93 | ) 94 | ) 95 | ) 96 | .zIndex(2f) 97 | .align(Alignment.CenterEnd) 98 | ) 99 | 100 | Column( 101 | horizontalAlignment = Alignment.End 102 | ) { 103 | Text( 104 | text = buildAnnotatedString { 105 | expression.forEach { c -> 106 | when (c) { 107 | Operations.INV_SIN.first[0] -> { 108 | append("sin") 109 | createSuperscript("-1") 110 | } 111 | Operations.INV_COS.first[0] -> { 112 | append("cos") 113 | createSuperscript("-1") 114 | } 115 | Operations.INV_TAN.first[0] -> { 116 | append("tan") 117 | createSuperscript("-1") 118 | } 119 | Operations.INV_SIN_DEGREES.first[0] -> { 120 | append("sin") 121 | createSuperscript("-1") 122 | } 123 | Operations.INV_COS_DEGREES.first[0] -> { 124 | append("cos") 125 | createSuperscript("-1") 126 | } 127 | Operations.INV_TAN_DEGREES.first[0] -> { 128 | append("tan") 129 | createSuperscript("-1") 130 | } 131 | '#' -> createSuperscript("2") 132 | else -> append(c) 133 | } 134 | } 135 | }, 136 | style = MaterialTheme.typography.headlineLarge, 137 | modifier = Modifier 138 | .horizontalScroll( 139 | state = expressionTextScrollState, 140 | reverseScrolling = true 141 | ) 142 | .padding( 143 | horizontal = 24.dp 144 | ) 145 | ) 146 | 147 | Text( 148 | text = calculationResult, 149 | style = MaterialTheme.typography.headlineSmall.copy( 150 | color = Color.Gray 151 | ), 152 | modifier = Modifier 153 | .padding( 154 | horizontal = 24.dp 155 | ) 156 | ) 157 | } 158 | 159 | Box( 160 | modifier = Modifier 161 | .fillMaxHeight() 162 | .width( 163 | config.screenWidthDp 164 | .div(9) 165 | .dp 166 | ) 167 | .background( 168 | brush = Brush.horizontalGradient( 169 | listOf( 170 | backgroundColorScheme, 171 | Color.Transparent 172 | ) 173 | ) 174 | ) 175 | .zIndex(2f) 176 | .align(Alignment.CenterStart) 177 | ) 178 | } 179 | 180 | Spacer( 181 | modifier = Modifier 182 | .weight(1f) 183 | ) 184 | 185 | Column( 186 | modifier = Modifier 187 | .background(MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.18f)) 188 | ) { 189 | IconButton( 190 | onClick = { 191 | isAdvancedButtonExpanded = !isAdvancedButtonExpanded 192 | }, 193 | modifier = Modifier 194 | .padding(4.dp) 195 | .rotate( 196 | if (isAdvancedButtonExpanded) 180f else 360f 197 | ) 198 | .align(Alignment.CenterHorizontally) 199 | ) { 200 | Icon( 201 | imageVector = Icons.Rounded.ArrowDropDown, 202 | contentDescription = null 203 | ) 204 | } 205 | 206 | AnimatedVisibility( 207 | visible = isAdvancedButtonExpanded, 208 | enter = expandVertically(), 209 | exit = shrinkVertically() 210 | ) { 211 | Column( 212 | modifier = Modifier 213 | .fillMaxWidth() 214 | .background(MaterialTheme.colorScheme.onSurfaceVariant) 215 | .padding(8.dp) 216 | .wrapContentHeight() 217 | ) { 218 | CalcCAdvancedButton( 219 | isInverse = isInverse, 220 | useDeg = useDeg, 221 | onUpdateExpression = { calc -> 222 | updateExpression(mainViewModel, expression, calc) 223 | }, 224 | onUseDegOrRad = { mUseDeg -> 225 | mainViewModel.dispatch( 226 | MainAction.UpdateUseDegOrRad(mUseDeg) 227 | ) 228 | } 229 | ) 230 | } 231 | } 232 | 233 | Spacer( 234 | modifier = Modifier 235 | .padding(4.dp) 236 | ) 237 | 238 | CalcCButtons( 239 | isAdvancedButtonExpanded = isAdvancedButtonExpanded, 240 | onUpdateExpression = { calc -> 241 | updateExpression(mainViewModel, expression, calc) 242 | } 243 | ) 244 | 245 | Spacer( 246 | modifier = Modifier 247 | .fillMaxWidth() 248 | .padding(8.dp) 249 | ) 250 | } 251 | } 252 | } 253 | 254 | @SuppressLint("ComposableNaming") 255 | @Composable 256 | private fun AnnotatedString.Builder.createSuperscript(s: String) { 257 | withStyle( 258 | superscriptSpanStyle.copy( 259 | fontSize = MaterialTheme.typography.bodyMedium.fontSize 260 | ) 261 | ) { 262 | append(s) 263 | } 264 | } 265 | 266 | private fun updateExpression( 267 | mainViewModel: MainViewModel, 268 | exp: String, 269 | calc: Calc 270 | ) { 271 | mainViewModel.dispatch( 272 | MainAction.UpdateExpression( 273 | mainViewModel.getExpression(exp, calc) 274 | ) 275 | ) 276 | } 277 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/MainState.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main 2 | 3 | data class MainState( 4 | val expression: String = "", 5 | val calculationResult: String = "", 6 | val isInverse: Boolean = false, 7 | val useDeg: Boolean = true, 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import com.anafthdev.calcc.data.* 5 | import com.anafthdev.calcc.foundation.extension.ifNotBlank 6 | import com.anafthdev.calcc.foundation.viewmodel.StatefulViewModel 7 | import com.anafthdev.calcc.ui.main.environment.IMainEnvironment 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.collect 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class MainViewModel @Inject constructor( 15 | environment: IMainEnvironment 16 | ): StatefulViewModel( 17 | MainState(), 18 | environment 19 | ) { 20 | 21 | init { 22 | viewModelScope.launch(environment.dispatcher) { 23 | environment.getExpression().collect { exp -> 24 | environment.calculate() 25 | setState { 26 | copy( 27 | expression = exp 28 | ) 29 | } 30 | } 31 | } 32 | 33 | viewModelScope.launch(environment.dispatcher) { 34 | environment.getCalculationResult().collect { result -> 35 | setState { 36 | copy( 37 | calculationResult = result 38 | ) 39 | } 40 | } 41 | } 42 | 43 | viewModelScope.launch(environment.dispatcher) { 44 | environment.getInverse().collect { isInverse -> 45 | setState { 46 | copy( 47 | isInverse = isInverse 48 | ) 49 | } 50 | } 51 | } 52 | 53 | viewModelScope.launch(environment.dispatcher) { 54 | environment.getUseDeg().collect { useDeg -> 55 | setState { 56 | copy( 57 | useDeg = useDeg 58 | ) 59 | } 60 | } 61 | } 62 | } 63 | 64 | override fun dispatch(action: MainAction) { 65 | when (action) { 66 | is MainAction.UpdateExpression -> { 67 | viewModelScope.launch(environment.dispatcher) { 68 | environment.setExpression(action.expression) 69 | } 70 | } 71 | is MainAction.UpdateUseDegOrRad -> { 72 | viewModelScope.launch(environment.dispatcher) { 73 | if (action.useDeg) environment.useDeg() else environment.useRad() 74 | } 75 | } 76 | } 77 | } 78 | 79 | fun getExpression(exp: String, calc: Calc): String { 80 | return when (calc) { 81 | is CalcCNumber -> exp + calc.symbol 82 | is CalcCOperation.Sin -> exp + "${calc.symbol}(" 83 | is CalcCOperation.Cos -> exp + "${calc.symbol}(" 84 | is CalcCOperation.Tan -> exp + "${calc.symbol}(" 85 | is CalcCOperation.Log -> exp + "${calc.symbol}(" 86 | is CalcCOperation.ArcSin -> exp + "${calc.operatorSymbol}(" 87 | is CalcCOperation.ArcCos -> exp + "${calc.operatorSymbol}(" 88 | is CalcCOperation.ArcTan -> exp + "${calc.operatorSymbol}(" 89 | is CalcCOperation.NaturalLogarithm -> exp + "${calc.symbol}(" 90 | is CalcCOperation.Pow -> exp.ifNotBlank { exp + calc.symbol } 91 | is CalcCOperation.Factorial -> exp.ifNotBlank { exp + calc.symbol } 92 | is CalcCOperation.InvSqrt -> exp.ifNotBlank { exp + calc.operatorSymbol } 93 | is CalcCOperation.InvLog -> exp + calc.operatorSymbol 94 | is CalcCOperation.InvNaturalLogarithm -> exp + calc.operatorSymbol 95 | is CalcCOperation -> exp + calc.symbol 96 | is CalcCAction.Percent -> exp + calc.symbol 97 | is CalcCAction.Decimal -> exp + calc.symbol 98 | is CalcCAction.Clear -> "" 99 | is CalcCAction.Delete -> { 100 | if (exp.isNotBlank()) exp.substring(0, exp.length - 1) 101 | else "" 102 | } 103 | is CalcCAction.Calculate -> { 104 | viewModelScope.launch(environment.dispatcher) { 105 | environment.calculate() 106 | } 107 | 108 | return exp 109 | } 110 | is CalcCAdvancedButton.OpenParenthesis -> exp + calc.symbol 111 | is CalcCAdvancedButton.CloseParenthesis -> exp + calc.symbol 112 | is CalcCAdvancedButton.Inverse -> { 113 | viewModelScope.launch { 114 | environment.setInverse() 115 | } 116 | 117 | return exp 118 | } 119 | is CalcCAdvancedButton.Rad -> { 120 | viewModelScope.launch { 121 | environment.useRad() 122 | } 123 | 124 | return exp 125 | } 126 | is CalcCAdvancedButton.Deg -> { 127 | viewModelScope.launch { 128 | environment.useDeg() 129 | } 130 | 131 | return exp 132 | } 133 | else -> exp 134 | } 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/component/CalcCAdvancedButton.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main.component 2 | 3 | import android.util.Log 4 | import androidx.compose.foundation.BorderStroke 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.interaction.MutableInteractionSource 7 | import androidx.compose.foundation.isSystemInDarkTheme 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.material3.* 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.text.SpanStyle 16 | import androidx.compose.ui.text.buildAnnotatedString 17 | import androidx.compose.ui.text.font.FontWeight 18 | import androidx.compose.ui.text.style.BaselineShift 19 | import androidx.compose.ui.text.style.TextAlign 20 | import androidx.compose.ui.text.withStyle 21 | import androidx.compose.ui.unit.dp 22 | import androidx.compose.ui.unit.sp 23 | import com.anafthdev.calcc.data.Calc 24 | import com.anafthdev.calcc.data.CalcCAdvancedButton 25 | import com.anafthdev.calcc.foundation.extension.split 26 | import com.anafthdev.calcc.foundation.extension.toast 27 | import com.anafthdev.calcc.ui.theme.fully_rounded 28 | import com.anafthdev.calcc.ui.theme.superscriptSpanStyle 29 | 30 | @OptIn(ExperimentalMaterial3Api::class) 31 | @Composable 32 | fun CalcCAdvancedButton( 33 | isInverse: Boolean, 34 | useDeg: Boolean, 35 | onUpdateExpression: (Calc) -> Unit, 36 | onUseDegOrRad: (Boolean) -> Unit 37 | ) { 38 | 39 | com.anafthdev.calcc.data.CalcCButtons.advancedButtons.split(5).second.forEach { list -> 40 | 41 | Row( 42 | verticalAlignment = Alignment.CenterVertically, 43 | horizontalArrangement = Arrangement.Center, 44 | modifier = Modifier 45 | .fillMaxWidth() 46 | ) { 47 | list.forEach { pair -> 48 | val calc = if (isInverse) pair!!.second else pair!!.first 49 | val isDegOrRad = (calc is CalcCAdvancedButton.Rad) or (calc is CalcCAdvancedButton.Deg) 50 | val interactionSource = remember { MutableInteractionSource() } 51 | val updateExpression = { onUpdateExpression(calc) } 52 | 53 | AdvanceButton( 54 | calc = calc, 55 | useDeg = useDeg, 56 | isDegOrRad = isDegOrRad, 57 | interactionSource = interactionSource, 58 | onClick = { 59 | if (isDegOrRad) { 60 | onUseDegOrRad(calc is CalcCAdvancedButton.Deg) 61 | } else updateExpression() 62 | }, 63 | modifier = Modifier 64 | .weight(1f) 65 | ) 66 | } 67 | } 68 | } 69 | } 70 | 71 | @OptIn(ExperimentalMaterial3Api::class) 72 | @Composable 73 | private fun AdvanceButton( 74 | calc: Calc, 75 | useDeg: Boolean, 76 | isDegOrRad: Boolean, 77 | interactionSource: MutableInteractionSource, 78 | modifier: Modifier = Modifier, 79 | onClick: () -> Unit 80 | ) { 81 | 82 | // ex: "sin -1" => ["sin", "-1"] 83 | val calcSymbol = calc.symbol.split(" ") 84 | 85 | val isDeg = (calc is CalcCAdvancedButton.Deg) and useDeg 86 | val isRad = (calc is CalcCAdvancedButton.Rad) and !useDeg 87 | 88 | Box( 89 | contentAlignment = Alignment.Center, 90 | modifier = modifier 91 | .clickable( 92 | enabled = true, 93 | indication = null, 94 | interactionSource = interactionSource, 95 | onClick = onClick 96 | ) 97 | ) { 98 | Card( 99 | shape = fully_rounded, 100 | interactionSource = interactionSource, 101 | border = BorderStroke( 102 | width = 2.dp, 103 | color = if (isDeg or isRad) MaterialTheme.colorScheme.outline else Color.Transparent 104 | ), 105 | colors = CardDefaults.cardColors( 106 | containerColor = if (isDegOrRad) { 107 | MaterialTheme.colorScheme.onSurface 108 | } else CardDefaults.cardColors().containerColor(enabled = true).value 109 | ), 110 | onClick = onClick, 111 | modifier = Modifier 112 | .padding(4.dp) 113 | .aspectRatio(3f / 1.2f) 114 | ) { 115 | Box( 116 | contentAlignment = Alignment.Center, 117 | modifier = Modifier 118 | .fillMaxSize() 119 | ) { 120 | Text( 121 | text = buildAnnotatedString { 122 | append(calcSymbol[0]) 123 | if (calcSymbol.size > 1) { 124 | withStyle(superscriptSpanStyle) { 125 | append(calcSymbol[1]) 126 | } 127 | } 128 | }, 129 | textAlign = TextAlign.Center, 130 | style = MaterialTheme.typography.bodyMedium.copy( 131 | color = if (isDegOrRad) MaterialTheme.colorScheme.surfaceVariant 132 | else MaterialTheme.colorScheme.onSurfaceVariant, 133 | fontWeight = FontWeight.Medium 134 | ) 135 | ) 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/component/CalcCButtons.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main.component 2 | 3 | import androidx.compose.animation.core.animateDpAsState 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.interaction.MutableInteractionSource 6 | import androidx.compose.foundation.layout.* 7 | import androidx.compose.material3.* 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.text.font.FontWeight 16 | import androidx.compose.ui.unit.Dp 17 | import androidx.compose.ui.unit.dp 18 | import com.anafthdev.calcc.R 19 | import com.anafthdev.calcc.data.Calc 20 | import com.anafthdev.calcc.data.CalcCAction 21 | import com.anafthdev.calcc.foundation.extension.split 22 | import com.anafthdev.calcc.foundation.window.LocalComponentSize 23 | import com.anafthdev.calcc.ui.theme.fully_rounded 24 | 25 | @OptIn(ExperimentalMaterial3Api::class) 26 | @Composable 27 | fun CalcCButtons( 28 | isAdvancedButtonExpanded: Boolean, 29 | onUpdateExpression: (Calc) -> Unit 30 | ) { 31 | 32 | val componentSize = LocalComponentSize.current 33 | 34 | Column( 35 | modifier = Modifier.fillMaxWidth() 36 | ) { 37 | com.anafthdev.calcc.data.CalcCButtons.buttons.split(4).second.forEachIndexed { i, list -> 38 | 39 | val buttonPadding by animateDpAsState( 40 | targetValue = if (isAdvancedButtonExpanded) 6.dp 41 | else 8.dp 42 | ) 43 | 44 | val buttonSize by animateDpAsState( 45 | targetValue = if (isAdvancedButtonExpanded) componentSize.calcCButton.width 46 | .minus(componentSize.calcCButton.width.div(4)) 47 | else componentSize.calcCButton.width 48 | ) 49 | 50 | Row( 51 | verticalAlignment = Alignment.CenterVertically, 52 | horizontalArrangement = Arrangement.Center, 53 | modifier = Modifier 54 | .fillMaxWidth() 55 | ) { 56 | list.forEachIndexed { j, calc -> 57 | 58 | val interactionSource = remember { MutableInteractionSource() } 59 | val updateExpression = { onUpdateExpression(calc!!) } 60 | 61 | CalcCButton( 62 | calc = calc!!, 63 | interactionSource = interactionSource, 64 | buttonPadding = buttonPadding, 65 | buttonSize = buttonSize, 66 | onClick = updateExpression, 67 | colors = CardDefaults.cardColors( 68 | containerColor = if ((i == 0) or (list.lastIndex == j)) CardDefaults.cardColors().containerColor( 69 | enabled = true 70 | ).value else Color.Transparent 71 | ), 72 | modifier = Modifier 73 | .weight(1f) 74 | ) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | @OptIn(ExperimentalMaterial3Api::class) 82 | @Composable 83 | private fun CalcCButton( 84 | calc: Calc, 85 | colors: CardColors, 86 | interactionSource: MutableInteractionSource, 87 | buttonPadding: Dp, 88 | buttonSize: Dp, 89 | modifier: Modifier = Modifier, 90 | onClick: () -> Unit 91 | ) { 92 | Box( 93 | contentAlignment = Alignment.Center, 94 | modifier = modifier 95 | .clickable( 96 | enabled = true, 97 | indication = null, 98 | interactionSource = interactionSource, 99 | onClick = onClick 100 | ) 101 | ) { 102 | Card( 103 | shape = fully_rounded, 104 | interactionSource = interactionSource, 105 | colors = colors, 106 | onClick = onClick, 107 | modifier = Modifier 108 | .padding(buttonPadding) 109 | .size(buttonSize) 110 | ) { 111 | Box( 112 | contentAlignment = Alignment.Center, 113 | modifier = Modifier 114 | .fillMaxSize() 115 | ) { 116 | if (calc == CalcCAction.Delete()) { 117 | Icon( 118 | painter = painterResource(id = R.drawable.ic_delete), 119 | contentDescription = null 120 | ) 121 | } else { 122 | Text( 123 | text = calc.symbol, 124 | style = MaterialTheme.typography.titleLarge.copy( 125 | fontWeight = FontWeight.Bold 126 | ) 127 | ) 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/di/MainModule.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main.di 2 | 3 | import com.anafthdev.calcc.ui.main.environment.IMainEnvironment 4 | import com.anafthdev.calcc.ui.main.environment.MainEnvironment 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ViewModelComponent 9 | 10 | @Module 11 | @InstallIn(ViewModelComponent::class) 12 | abstract class MainModule { 13 | 14 | @Binds 15 | abstract fun provideEnvironment( 16 | environment: MainEnvironment 17 | ): IMainEnvironment 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/environment/IMainEnvironment.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main.environment 2 | 3 | import com.anafthdev.calcc.data.Calc 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface IMainEnvironment { 8 | 9 | val dispatcher: CoroutineDispatcher 10 | 11 | suspend fun calculate() 12 | 13 | suspend fun setExpression(exp: String) 14 | 15 | suspend fun useDeg() 16 | 17 | suspend fun useRad() 18 | 19 | suspend fun setInverse() 20 | 21 | fun getCalculationResult(): Flow 22 | 23 | fun getExpression(): Flow 24 | 25 | fun getInverse(): Flow 26 | 27 | fun getUseDeg(): Flow 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/main/environment/MainEnvironment.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.main.environment 2 | 3 | import android.util.Log 4 | import com.anafthdev.calcc.data.Calc 5 | import com.anafthdev.calcc.data.CalcCAction 6 | import com.anafthdev.calcc.data.Operations 7 | import com.anafthdev.calcc.data.Parser 8 | import com.anafthdev.calcc.foundation.di.DiName 9 | import com.anafthdev.calcc.foundation.extension.* 10 | import kotlinx.coroutines.CoroutineDispatcher 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.flow.Flow 13 | import kotlinx.coroutines.flow.MutableStateFlow 14 | import kotlinx.coroutines.flow.StateFlow 15 | import kotlinx.coroutines.launch 16 | import net.objecthunter.exp4j.ExpressionBuilder 17 | import net.objecthunter.exp4j.function.Function 18 | import net.objecthunter.exp4j.operator.Operator 19 | import timber.log.Timber 20 | import java.lang.Exception 21 | import java.lang.Math.toDegrees 22 | import java.lang.Math.toRadians 23 | import javax.inject.Inject 24 | import javax.inject.Named 25 | import kotlin.math.* 26 | 27 | class MainEnvironment @Inject constructor( 28 | @Named(DiName.DISPATCHER_MAIN) override val dispatcher: CoroutineDispatcher 29 | ): IMainEnvironment { 30 | 31 | private val _expression = MutableStateFlow("") 32 | private val expression: StateFlow = _expression 33 | 34 | private val _calculationResult = MutableStateFlow("") 35 | private val calculationResult: StateFlow = _calculationResult 36 | 37 | /** 38 | * if true, use deg else rad 39 | */ 40 | private val _useDeg = MutableStateFlow(true) 41 | private val useDeg: StateFlow = _useDeg 42 | 43 | private val _isInverse = MutableStateFlow(false) 44 | private val isInverse: StateFlow = _isInverse 45 | 46 | private val percentOperator = object : Operator( 47 | "!|", 48 | 1, 49 | true, 50 | PRECEDENCE_DIVISION 51 | ) { 52 | override fun apply(vararg args: Double): Double { 53 | return args[0] / 100 54 | } 55 | } 56 | 57 | private val factorialOperator = object : Operator( 58 | "!", 59 | 1, 60 | true, 61 | PRECEDENCE_MULTIPLICATION 62 | ) { 63 | override fun apply(vararg args: Double): Double { 64 | return if (args[0].isInt()) factorial(args[0].toInt()).toDouble() else Double.NaN 65 | } 66 | } 67 | 68 | private val powTwoOperator = object : Operator( 69 | "#", 70 | 1, 71 | true, 72 | PRECEDENCE_POWER 73 | ) { 74 | override fun apply(vararg args: Double): Double { 75 | return args[0].pow(2.0) 76 | } 77 | } 78 | 79 | private val naturalLogarithmFunction = object : Function("ln", 1) { 80 | override fun apply(vararg args: Double): Double { 81 | return 2.303 * log10(args[0]) 82 | } 83 | } 84 | 85 | private val invSinDegrees = object : Function("ASIN") { 86 | override fun apply(vararg args: Double): Double { 87 | return toDegrees(asin(args[0])) 88 | } 89 | } 90 | 91 | private val invCosDegrees = object : Function("ACOS") { 92 | override fun apply(vararg args: Double): Double { 93 | return toDegrees(acos(args[0])) 94 | } 95 | } 96 | 97 | private val invTanDegrees = object : Function("ATAN") { 98 | override fun apply(vararg args: Double): Double { 99 | return toDegrees(atan(args[0])) 100 | } 101 | } 102 | 103 | private val sinDegrees = object : Function("Sin") { 104 | override fun apply(vararg args: Double): Double { 105 | return sin(toRadians(args[0])) 106 | } 107 | } 108 | 109 | private val cosDegrees = object : Function("Cos") { 110 | override fun apply(vararg args: Double): Double { 111 | return cos(toRadians(args[0])) 112 | } 113 | } 114 | 115 | private val tanDegrees = object : Function("Tan") { 116 | override fun apply(vararg args: Double): Double { 117 | return tan(toRadians(args[0])) 118 | } 119 | } 120 | 121 | init { 122 | CoroutineScope(dispatcher).launch { 123 | useDeg.collect { 124 | calculate() 125 | } 126 | } 127 | } 128 | 129 | override suspend fun setExpression(exp: String) { 130 | _expression.emit(exp) 131 | } 132 | 133 | override suspend fun useDeg() { 134 | _useDeg.emit(true) 135 | } 136 | 137 | override suspend fun useRad() { 138 | _useDeg.emit(false) 139 | } 140 | 141 | override suspend fun setInverse() { 142 | _isInverse.emit(!isInverse.value) 143 | } 144 | 145 | override fun getCalculationResult(): Flow { 146 | return calculationResult 147 | } 148 | 149 | override fun getExpression(): Flow { 150 | return expression 151 | } 152 | 153 | override fun getInverse(): Flow { 154 | return isInverse 155 | } 156 | 157 | override fun getUseDeg(): Flow { 158 | return useDeg 159 | } 160 | 161 | override suspend fun calculate() { 162 | changeSymbol(useDeg.value) 163 | 164 | val parsedExpression = parseExpression(expression.value) 165 | _calculationResult.emit( 166 | try { 167 | ExpressionBuilder( 168 | if (parsedExpression.contains("(") and !parsedExpression.contains(")")) "$parsedExpression)" 169 | else parsedExpression 170 | ) 171 | .functions( 172 | naturalLogarithmFunction, 173 | invSinDegrees, 174 | invCosDegrees, 175 | invTanDegrees, 176 | sinDegrees, 177 | cosDegrees, 178 | tanDegrees 179 | ) 180 | .operator( 181 | percentOperator, 182 | factorialOperator, 183 | powTwoOperator 184 | ) 185 | .build() 186 | .evaluate() 187 | .toString() 188 | .replace("NaN", "") 189 | } catch (e: Exception) { 190 | Timber.i("calculation error: ${e.message}") 191 | Timber.i("parsedExpression: $parsedExpression") 192 | "" 193 | } 194 | ) 195 | } 196 | 197 | private fun parseExpression(exp: String): String { 198 | var result = exp 199 | val sinCosTanDegrees = listOf( 200 | Operations.SIN_DEGREES.first, 201 | Operations.COS_DEGREES.first, 202 | Operations.TAN_DEGREES.first, 203 | ) 204 | 205 | val invSinCosTanDegrees = listOf( 206 | Operations.INV_SIN_DEGREES.first, 207 | Operations.INV_COS_DEGREES.first, 208 | Operations.INV_TAN_DEGREES.first, 209 | ) 210 | 211 | Operations.values.forEach { pair -> 212 | if (pair.first.equalsOr(sinCosTanDegrees)) { 213 | if (useDeg.value) { 214 | result = result.replace(pair.first, pair.second) 215 | } 216 | } else if (pair.first.equalsOr(invSinCosTanDegrees)) { 217 | if (useDeg.value) { 218 | Timber.i("calculate: $pair") 219 | result = result.replace(pair.first, pair.second) 220 | } else { 221 | val inv = when (pair.first) { 222 | Operations.INV_SIN_DEGREES.first -> Operations.INV_SIN 223 | Operations.INV_COS_DEGREES.first -> Operations.INV_COS 224 | Operations.INV_TAN_DEGREES.first -> Operations.INV_TAN 225 | else -> Operations.INV_SIN 226 | } 227 | 228 | result = result.replace(inv.first, inv.second) 229 | } 230 | } else { 231 | result = result.replace(pair.first, pair.second) 232 | } 233 | 234 | Timber.i("calculate res: $result") 235 | } 236 | 237 | return Parser.parseSqrt(result) 238 | } 239 | 240 | private fun changeSymbol(useDeg: Boolean) { 241 | var result = expression.value 242 | val symbolsRad = listOf( 243 | Operations.INV_SIN, 244 | Operations.INV_COS, 245 | Operations.INV_TAN 246 | ) 247 | 248 | val symbolsDeg = listOf( 249 | Operations.INV_SIN_DEGREES, 250 | Operations.INV_COS_DEGREES, 251 | Operations.INV_TAN_DEGREES 252 | ) 253 | 254 | symbolsRad.forEachIndexed { i, pair -> 255 | result = if (useDeg) { 256 | result.replace(pair.first, symbolsDeg[i].first) 257 | } else { 258 | result.replace(symbolsDeg[i].first, pair.first) 259 | } 260 | } 261 | 262 | _expression.tryEmit(result) 263 | } 264 | 265 | private fun factorial(n: Int): Long { 266 | if (n == 0) return 1 267 | 268 | return factorial(n - 1) * n 269 | } 270 | 271 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val md_theme_light_primary = Color(0xFF725c00) 6 | val md_theme_light_onPrimary = Color(0xFFffffff) 7 | val md_theme_light_primaryContainer = Color(0xFFffe16e) 8 | val md_theme_light_onPrimaryContainer = Color(0xFF231b00) 9 | val md_theme_light_secondary = Color(0xFF685e40) 10 | val md_theme_light_onSecondary = Color(0xFFffffff) 11 | val md_theme_light_secondaryContainer = Color(0xFFf0e2bc) 12 | val md_theme_light_onSecondaryContainer = Color(0xFF211b04) 13 | val md_theme_light_tertiary = Color(0xFF44664c) 14 | val md_theme_light_onTertiary = Color(0xFFffffff) 15 | val md_theme_light_tertiaryContainer = Color(0xFFc7eccc) 16 | val md_theme_light_onTertiaryContainer = Color(0xFF01210e) 17 | val md_theme_light_error = Color(0xFFba1b1b) 18 | val md_theme_light_errorContainer = Color(0xFFffdad4) 19 | val md_theme_light_onError = Color(0xFFffffff) 20 | val md_theme_light_onErrorContainer = Color(0xFF410001) 21 | val md_theme_light_background = Color(0xFFfffbf7) 22 | val md_theme_light_onBackground = Color(0xFF1d1b16) 23 | val md_theme_light_surface = Color(0xFFfffbf7) 24 | val md_theme_light_onSurface = Color(0xFF1d1b16) 25 | val md_theme_light_surfaceVariant = Color(0xFFebe2d0) 26 | val md_theme_light_onSurfaceVariant = Color(0xFF4b4639) 27 | val md_theme_light_outline = Color(0xFF7d7767) 28 | val md_theme_light_inverseOnSurface = Color(0xFFf6f0e7) 29 | val md_theme_light_inverseSurface = Color(0xFF33302a) 30 | val md_theme_light_inversePrimary = Color(0xFFe5c44a) 31 | val md_theme_light_shadow = Color(0xFF000000) 32 | 33 | val md_theme_dark_primary = Color(0xFFe5c44a) 34 | val md_theme_dark_onPrimary = Color(0xFF3c2f00) 35 | val md_theme_dark_primaryContainer = Color(0xFF564500) 36 | val md_theme_dark_onPrimaryContainer = Color(0xFFffe16e) 37 | val md_theme_dark_secondary = Color(0xFFd3c6a1) 38 | val md_theme_dark_onSecondary = Color(0xFF383016) 39 | val md_theme_dark_secondaryContainer = Color(0xFF4f462a) 40 | val md_theme_dark_onSecondaryContainer = Color(0xFFf0e2bc) 41 | val md_theme_dark_tertiary = Color(0xFFabd0b1) 42 | val md_theme_dark_onTertiary = Color(0xFF163721) 43 | val md_theme_dark_tertiaryContainer = Color(0xFF2d4e36) 44 | val md_theme_dark_onTertiaryContainer = Color(0xFFc7eccc) 45 | val md_theme_dark_error = Color(0xFFffb4a9) 46 | val md_theme_dark_errorContainer = Color(0xFF930006) 47 | val md_theme_dark_onError = Color(0xFF680003) 48 | val md_theme_dark_onErrorContainer = Color(0xFFffdad4) 49 | val md_theme_dark_background = Color(0xFF1d1b16) 50 | val md_theme_dark_onBackground = Color(0xFFe8e2d9) 51 | val md_theme_dark_surface = Color(0xFF1d1b16) 52 | val md_theme_dark_onSurface = Color(0xFFe8e2d9) 53 | val md_theme_dark_surfaceVariant = Color(0xFF4b4639) 54 | val md_theme_dark_onSurfaceVariant = Color(0xFFcec6b4) 55 | val md_theme_dark_outline = Color(0xFF979080) 56 | val md_theme_dark_inverseOnSurface = Color(0xFF1d1b16) 57 | val md_theme_dark_inverseSurface = Color(0xFFe8e2d9) 58 | val md_theme_dark_inversePrimary = Color(0xFF725c00) 59 | val md_theme_dark_shadow = Color(0xFF000000) 60 | 61 | 62 | 63 | val seed = Color(0xFFe8c959) 64 | val error = Color(0xFFba1b1b) -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.ui.unit.dp 5 | import androidx.compose.material3.Shapes 6 | 7 | val fully_rounded = RoundedCornerShape(100) 8 | 9 | val Shapes = Shapes( 10 | small = RoundedCornerShape(4.dp), 11 | medium = RoundedCornerShape(4.dp), 12 | large = RoundedCornerShape(0.dp) 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.lightColorScheme 6 | import androidx.compose.material3.darkColorScheme 7 | import androidx.compose.runtime.Composable 8 | 9 | private val LightThemeColors = lightColorScheme( 10 | 11 | primary = md_theme_light_primary, 12 | onPrimary = md_theme_light_onPrimary, 13 | primaryContainer = md_theme_light_primaryContainer, 14 | onPrimaryContainer = md_theme_light_onPrimaryContainer, 15 | secondary = md_theme_light_secondary, 16 | onSecondary = md_theme_light_onSecondary, 17 | secondaryContainer = md_theme_light_secondaryContainer, 18 | onSecondaryContainer = md_theme_light_onSecondaryContainer, 19 | tertiary = md_theme_light_tertiary, 20 | onTertiary = md_theme_light_onTertiary, 21 | tertiaryContainer = md_theme_light_tertiaryContainer, 22 | onTertiaryContainer = md_theme_light_onTertiaryContainer, 23 | error = md_theme_light_error, 24 | errorContainer = md_theme_light_errorContainer, 25 | onError = md_theme_light_onError, 26 | onErrorContainer = md_theme_light_onErrorContainer, 27 | background = md_theme_light_background, 28 | onBackground = md_theme_light_onBackground, 29 | surface = md_theme_light_surface, 30 | onSurface = md_theme_light_onSurface, 31 | surfaceVariant = md_theme_light_surfaceVariant, 32 | onSurfaceVariant = md_theme_light_onSurfaceVariant, 33 | outline = md_theme_light_outline, 34 | inverseOnSurface = md_theme_light_inverseOnSurface, 35 | inverseSurface = md_theme_light_inverseSurface, 36 | inversePrimary = md_theme_light_inversePrimary, 37 | ) 38 | private val DarkThemeColors = darkColorScheme( 39 | 40 | primary = md_theme_dark_primary, 41 | onPrimary = md_theme_dark_onPrimary, 42 | primaryContainer = md_theme_dark_primaryContainer, 43 | onPrimaryContainer = md_theme_dark_onPrimaryContainer, 44 | secondary = md_theme_dark_secondary, 45 | onSecondary = md_theme_dark_onSecondary, 46 | secondaryContainer = md_theme_dark_secondaryContainer, 47 | onSecondaryContainer = md_theme_dark_onSecondaryContainer, 48 | tertiary = md_theme_dark_tertiary, 49 | onTertiary = md_theme_dark_onTertiary, 50 | tertiaryContainer = md_theme_dark_tertiaryContainer, 51 | onTertiaryContainer = md_theme_dark_onTertiaryContainer, 52 | error = md_theme_dark_error, 53 | errorContainer = md_theme_dark_errorContainer, 54 | onError = md_theme_dark_onError, 55 | onErrorContainer = md_theme_dark_onErrorContainer, 56 | background = md_theme_dark_background, 57 | onBackground = md_theme_dark_onBackground, 58 | surface = md_theme_dark_surface, 59 | onSurface = md_theme_dark_onSurface, 60 | surfaceVariant = md_theme_dark_surfaceVariant, 61 | onSurfaceVariant = md_theme_dark_onSurfaceVariant, 62 | outline = md_theme_dark_outline, 63 | inverseOnSurface = md_theme_dark_inverseOnSurface, 64 | inverseSurface = md_theme_dark_inverseSurface, 65 | inversePrimary = md_theme_dark_inversePrimary, 66 | ) 67 | @Composable 68 | fun CalcCTheme( 69 | useDarkTheme: Boolean = isSystemInDarkTheme(), 70 | content: @Composable () -> Unit 71 | ) { 72 | val colors = if (!useDarkTheme) LightThemeColors else DarkThemeColors 73 | 74 | MaterialTheme( 75 | colorScheme = colors, 76 | typography = Typography, 77 | content = content 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/anafthdev/calcc/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc.ui.theme 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.material3.Typography 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.text.SpanStyle 7 | import androidx.compose.ui.text.TextStyle 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.text.font.FontWeight 10 | import androidx.compose.ui.text.style.BaselineShift 11 | import androidx.compose.ui.unit.sp 12 | 13 | val Roboto = FontFamily.Default 14 | 15 | val Typography = Typography( 16 | displayLarge = TextStyle( 17 | fontFamily = Roboto, 18 | fontWeight = FontWeight.W400, 19 | fontSize = 57.sp, 20 | lineHeight = 64.sp, 21 | letterSpacing = (-0.25).sp, 22 | ), 23 | displayMedium = TextStyle( 24 | fontFamily = Roboto, 25 | fontWeight = FontWeight.W400, 26 | fontSize = 45.sp, 27 | lineHeight = 52.sp, 28 | letterSpacing = 0.sp, 29 | ), 30 | displaySmall = TextStyle( 31 | fontFamily = Roboto, 32 | fontWeight = FontWeight.W400, 33 | fontSize = 36.sp, 34 | lineHeight = 44.sp, 35 | letterSpacing = 0.sp, 36 | ), 37 | headlineLarge = TextStyle( 38 | fontFamily = Roboto, 39 | fontWeight = FontWeight.W400, 40 | fontSize = 32.sp, 41 | lineHeight = 40.sp, 42 | letterSpacing = 0.sp, 43 | ), 44 | headlineMedium = TextStyle( 45 | fontFamily = Roboto, 46 | fontWeight = FontWeight.W400, 47 | fontSize = 28.sp, 48 | lineHeight = 36.sp, 49 | letterSpacing = 0.sp, 50 | ), 51 | headlineSmall = TextStyle( 52 | fontFamily = Roboto, 53 | fontWeight = FontWeight.W400, 54 | fontSize = 24.sp, 55 | lineHeight = 32.sp, 56 | letterSpacing = 0.sp, 57 | ), 58 | titleLarge = TextStyle( 59 | fontFamily = Roboto, 60 | fontWeight = FontWeight.W400, 61 | fontSize = 22.sp, 62 | lineHeight = 28.sp, 63 | letterSpacing = 0.sp, 64 | ), 65 | titleMedium = TextStyle( 66 | fontFamily = Roboto, 67 | fontWeight = FontWeight.Medium, 68 | fontSize = 16.sp, 69 | lineHeight = 24.sp, 70 | letterSpacing = 0.1.sp, 71 | ), 72 | titleSmall = TextStyle( 73 | fontFamily = Roboto, 74 | fontWeight = FontWeight.Medium, 75 | fontSize = 14.sp, 76 | lineHeight = 20.sp, 77 | letterSpacing = 0.1.sp, 78 | ), 79 | labelLarge = TextStyle( 80 | fontFamily = Roboto, 81 | fontWeight = FontWeight.Medium, 82 | fontSize = 14.sp, 83 | lineHeight = 20.sp, 84 | letterSpacing = 0.1.sp, 85 | ), 86 | bodyLarge = TextStyle( 87 | fontFamily = Roboto, 88 | fontWeight = FontWeight.W400, 89 | fontSize = 16.sp, 90 | lineHeight = 24.sp, 91 | letterSpacing = 0.5.sp, 92 | ), 93 | bodyMedium = TextStyle( 94 | fontFamily = Roboto, 95 | fontWeight = FontWeight.W400, 96 | fontSize = 14.sp, 97 | lineHeight = 20.sp, 98 | letterSpacing = 0.25.sp, 99 | ), 100 | bodySmall = TextStyle( 101 | fontFamily = Roboto, 102 | fontWeight = FontWeight.W400, 103 | fontSize = 12.sp, 104 | lineHeight = 16.sp, 105 | letterSpacing = 0.4.sp, 106 | ), 107 | labelMedium = TextStyle( 108 | fontFamily = Roboto, 109 | fontWeight = FontWeight.Medium, 110 | fontSize = 12.sp, 111 | lineHeight = 16.sp, 112 | letterSpacing = 0.5.sp, 113 | ), 114 | labelSmall = TextStyle( 115 | fontFamily = Roboto, 116 | fontWeight = FontWeight.Medium, 117 | fontSize = 11.sp, 118 | lineHeight = 16.sp, 119 | letterSpacing = 0.5.sp, 120 | ), 121 | ) 122 | 123 | val superscriptSpanStyle: SpanStyle 124 | @Composable 125 | get() = SpanStyle( 126 | baselineShift = BaselineShift.Superscript, 127 | fontSize = MaterialTheme.typography.labelSmall.fontSize, 128 | color = MaterialTheme.typography.bodyMedium.color 129 | ) 130 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_app_icon.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 12 | 13 | 16 | 19 | 20 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00000000 4 | 5 | #ebdbfb 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CalcC 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/com/anafthdev/calcc/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.anafthdev.calcc 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | compose_version = '1.1.1' 4 | kotlin_version = '1.6.10' 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:7.1.2' 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | classpath 'com.google.dagger:hilt-android-gradle-plugin:2.39.1' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | }// Top-level build file where you can add configuration options common to all sub-projects/modules. 16 | plugins { 17 | id 'com.android.application' version '7.1.0' apply false 18 | id 'com.android.library' version '7.1.0' apply false 19 | id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 26 16:36:17 ICT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /img/img_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/img/img_1.jpg -------------------------------------------------------------------------------- /img/img_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/img/img_2.jpg -------------------------------------------------------------------------------- /img/img_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/img/img_3.jpg -------------------------------------------------------------------------------- /img/img_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafri8889/Calculator-Compose/421838dc5f08eadabb9804cd157439d331d9f4b6/img/img_4.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "CalcC" 16 | include ':app' 17 | --------------------------------------------------------------------------------