diff --git a/app/.gitignore b/androidApp/.gitignore similarity index 100% rename from app/.gitignore rename to androidApp/.gitignore diff --git a/androidApp/build.gradle b/androidApp/build.gradle new file mode 100644 index 0000000..13a8348 --- /dev/null +++ b/androidApp/build.gradle @@ -0,0 +1,72 @@ +plugins{ + alias(libs.plugins.androidApplication) + alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.compose.compiler) +} + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + signingConfigs { + release { + storeFile file("../default_key_store.jks") + storePassword "mifos1234" + keyAlias "mifos-passcode" + keyPassword "mifos1234" + } + } + compileSdk 35 + defaultConfig { + namespace "com.mifos.passcode" + minSdk 24 + targetSdk 35 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.2" + } + kotlinOptions { + jvmTarget = '17' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation(project(":compose")) + implementation project(':shared') + implementation(libs.compose.ui) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation(libs.androidx.activity.compose) + implementation libs.androidx.appcompat + androidTestImplementation libs.testng + androidTestImplementation libs.androidx.monitor + androidTestImplementation libs.runner + androidTestImplementation libs.junit + androidTestImplementation libs.runner + androidTestImplementation libs.androidx.monitor + debugImplementation(libs.compose.ui.tooling) + implementation (libs.androidx.navigation.compose) + implementation (libs.androidx.biometric) +} +repositories { + mavenCentral() +} + +configurations.implementation { + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' +} diff --git a/app/proguard-rules.pro b/androidApp/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to androidApp/proguard-rules.pro diff --git a/app/src/androidTest/java/com/mifos/passcode/ExampleInstrumentedTest.java b/androidApp/src/androidTest/java/com/mifos/passcode/ExampleInstrumentedTest.java similarity index 99% rename from app/src/androidTest/java/com/mifos/passcode/ExampleInstrumentedTest.java rename to androidApp/src/androidTest/java/com/mifos/passcode/ExampleInstrumentedTest.java index 32a18a1..52aef03 100644 --- a/app/src/androidTest/java/com/mifos/passcode/ExampleInstrumentedTest.java +++ b/androidApp/src/androidTest/java/com/mifos/passcode/ExampleInstrumentedTest.java @@ -1,12 +1,9 @@ package com.mifos.passcode; import static org.junit.Assert.assertEquals; - import android.content.Context; - import org.junit.Test; import org.junit.runner.RunWith; - import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -24,4 +21,4 @@ public void useAppContext() { assertEquals("com.mifos.passcode", appContext.getPackageName()); } -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml similarity index 88% rename from app/src/main/AndroidManifest.xml rename to androidApp/src/main/AndroidManifest.xml index 46b319e..58ef7ed 100644 --- a/app/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -7,9 +7,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:name=".MifosApplication" android:theme="@style/AppTheme" > - diff --git a/app/src/main/java/com/mifos/passcode/LoginActivity.kt b/androidApp/src/main/java/com/mifos/passcode/LoginActivity.kt similarity index 100% rename from app/src/main/java/com/mifos/passcode/LoginActivity.kt rename to androidApp/src/main/java/com/mifos/passcode/LoginActivity.kt diff --git a/androidApp/src/main/java/com/mifos/passcode/MainActivity.kt b/androidApp/src/main/java/com/mifos/passcode/MainActivity.kt new file mode 100644 index 0000000..19e3c55 --- /dev/null +++ b/androidApp/src/main/java/com/mifos/passcode/MainActivity.kt @@ -0,0 +1,81 @@ +package com.mifos.passcode + +import android.content.Intent +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.fragment.app.FragmentActivity +import com.mifos.shared.BiometricUtilAndroidImpl +import com.mifos.shared.CipherUtilAndroidImpl +import com.mifos.shared.utility.PreferenceManager +import com.mifos.shared.PasscodeRepository +import com.mifos.shared.PasscodeRepositoryImpl +import com.mifos.shared.viewmodels.BiometricAuthorizationViewModel +import androidx.lifecycle.viewmodel.compose.viewModel +import com.mifos.shared.component.PasscodeScreen + + +class MainActivity : FragmentActivity() { + + private val bioMetricUtil by lazy { + BiometricUtilAndroidImpl(this, CipherUtilAndroidImpl()) + } + private lateinit var passcodeRepository: PasscodeRepository + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + bioMetricUtil.preparePrompt( + title= getString(R.string.biometric_auth_title), + subtitle = "", + description = getString(R.string.biometric_auth_description) + ) + passcodeRepository = PasscodeRepositoryImpl(PreferenceManager()) + + setContent { + PasscodeScreen( + onForgotButton = { onPasscodeForgot() }, + onSkipButton = { onPasscodeSkip() }, + onPasscodeConfirm = { onPassCodeReceive(it) }, + onPasscodeRejected = { onPasscodeReject() }, + enableBiometric = true, + bioMetricUtil = bioMetricUtil, + onBiometricAuthSuccess = { launchNextActivity() }, + ) + } + } + + private fun onPassCodeReceive(passcode: String) { + if (passcodeRepository.getSavedPasscode() == passcode) { + launchNextActivity() + } + } + + private fun onPasscodeReject() {} + + private fun onPasscodeForgot() { + // Add logic to redirect user to login page + } + + private fun onPasscodeSkip() { + finish() + } + + private fun launchNextActivity() { + startActivity(Intent(this, LoginActivity::class.java)) + finish() + } +} +@Composable +fun GreetingView(text: String) { + Text(text = text) +} + +//@Preview +//@Composable +//fun DefaultPreview() { +// MyApplicationTheme { +// GreetingView("Hello, Android!") +// } +//} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/androidApp/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_background.xml rename to androidApp/src/main/res/drawable/ic_launcher_background.xml diff --git a/app/src/main/res/drawable/mifos_logo.jpg b/androidApp/src/main/res/drawable/mifos_logo.jpg similarity index 100% rename from app/src/main/res/drawable/mifos_logo.jpg rename to androidApp/src/main/res/drawable/mifos_logo.jpg diff --git a/app/src/main/res/layout/activity_login.xml b/androidApp/src/main/res/layout/activity_login.xml similarity index 100% rename from app/src/main/res/layout/activity_login.xml rename to androidApp/src/main/res/layout/activity_login.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/values/colors.xml b/androidApp/src/main/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to androidApp/src/main/res/values/colors.xml diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml new file mode 100644 index 0000000..bf3e104 --- /dev/null +++ b/androidApp/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + mobile_passcode + Login + Unlock Mifos + Confirm your screen lock pattern, PIN, password, or fingerprint to unlock + diff --git a/app/src/main/res/values/styles.xml b/androidApp/src/main/res/values/styles.xml similarity index 100% rename from app/src/main/res/values/styles.xml rename to androidApp/src/main/res/values/styles.xml diff --git a/app/src/test/java/com/mifos/passcode/ExampleUnitTest.java b/androidApp/src/test/java/com/mifos/passcode/ExampleUnitTest.java similarity index 100% rename from app/src/test/java/com/mifos/passcode/ExampleUnitTest.java rename to androidApp/src/test/java/com/mifos/passcode/ExampleUnitTest.java diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 439e5e4..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,81 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'com.google.devtools.ksp' -apply plugin: 'com.google.dagger.hilt.android' - -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - signingConfigs { - release { - storeFile file("../default_key_store.jks") - storePassword "mifos1234" - keyAlias "mifos-passcode" - keyPassword "mifos1234" - } - } - compileSdk 34 - defaultConfig { - namespace "com.mifos.passcode" - minSdk 21 - targetSdk 34 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - signingConfig signingConfigs.release - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.2" - } - kotlinOptions { - jvmTarget = '17' - } -} - -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.6.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.6.0-alpha04' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.0-alpha01' - implementation project(':mifos-passcode') - //implementation 'com.mifos.mobile:mifos-passcode:1.0.0' - implementation "androidx.core:core-ktx:+" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation(project(":compose")) - - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - implementation(platform("androidx.compose:compose-bom:2023.03.00")) - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-graphics") - implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3") - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' - - - // Hilt Implementation - implementation 'com.google.dagger:hilt-android:2.48.1' - ksp("com.google.dagger:hilt-compiler:2.48.1") -} -repositories { - mavenCentral() -} - -configurations.implementation { - exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' -} diff --git a/app/src/main/java/com/mifos/passcode/BaseActivity.kt b/app/src/main/java/com/mifos/passcode/BaseActivity.kt deleted file mode 100644 index 7160e67..0000000 --- a/app/src/main/java/com/mifos/passcode/BaseActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.mifos.passcode - -import com.mifos.mobile.passcode.BasePassCodeActivity - -/** - * Created by dilpreet on 19/01/18. - */ -class BaseActivity : BasePassCodeActivity() { - override val passCodeClass: Class<*> - get() =//name of the activity which extends MifosPassCodeActivity - PassCodeActivity::class.java -} \ No newline at end of file diff --git a/app/src/main/java/com/mifos/passcode/MifosApplication.kt b/app/src/main/java/com/mifos/passcode/MifosApplication.kt deleted file mode 100644 index 8b33a7c..0000000 --- a/app/src/main/java/com/mifos/passcode/MifosApplication.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.mifos.passcode - -import android.app.Application -import com.mifos.mobile.passcode.utils.ForegroundChecker.Companion.init -import dagger.hilt.android.HiltAndroidApp - -/** - * Created by dilpreet on 19/01/18. - */ - -@HiltAndroidApp -class MifosApplication : Application() { - override fun onCreate() { - super.onCreate() - //need to initialize this - init(this) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/mifos/passcode/PassCodeActivity.kt b/app/src/main/java/com/mifos/passcode/PassCodeActivity.kt deleted file mode 100644 index 21251d1..0000000 --- a/app/src/main/java/com/mifos/passcode/PassCodeActivity.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.mifos.passcode - -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import androidx.activity.compose.setContent -import androidx.appcompat.app.AppCompatActivity -import com.mifos.compose.PasscodeRepository -import com.mifos.compose.component.PasscodeScreen -import com.mifos.compose.theme.MifosPasscodeTheme -import com.mifos.compose.utility.PreferenceManager -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -/** - * Created by dilpreet on 19/01/18. - */ -@AndroidEntryPoint -class PassCodeActivity : AppCompatActivity() { - - @Inject - lateinit var passcodeRepository: PasscodeRepository - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MifosPasscodeTheme { - PasscodeScreen( - onForgotButton = { onPasscodeForgot() }, - onSkipButton = { onPasscodeSkip() }, - onPasscodeConfirm = { onPassCodeReceive(it) }, - onPasscodeRejected = { onPasscodeReject() } - ) - } - } - } - - private fun onPassCodeReceive(passcode: String) { - if (passcodeRepository.getSavedPasscode() == passcode) { - startActivity(Intent(this, LoginActivity::class.java)) - Toast.makeText(this, "New Screen", Toast.LENGTH_SHORT).show() - finish() - } - } - - private fun onPasscodeReject() {} - - private fun onPasscodeForgot() { - // Add logic to redirect user to login page - Toast.makeText(this, "Forgot Passcode", Toast.LENGTH_SHORT).show() - } - - private fun onPasscodeSkip() { - Toast.makeText(this, "Skip Button", Toast.LENGTH_SHORT).show() - finish() - } -} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 69c4f03..0000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - mobile_passcode - Login - diff --git a/build.gradle b/build.gradle index 0e6bf33..d6a643f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { ext.kotlin_version = '1.7.20' @@ -10,18 +8,27 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.3.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' + classpath libs.gradle +// classpath libs.gradle.bintray.plugin + classpath libs.android.maven.gradle.plugin classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } + plugins { - id 'com.google.dagger.hilt.android' version '2.48.1' apply false - id "com.google.devtools.ksp" version "1.9.0-1.0.12" + //trick: for the same plugin versions in all sub-modules + alias(libs.plugins.androidApplication).apply(false) + alias(libs.plugins.androidLibrary).apply(false) + alias(libs.plugins.kotlinAndroid).apply(false) + alias(libs.plugins.kotlinMultiplatform).apply(false) + alias(libs.plugins.kotlinCocoapods).apply(false) + alias(libs.plugins.compose.compiler).apply(false) + alias(libs.plugins.dagger.hilt).apply(false) + alias(libs.plugins.devToolsKsp).apply(false) } + allprojects { repositories { google() @@ -33,4 +40,4 @@ allprojects { tasks.register('clean', Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/compose/build.gradle b/compose/build.gradle index d6530bf..541323d 100644 --- a/compose/build.gradle +++ b/compose/build.gradle @@ -1,16 +1,17 @@ plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'com.google.devtools.ksp' - id 'com.google.dagger.hilt.android' + alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.dagger.hilt) + alias(libs.plugins.devToolsKsp) + alias(libs.plugins.compose.compiler) } android { namespace 'com.mifos.compose' - compileSdk 34 + compileSdk 35 defaultConfig { - minSdk 21 + minSdk 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -35,33 +36,37 @@ android { composeOptions { kotlinCompilerExtensionVersion = "1.5.2" } + packagingOptions { + exclude 'META-INF/com.google.dagger_dagger.version' + } + } dependencies { - implementation 'androidx.core:core-ktx:1.12.0' - implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.6.0' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - implementation(platform("androidx.compose:compose-bom:2023.03.00")) - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-graphics") - implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3") - implementation "androidx.compose.material:material-icons-extended" - implementation "androidx.lifecycle:lifecycle-runtime-compose" - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' + implementation libs.androidx.core.ktx.v1131 + implementation platform(libs.kotlin.bom) + implementation libs.androidx.appcompat + implementation libs.material + implementation project(':shared') + testImplementation libs.junit + androidTestImplementation libs.ext.junit + androidTestImplementation libs.androidx.espresso.core + implementation(libs.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation libs.androidx.material.icons.extended + implementation libs.androidx.lifecycle.runtime.compose + debugImplementation libs.compose.ui.tooling + debugImplementation libs.androidx.ui.test.manifest // Hilt Implementation - implementation 'com.google.dagger:hilt-android:2.48.1' - ksp("com.google.dagger:hilt-compiler:2.48.1") - implementation("androidx.hilt:hilt-navigation-compose:1.0.0") + implementation libs.hilt.android + ksp(libs.hilt.compiler) + implementation(libs.androidx.hilt.navigation.compose.v120) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..65d4004 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,80 @@ +[versions] +agp = "8.3.0" +androidMavenGradlePlugin = "2.1" +coreKtxVersion = "1.13.1" +espressoCoreVersion = "3.6.0-alpha01" +gradle = "8.3.1" +hiltCompiler = "2.48.1" +hiltNavigationComposeVersion = "1.2.0" +kotlin = "2.0.0" +compose = "1.5.4" +compose-material3 = "1.1.2" +androidx-activityCompose = "1.8.0" +kotlinStdlibJdk8 = "1.7.20" +lifecycleViewmodelKtx = "2.8.1" +compose-plugin = "1.6.10" +multiplatformSettings = "1.0.0" +navigationCompose = "2.7.7" +navigationComposeVersion = "2.7.0-alpha07" +dagger-hilt = "2.48.1" +biometricKtx = "1.1.0" +devToolsKsp = "2.0.10-1.0.24" +material = "1.6.0" +junit = "4.13.2" +extJunit = "1.1.5" +lifecycleRuntimeKtx = "2.7.0" +composeBom = "2023.03.00" +hiltAndroid = "2.48.1" +uiToolingPreviewAndroid = "1.6.8" +uiAndroid = "1.6.8" +appcompatVersion = "1.7.0" +testng = "6.9.6" +monitor = "1.6.1" +runnerVersion = "1.5.2" + +[libraries] +android-maven-gradle-plugin = { module = "com.github.dcendents:android-maven-gradle-plugin", version.ref = "androidMavenGradlePlugin" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } +androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometricKtx" } +androidx-core-ktx-v1131 = { module = "androidx.core:core-ktx", version.ref = "coreKtxVersion" } +androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCoreVersion" } +androidx-hilt-navigation-compose-v120 = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationComposeVersion" } +androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose" } +androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } +androidx-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } +androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } +compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } +compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } +gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } +hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltCompiler" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "lifecycleViewmodelKtx" } +multiplatform-settings-no-arg = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatformSettings" } +kotlin-bom = { group = "org.jetbrains.kotlin", name = "kotlin-bom", version.ref = "kotlin" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompatVersion" } +navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationComposeVersion" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "extJunit" } +lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltAndroid" } +androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" } +androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android", version.ref = "uiAndroid" } +testng = { group = "org.testng", name = "testng", version.ref = "testng" } +androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "monitor" } +runner = { group = "androidx.test", name = "runner", version.ref = "runnerVersion" } + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } +kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +devToolsKsp = { id = "com.google.devtools.ksp", version.ref = "devToolsKsp" } +kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "dagger-hilt" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 65bc83b..c2239c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,5 @@ +#Wed Aug 14 15:44:40 IST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStorePath=wrapper/dists diff --git a/iosApp/Podfile b/iosApp/Podfile new file mode 100644 index 0000000..3fd8aca --- /dev/null +++ b/iosApp/Podfile @@ -0,0 +1,5 @@ +target 'iosApp' do + use_frameworks! + platform :ios, '16.0' + pod 'shared', :path => '../shared' +end \ No newline at end of file diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock new file mode 100644 index 0000000..d2bfea5 --- /dev/null +++ b/iosApp/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - shared (1.0) + +DEPENDENCIES: + - shared (from `../shared`) + +EXTERNAL SOURCES: + shared: + :path: "../shared" + +SPEC CHECKSUMS: + shared: 476f8f613e223c690e3b4311f65291534e4fa034 + +PODFILE CHECKSUM: 78d6cc1b71b232202c48a29d4e6c34972c534c73 + +COCOAPODS: 1.15.2 diff --git a/iosApp/Pods/Local Podspecs/shared.podspec.json b/iosApp/Pods/Local Podspecs/shared.podspec.json new file mode 100644 index 0000000..7620349 --- /dev/null +++ b/iosApp/Pods/Local Podspecs/shared.podspec.json @@ -0,0 +1,34 @@ +{ + "name": "shared", + "version": "1.0", + "homepage": "Link to the Shared Module homepage", + "source": { + "http": "" + }, + "authors": "", + "license": "", + "summary": "Some description for the Shared Module", + "vendored_frameworks": "build/cocoapods/framework/shared.framework", + "libraries": "c++", + "platforms": { + "ios": "16.0" + }, + "xcconfig": { + "ENABLE_USER_SCRIPT_SANDBOXING": "NO" + }, + "pod_target_xcconfig": { + "KOTLIN_PROJECT_PATH": ":shared", + "PRODUCT_MODULE_NAME": "shared" + }, + "script_phases": [ + { + "name": "Build shared", + "execution_position": "before_compile", + "shell_path": "/bin/sh", + "script": " if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=\"$CONFIGURATION\"\n" + } + ], + "resources": [ + "build/compose/cocoapods/compose-resources" + ] +} diff --git a/iosApp/Pods/Manifest.lock b/iosApp/Pods/Manifest.lock new file mode 100644 index 0000000..d2bfea5 --- /dev/null +++ b/iosApp/Pods/Manifest.lock @@ -0,0 +1,16 @@ +PODS: + - shared (1.0) + +DEPENDENCIES: + - shared (from `../shared`) + +EXTERNAL SOURCES: + shared: + :path: "../shared" + +SPEC CHECKSUMS: + shared: 476f8f613e223c690e3b4311f65291534e4fa034 + +PODFILE CHECKSUM: 78d6cc1b71b232202c48a29d4e6c34972c534c73 + +COCOAPODS: 1.15.2 diff --git a/iosApp/Pods/Pods.xcodeproj/project.pbxproj b/iosApp/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..677c883 --- /dev/null +++ b/iosApp/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,553 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 8777C9F6889E59EFFD631D80AEE9048B /* shared */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 8349D8E2EC974421A14EF8ABFF6AD6DC /* Build configuration list for PBXAggregateTarget "shared" */; + buildPhases = ( + BEA8885189D408D600647BDC228A6A20 /* [CP-User] Build shared */, + ); + dependencies = ( + ); + name = shared; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 648F16425FEF89525AE0325F5A984B86 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; + 8749C8E8DC500B064FA0BC7A78C38A2A /* Pods-iosApp-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BC3BD8CAFAE0C8EB92CD04E5FC24E61 /* Pods-iosApp-dummy.m */; }; + 8801CBFD38B946597BD07145B2EEFC9F /* Pods-iosApp-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 70E8DFC7821955063C886C71258CBE53 /* Pods-iosApp-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3B3DD97234976EAE23DEF848B521F3BD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8777C9F6889E59EFFD631D80AEE9048B; + remoteInfo = shared; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 257390D34074D2442461A69FE6970CBD /* Pods-iosApp-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iosApp-resources.sh"; sourceTree = ""; }; + 4D3E6DCB9CAB65A8A05C467E2BBC1F0D /* Pods-iosApp-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-iosApp-acknowledgements.markdown"; sourceTree = ""; }; + 4DAD13487B641E6B9D7476999E775A45 /* shared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = shared.debug.xcconfig; sourceTree = ""; }; + 6A3C5EB0586A09C512019B6B6A2DE103 /* Pods-iosApp-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-Info.plist"; sourceTree = ""; }; + 70E8DFC7821955063C886C71258CBE53 /* Pods-iosApp-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-iosApp-umbrella.h"; sourceTree = ""; }; + 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 9BC3BD8CAFAE0C8EB92CD04E5FC24E61 /* Pods-iosApp-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-iosApp-dummy.m"; sourceTree = ""; }; + 9C49AEBC7AA7C80C03295804C6F07963 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.release.xcconfig"; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + B097DD7534E741D5C41838011D755842 /* Pods-iosApp */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-iosApp"; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CE6991E448B5709365FFBCAB5EF9A9B4 /* shared.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = shared.framework; path = build/cocoapods/framework/shared.framework; sourceTree = ""; }; + E2E6BF268E792329201BC2BF5DE0B8DB /* shared.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = shared.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + E39BE12967CFA5BCDF680F7E8376943C /* shared.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = shared.release.xcconfig; sourceTree = ""; }; + F064C0D7CE795102A652A82AEBBA7514 /* compose-resources */ = {isa = PBXFileReference; includeInIndex = 1; name = "compose-resources"; path = "build/compose/cocoapods/compose-resources"; sourceTree = ""; }; + F6DF6FB4000E345BDEE186C956C36ABF /* Pods-iosApp-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-acknowledgements.plist"; sourceTree = ""; }; + F981EE0C95E2DFD40CA16F05D2C35B8A /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + FB978CA3A69A4DEF4DC035E9CD8D83A4 /* Pods-iosApp.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-iosApp.modulemap"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CC3CD5459C4A5476C5A93268587E63E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 648F16425FEF89525AE0325F5A984B86 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 11C970DEAE48C6D0282DFE54684F53F1 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 4C16E8CC03E90AF9CABF8C82B813AE97 /* Pods-iosApp */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 1F86AA6785DF34AFD5A71790761717DE /* Products */ = { + isa = PBXGroup; + children = ( + B097DD7534E741D5C41838011D755842 /* Pods-iosApp */, + ); + name = Products; + sourceTree = ""; + }; + 244B481B25AE39ED2811E8ED93C90033 /* Frameworks */ = { + isa = PBXGroup; + children = ( + CE6991E448B5709365FFBCAB5EF9A9B4 /* shared.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 2E499C4A5F1591C71B6F87A09468D6A7 /* Support Files */ = { + isa = PBXGroup; + children = ( + 4DAD13487B641E6B9D7476999E775A45 /* shared.debug.xcconfig */, + E39BE12967CFA5BCDF680F7E8376943C /* shared.release.xcconfig */, + ); + name = "Support Files"; + path = "../iosApp/Pods/Target Support Files/shared"; + sourceTree = ""; + }; + 40443BD2A1BD6FFE32B582A00ABBCF31 /* shared */ = { + isa = PBXGroup; + children = ( + F064C0D7CE795102A652A82AEBBA7514 /* compose-resources */, + 244B481B25AE39ED2811E8ED93C90033 /* Frameworks */, + 4A34E35645B6AB5ADA95001E5FA8730B /* Pod */, + 2E499C4A5F1591C71B6F87A09468D6A7 /* Support Files */, + ); + name = shared; + path = ../../shared; + sourceTree = ""; + }; + 4A34E35645B6AB5ADA95001E5FA8730B /* Pod */ = { + isa = PBXGroup; + children = ( + E2E6BF268E792329201BC2BF5DE0B8DB /* shared.podspec */, + ); + name = Pod; + sourceTree = ""; + }; + 4C16E8CC03E90AF9CABF8C82B813AE97 /* Pods-iosApp */ = { + isa = PBXGroup; + children = ( + FB978CA3A69A4DEF4DC035E9CD8D83A4 /* Pods-iosApp.modulemap */, + 4D3E6DCB9CAB65A8A05C467E2BBC1F0D /* Pods-iosApp-acknowledgements.markdown */, + F6DF6FB4000E345BDEE186C956C36ABF /* Pods-iosApp-acknowledgements.plist */, + 9BC3BD8CAFAE0C8EB92CD04E5FC24E61 /* Pods-iosApp-dummy.m */, + 6A3C5EB0586A09C512019B6B6A2DE103 /* Pods-iosApp-Info.plist */, + 257390D34074D2442461A69FE6970CBD /* Pods-iosApp-resources.sh */, + 70E8DFC7821955063C886C71258CBE53 /* Pods-iosApp-umbrella.h */, + F981EE0C95E2DFD40CA16F05D2C35B8A /* Pods-iosApp.debug.xcconfig */, + 9C49AEBC7AA7C80C03295804C6F07963 /* Pods-iosApp.release.xcconfig */, + ); + name = "Pods-iosApp"; + path = "Target Support Files/Pods-iosApp"; + sourceTree = ""; + }; + 578452D2E740E91742655AC8F1636D1F /* iOS */ = { + isa = PBXGroup; + children = ( + 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 58AAD176B64323B9974E5B70EC8B12DC /* Development Pods */ = { + isa = PBXGroup; + children = ( + 40443BD2A1BD6FFE32B582A00ABBCF31 /* shared */, + ); + name = "Development Pods"; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + 58AAD176B64323B9974E5B70EC8B12DC /* Development Pods */, + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, + 1F86AA6785DF34AFD5A71790761717DE /* Products */, + 11C970DEAE48C6D0282DFE54684F53F1 /* Targets Support Files */, + ); + sourceTree = ""; + }; + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 578452D2E740E91742655AC8F1636D1F /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + DA71CB665A4F7860DB550FAA48FB6AD2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8801CBFD38B946597BD07145B2EEFC9F /* Pods-iosApp-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + ED39C638569286489CD697A6C8964146 /* Pods-iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9F1E85ECB672A0CC96333A6C6DF60EE6 /* Build configuration list for PBXNativeTarget "Pods-iosApp" */; + buildPhases = ( + DA71CB665A4F7860DB550FAA48FB6AD2 /* Headers */, + EB28A529759E3D2117E28CE3CB8387D3 /* Sources */, + CC3CD5459C4A5476C5A93268587E63E7 /* Frameworks */, + 0EC1C62FF9B25EAB2D236D122EAF4C98 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 6D08CF75140F29DD6A6970543E494B09 /* PBXTargetDependency */, + ); + name = "Pods-iosApp"; + productName = Pods_iosApp; + productReference = B097DD7534E741D5C41838011D755842 /* Pods-iosApp */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + minimizedProjectReferenceProxies = 0; + productRefGroup = 1F86AA6785DF34AFD5A71790761717DE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ED39C638569286489CD697A6C8964146 /* Pods-iosApp */, + 8777C9F6889E59EFFD631D80AEE9048B /* shared */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0EC1C62FF9B25EAB2D236D122EAF4C98 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + BEA8885189D408D600647BDC228A6A20 /* [CP-User] Build shared */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Build shared"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = " if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=\"$CONFIGURATION\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EB28A529759E3D2117E28CE3CB8387D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8749C8E8DC500B064FA0BC7A78C38A2A /* Pods-iosApp-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6D08CF75140F29DD6A6970543E494B09 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = shared; + target = 8777C9F6889E59EFFD631D80AEE9048B /* shared */; + targetProxy = 3B3DD97234976EAE23DEF848B521F3BD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 03527EFE33C6B1E7347416F7E6E66403 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9C49AEBC7AA7C80C03295804C6F07963 /* Pods-iosApp.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 27E9609BB6FEB3D98AC297415D73EE7D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E39BE12967CFA5BCDF680F7E8376943C /* shared.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 30E0B9EFD9A5C45D0D351231E81B30B3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + 5D61A0D386909A90D35FAA1B9A95207D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F981EE0C95E2DFD40CA16F05D2C35B8A /* Pods-iosApp.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + B25A1C2429B15FBB86AA4312053B69E4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4DAD13487B641E6B9D7476999E775A45 /* shared.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F4FF6A0D1970CA9705974E3CB2134802 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F4FF6A0D1970CA9705974E3CB2134802 /* Debug */, + 30E0B9EFD9A5C45D0D351231E81B30B3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8349D8E2EC974421A14EF8ABFF6AD6DC /* Build configuration list for PBXAggregateTarget "shared" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B25A1C2429B15FBB86AA4312053B69E4 /* Debug */, + 27E9609BB6FEB3D98AC297415D73EE7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9F1E85ECB672A0CC96333A6C6DF60EE6 /* Build configuration list for PBXNativeTarget "Pods-iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5D61A0D386909A90D35FAA1B9A95207D /* Debug */, + 03527EFE33C6B1E7347416F7E6E66403 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/Pods-iosApp.xcscheme b/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/Pods-iosApp.xcscheme new file mode 100644 index 0000000..d58b01a --- /dev/null +++ b/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/Pods-iosApp.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/shared.xcscheme b/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/shared.xcscheme new file mode 100644 index 0000000..5bb8b2e --- /dev/null +++ b/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/shared.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..7a05c7f --- /dev/null +++ b/iosApp/Pods/Pods.xcodeproj/xcuserdata/akash.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,25 @@ + + + + + SchemeUserState + + Pods-iosApp.xcscheme + + isShown + + orderHint + 0 + + shared.xcscheme + + isShown + + orderHint + 1 + + + SuppressBuildableAutocreation + + + diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist new file mode 100644 index 0000000..19cf209 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + ${PODS_DEVELOPMENT_LANGUAGE} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.markdown b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.markdown new file mode 100644 index 0000000..102af75 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.markdown @@ -0,0 +1,3 @@ +# Acknowledgements +This application makes use of the following third party libraries: +Generated by CocoaPods - https://cocoapods.org diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist new file mode 100644 index 0000000..7acbad1 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist @@ -0,0 +1,29 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-dummy.m b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-dummy.m new file mode 100644 index 0000000..e1bcef4 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_iosApp : NSObject +@end +@implementation PodsDummy_Pods_iosApp +@end diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-input-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-input-files.xcfilelist new file mode 100644 index 0000000..bab80cf --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh +${PODS_ROOT}/../../shared/build/compose/cocoapods/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-output-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-output-files.xcfilelist new file mode 100644 index 0000000..383ba86 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-input-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-input-files.xcfilelist new file mode 100644 index 0000000..bab80cf --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh +${PODS_ROOT}/../../shared/build/compose/cocoapods/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-output-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-output-files.xcfilelist new file mode 100644 index 0000000..383ba86 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh new file mode 100755 index 0000000..c932364 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then + # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy + # resources to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +case "${TARGETED_DEVICE_FAMILY:-}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + 3) + TARGET_DEVICE_ARGS="--target-device tv" + ;; + 4) + TARGET_DEVICE_ARGS="--target-device watch" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; +esac + +install_resource() +{ + if [[ "$1" = /* ]] ; then + RESOURCE_PATH="$1" + else + RESOURCE_PATH="${PODS_ROOT}/$1" + fi + if [[ ! -e "$RESOURCE_PATH" ]] ; then + cat << EOM +error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. +EOM + exit 1 + fi + case $RESOURCE_PATH in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.framework) + echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true + xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + *) + echo "$RESOURCE_PATH" || true + echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" + ;; + esac +} +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_resource "${PODS_ROOT}/../../shared/build/compose/cocoapods/compose-resources" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_resource "${PODS_ROOT}/../../shared/build/compose/cocoapods/compose-resources" +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] +then + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + else + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" + fi +fi diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-umbrella.h b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-umbrella.h new file mode 100644 index 0000000..a3d6034 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_iosAppVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_iosAppVersionString[]; + diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig new file mode 100644 index 0000000..273bd89 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig @@ -0,0 +1,12 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +ENABLE_USER_SCRIPT_SANDBOXING = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -framework "shared" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.modulemap b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.modulemap new file mode 100644 index 0000000..1bb57b2 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.modulemap @@ -0,0 +1,6 @@ +framework module Pods_iosApp { + umbrella header "Pods-iosApp-umbrella.h" + + export * + module * { export * } +} diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig new file mode 100644 index 0000000..273bd89 --- /dev/null +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig @@ -0,0 +1,12 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +ENABLE_USER_SCRIPT_SANDBOXING = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -framework "shared" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/Pods/Target Support Files/shared/shared.debug.xcconfig b/iosApp/Pods/Target Support Files/shared/shared.debug.xcconfig new file mode 100644 index 0000000..640705e --- /dev/null +++ b/iosApp/Pods/Target Support Files/shared/shared.debug.xcconfig @@ -0,0 +1,17 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/shared +ENABLE_USER_SCRIPT_SANDBOXING = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +KOTLIN_PROJECT_PATH = :shared +OTHER_LDFLAGS = $(inherited) -l"c++" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../shared +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +PRODUCT_MODULE_NAME = shared +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/Pods/Target Support Files/shared/shared.release.xcconfig b/iosApp/Pods/Target Support Files/shared/shared.release.xcconfig new file mode 100644 index 0000000..640705e --- /dev/null +++ b/iosApp/Pods/Target Support Files/shared/shared.release.xcconfig @@ -0,0 +1,17 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/shared +ENABLE_USER_SCRIPT_SANDBOXING = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +KOTLIN_PROJECT_PATH = :shared +OTHER_LDFLAGS = $(inherited) -l"c++" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../shared +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +PRODUCT_MODULE_NAME = shared +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..39a4159 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,427 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; + 233D688A2C65FB740071A8DB /* BiometricUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233D68892C65FB740071A8DB /* BiometricUtil.swift */; }; + 233D688C2C65FBB00071A8DB /* CipherUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233D688B2C65FBB00071A8DB /* CipherUtil.swift */; }; + 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; + B4058FFAC578793E917A8C84 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEF023C9E821DAC5AA81FB02 /* Pods_iosApp.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 233D68892C65FB740071A8DB /* BiometricUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricUtil.swift; sourceTree = ""; }; + 233D688B2C65FBB00071A8DB /* CipherUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CipherUtil.swift; sourceTree = ""; }; + 238E63B12C43A6CA009CC6DA /* SetPublicKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetPublicKey.swift; sourceTree = ""; }; + 238E63B32C43A92C009CC6DA /* VerifyBiometric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyBiometric.swift; sourceTree = ""; }; + 5494C64AF663CFCF15EEFD3B /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AEF023C9E821DAC5AA81FB02 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F21EE08A49B21B38D5DDF997 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ECCB855EC93537E66FFEB0D9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B4058FFAC578793E917A8C84 /* Pods_iosApp.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 0D5CFB942850CFC40B6CE5E8 /* Pods */ = { + isa = PBXGroup; + children = ( + 5494C64AF663CFCF15EEFD3B /* Pods-iosApp.debug.xcconfig */, + F21EE08A49B21B38D5DDF997 /* Pods-iosApp.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 233D68882C65FB660071A8DB /* Biometric */ = { + isa = PBXGroup; + children = ( + 233D68892C65FB740071A8DB /* BiometricUtil.swift */, + 233D688B2C65FBB00071A8DB /* CipherUtil.swift */, + ); + path = Biometric; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 0D5CFB942850CFC40B6CE5E8 /* Pods */, + B0FFA7982D710D94A9EBBA41 /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 233D68882C65FB660071A8DB /* Biometric */, + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 7555FF82242A565900829871 /* ContentView.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + 2152FB032600AC8F00CF470E /* iOSApp.swift */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + 238E63B12C43A6CA009CC6DA /* SetPublicKey.swift */, + 238E63B32C43A92C009CC6DA /* VerifyBiometric.swift */, + ); + path = iosApp; + sourceTree = ""; + }; + B0FFA7982D710D94A9EBBA41 /* Frameworks */ = { + isa = PBXGroup; + children = ( + AEF023C9E821DAC5AA81FB02 /* Pods_iosApp.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + 2D1319DD9D7B16971087DF27 /* [CP] Check Pods Manifest.lock */, + 7555FF77242A565900829871 /* Sources */, + 7555FF79242A565900829871 /* Resources */, + ECCB855EC93537E66FFEB0D9 /* Frameworks */, + 15FD82303FC37EC32D950B51 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + productReference = 7555FF7B242A565900829871 /* iosApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + KnownAssetTags = ( + New, + ); + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = orgName; + TargetAttributes = { + 7555FF7A242A565900829871 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 15FD82303FC37EC32D950B51 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 2D1319DD9D7B16971087DF27 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 233D688A2C65FB740071A8DB /* BiometricUtil.swift in Sources */, + 233D688C2C65FBB00071A8DB /* CipherUtil.swift in Sources */, + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 7555FF83242A565900829871 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5494C64AF663CFCF15EEFD3B /* Pods-iosApp.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = N5F88VN4G4; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mifos.biometric; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F21EE08A49B21B38D5DDF997 /* Pods-iosApp.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = N5F88VN4G4; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mifos.biometric; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c009e7d --- /dev/null +++ b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..ee7e3ca --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..fb88a39 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/biometric.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/biometric.imageset/Contents.json new file mode 100644 index 0000000..43fadfd --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/biometric.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mifos_logo.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/biometric.imageset/mifos_logo.jpg b/iosApp/iosApp/Assets.xcassets/biometric.imageset/mifos_logo.jpg new file mode 100644 index 0000000..a067a3c Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/biometric.imageset/mifos_logo.jpg differ diff --git a/iosApp/iosApp/Biometric/BiometricUtil.swift b/iosApp/iosApp/Biometric/BiometricUtil.swift new file mode 100644 index 0000000..be70973 --- /dev/null +++ b/iosApp/iosApp/Biometric/BiometricUtil.swift @@ -0,0 +1,112 @@ +import Foundation +import shared +import LocalAuthentication + +class BiometricUtilIosImpl: BioMetricUtil { + + private let cipherUtil = CipherUtilIosImpl() + + private var promptDescription: String = "Authenticate" + + func setAndReturnPublicKey(completionHandler: @escaping (String?, (any Error)?) -> Void) { + + let laContext = LAContext() + laContext.localizedReason = promptDescription + laContext.localizedFallbackTitle = "Cancel" + laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: promptDescription) { + [weak self] success, authenticationError in + + DispatchQueue.main.async { + if success { + completionHandler(self?.generatePublicKey(), nil) + } else { + completionHandler(nil, authenticationError) + } + } + } + } + + func authenticate() async throws -> AuthenticationResult { + do { + _ = try self.cipherUtil.getCrypto() + return AuthenticationResult.Success() + } catch { + print("AuthenticateError: \(error.localizedDescription)") + return AuthenticationResult.Error(error: error.localizedDescription) + } + } + + func canAuthenticate() -> Bool { + var error: NSError? + let laContext = LAContext() + return laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) + } + + + func generatePublicKey() -> String? { + let keyPair = try? cipherUtil.generateKeyPair() + return keyPair?.publicKey?.toPemFormat().toBase64() + } + + func getPublicKey() -> String? { + return cipherUtil.getPublicKey()?.encoded?.toPemFormat().toBase64() + } + + func isBiometricSet() -> Bool { + return (getPublicKey() != nil) && isValidCrypto() + } + + func isValidCrypto() -> Bool { + do { + _ = try cipherUtil.getCrypto() + return true + } catch { + return false + } + } + + func signUserId(ucc: String) -> String { + guard let data = ucc.data(using: .utf8) else { + print("Failed to convert UCC to data") + fatalError() + } + + var error: Unmanaged? + guard let signature = SecKeyCreateSignature(cipherUtil.getKey()!, .rsaSignatureMessagePKCS1v15SHA256, data as CFData, &error) else { + if let error = error { + print("Error creating signature: \(error.takeRetainedValue())") + } + fatalError() + } + + return (signature as Data).base64EncodedString() + } + + +} + +extension String { + func toPemFormat() -> String { + let chunkSize = 64 + var pemString = "-----BEGIN RSA PUBLIC KEY-----\n" + var base64String = self + while base64String.count > 0 { + let chunkIndex = base64String.index(base64String.startIndex, offsetBy: min(chunkSize, base64String.count)) + let chunk = base64String[.. String? { + guard let data = self.data(using: .utf8) else { + return nil + } + return data.base64EncodedString() + } +} diff --git a/iosApp/iosApp/Biometric/CipherUtil.swift b/iosApp/iosApp/Biometric/CipherUtil.swift new file mode 100644 index 0000000..2b1daea --- /dev/null +++ b/iosApp/iosApp/Biometric/CipherUtil.swift @@ -0,0 +1,102 @@ +import Foundation +import shared + +class CipherUtilIosImpl: ICipherUtil { + private let KEY_NAME = "my_biometric_key" + private let tag: Data + + private lazy var key: SecKey? = { + let query: [String: Any] = [ + kSecClass as String: kSecClassKey, + kSecAttrApplicationTag as String: tag, + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecReturnRef as String: true + ] + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + guard status == errSecSuccess else { return nil } + return (item as! SecKey) + }() + + init() { + self.tag = KEY_NAME.data(using: .utf8)! + } + + func generateKeyPair() throws -> CommonKeyPair { + let access = SecAccessControlCreateWithFlags( + nil, + kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, + .userPresence, + nil + )! + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits as String: 2048, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: true, + kSecAttrApplicationTag as String: tag, + kSecAttrAccessControl as String: access, + ] + ] + + var error: Unmanaged? + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + throw error!.takeRetainedValue() as Error + } + guard let publicKey = SecKeyCopyPublicKey(privateKey) else { + throw NSError(domain: NSOSStatusErrorDomain, code: Int(errSecInternalError), userInfo: nil) + } + + let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil)! as Data + let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, nil)! as Data + + return CommonKeyPair(publicKey: publicKeyData.base64EncodedString(), privateKey: privateKeyData.base64EncodedString()) + } + + func getCrypto() throws -> Crypto { + guard let privateKey = getKey() else { + throw NSError(domain: NSOSStatusErrorDomain, code: Int(errSecItemNotFound), userInfo: nil) + } + + let publicKey = SecKeyCopyPublicKey(privateKey)! + let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil)! as Data + let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, nil)! as Data + + UserDefaults.standard.setValue(publicKeyData.base64EncodedString(), forKey: "PublicKey") + + return Crypto() + } + + func getPublicKey() -> (any CommonPublicKey)? { + let savedPublicKey = UserDefaults.standard.string(forKey: "PublicKey") + if (savedPublicKey != nil) { + return CommonPublicKeyImpl(encoded: savedPublicKey!) + } + + guard let privateKey = getKey() else { return CommonPublicKeyImpl(encoded: "") } + guard let publicKey = SecKeyCopyPublicKey(privateKey) else { return CommonPublicKeyImpl(encoded: "") } + let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil)! as Data + let publicKeyString = publicKeyData.base64EncodedString() + UserDefaults.standard.setValue(publicKeyString, forKey: "PublicKey") + return CommonPublicKeyImpl(encoded: publicKeyString) + } + + + func removePublicKey() async throws { + UserDefaults.standard.removeObject(forKey: "PublicKey") + let query: [String: Any] = [ + kSecClass as String: kSecClassKey, + kSecAttrApplicationTag as String: tag, + kSecAttrKeyType as String: kSecAttrKeyTypeRSA + ] + let status = SecItemDelete(query as CFDictionary) + if status != errSecSuccess && status != errSecItemNotFound { + throw NSError(domain: NSOSStatusErrorDomain, code: Int(status), userInfo: nil) + } + } + + func getKey() -> SecKey? { + return key + } + +} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 0000000..b83d4f6 --- /dev/null +++ b/iosApp/iosApp/ContentView.swift @@ -0,0 +1,32 @@ +import SwiftUI +import shared + +let biometricUtil = BiometricUtilIosImpl() +struct ContentView: View { + let greet = Greeting().greet() + @State private var path = NavigationPath() + + var body: some View { + ZStack { + ComposeViewController() + } + } +} + +struct ComposeViewController: UIViewControllerRepresentable { + @StateObject var biometricAuthorizationViewModel: BiometricAuthorizationViewModel = BiometricAuthorizationViewModel() + func makeUIViewController(context: Context) -> UIViewController { + return App_iosKt.MainViewController(bioMetricUtil: biometricUtil, biometricViewModel: biometricAuthorizationViewModel) + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} + +extension BiometricAuthorizationViewModel: ObservableObject {} diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 0000000..2a21373 --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,50 @@ + + + + + NSFaceIDUsageDescription + Set Biometric 2FA + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..4aa7c53 --- /dev/null +++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 0000000..b7bf2f4 --- /dev/null +++ b/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/mifos-passcode/build.gradle b/mifos-passcode/build.gradle index 207707c..2fc17de 100644 --- a/mifos-passcode/build.gradle +++ b/mifos-passcode/build.gradle @@ -1,5 +1,7 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlinAndroid) +} ext { bintrayRepo = 'maven' @@ -33,7 +35,7 @@ android { } defaultConfig { namespace "com.mifos.mobile.passcode" - minSdk 15 + minSdk 24 targetSdk 34 versionCode 2 versionName "1.0.0" @@ -59,14 +61,14 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.6.0' + implementation libs.androidx.appcompat + implementation libs.material - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.6.0-alpha04' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.0-alpha01' - implementation "androidx.core:core-ktx:+" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testImplementation libs.junit + androidTestImplementation libs.runner + androidTestImplementation libs.androidx.espresso.core + implementation libs.androidx.core.ktx.v1131 + implementation platform(libs.kotlin.bom) } tasks.withType(Javadoc) { diff --git a/settings.gradle b/settings.gradle index 396028a..791ecb3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,18 @@ -include ':app', ':mifos-passcode' +pluginManagement { + repositories { + google() + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} +include ':androidApp' +include ':mifos-passcode' include ':compose' +include ':shared' diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts new file mode 100644 index 0000000..96df9b6 --- /dev/null +++ b/shared/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinCocoapods) + alias(libs.plugins.androidLibrary) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.compose.compiler) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = "17" + } + } + } + iosX64() + iosArm64() + iosSimulatorArm64() + + cocoapods { + summary = "Some description for the Shared Module" + homepage = "Link to the Shared Module homepage" + version = "1.0" + ios.deploymentTarget = "16.0" + framework { + baseName = "shared" + isStatic = true + } + } + + sourceSets { + commonMain.dependencies { + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(compose.ui) + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(libs.navigation.compose) + implementation(libs.multiplatform.settings.no.arg) + } + commonTest.dependencies { + implementation(libs.kotlin.test) + } + androidMain.dependencies { + implementation (libs.androidx.biometric) + } + } + tasks.register("testClasses") +} + +android { + namespace = "com.mifos.shared" + compileSdk = 35 + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + defaultConfig { + minSdk = 24 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} +dependencies { + implementation(libs.androidx.ui.tooling.preview.android) + implementation(libs.androidx.ui.android) +} + +compose.resources { + publicResClass = true + packageOfResClass = "com.mifos.shared.resources" + generateResClass = always +} \ No newline at end of file diff --git a/shared/shared.podspec b/shared/shared.podspec new file mode 100644 index 0000000..d6b378f --- /dev/null +++ b/shared/shared.podspec @@ -0,0 +1,54 @@ +Pod::Spec.new do |spec| + spec.name = 'shared' + spec.version = '1.0' + spec.homepage = 'Link to the Shared Module homepage' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = 'Some description for the Shared Module' + spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework' + spec.libraries = 'c++' + spec.ios.deployment_target = '16.0' + + + if !Dir.exist?('build/cocoapods/framework/shared.framework') || Dir.empty?('build/cocoapods/framework/shared.framework') + raise " + + Kotlin framework 'shared' doesn't exist yet, so a proper Xcode project can't be generated. + 'pod install' should be executed after running ':generateDummyFramework' Gradle task: + + ./gradlew :shared:generateDummyFramework + + Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" + end + + spec.xcconfig = { + 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', + } + + spec.pod_target_xcconfig = { + 'KOTLIN_PROJECT_PATH' => ':shared', + 'PRODUCT_MODULE_NAME' => 'shared', + } + + spec.script_phases = [ + { + :name => 'Build shared', + :execution_position => :before_compile, + :shell_path => '/bin/sh', + :script => <<-SCRIPT + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" + exit 0 + fi + set -ev + REPO_ROOT="$PODS_TARGET_SRCROOT" + "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ + -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ + -Pkotlin.native.cocoapods.archs="$ARCHS" \ + -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" + SCRIPT + } + ] + spec.resources = ['build\compose\cocoapods\compose-resources'] +end \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/mifos/shared/BiometricUtilAndroidImpl.kt b/shared/src/androidMain/kotlin/com/mifos/shared/BiometricUtilAndroidImpl.kt new file mode 100644 index 0000000..76a1c5b --- /dev/null +++ b/shared/src/androidMain/kotlin/com/mifos/shared/BiometricUtilAndroidImpl.kt @@ -0,0 +1,132 @@ +package com.mifos.shared + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG +import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import com.mifos.shared.utility.AuthenticationResult +import com.mifos.shared.utility.BioMetricUtil +import java.util.Base64 +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class BiometricUtilAndroidImpl( + private val activity: FragmentActivity, + private val cipherUtil: ICipherUtil +) : BioMetricUtil { + + private val executor = ContextCompat.getMainExecutor(activity) + private var promptInfo: BiometricPrompt.PromptInfo? = null + private var biometricPrompt: BiometricPrompt? = null + + @RequiresApi(Build.VERSION_CODES.O) + override suspend fun setAndReturnPublicKey(): String? { + val authenticateResult = authenticate() + return when (authenticateResult) { + is AuthenticationResult.Success -> generatePublicKey() + else -> null + } + } + + override fun canAuthenticate(): Boolean { + return BiometricManager.from(activity).canAuthenticate(BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun generatePublicKey(): String? { + return cipherUtil.generateKeyPair().public?.encoded?.toBase64Encoded()?.toPemFormat()?.toBase64Encoded() + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun getPublicKey(): String? { + return cipherUtil.getPublicKey()?.encoded?.toBase64Encoded()?.toPemFormat()?.toBase64Encoded() + } + + override fun isValidCrypto(): Boolean { + return try { + cipherUtil.getCrypto() + true + } catch (e: Exception){ + false + } + } + + override suspend fun authenticate(): AuthenticationResult = suspendCoroutine { continuation -> + + biometricPrompt = BiometricPrompt(activity, executor, object : + BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + when (errorCode) { + BiometricPrompt.ERROR_LOCKOUT, BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> continuation.resume( + AuthenticationResult.AttemptExhausted) + BiometricPrompt.ERROR_NEGATIVE_BUTTON -> continuation.resume( + AuthenticationResult.NegativeButtonClick) + else -> continuation.resume(AuthenticationResult.Error(errString.toString())) + } + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + continuation.resume(AuthenticationResult.Success) + } + }) + + promptInfo?.let { + biometricPrompt?.authenticate(it, cipherUtil.getCrypto()) + } + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun signUserId(ucc: String): String { + cipherUtil.getCrypto().signature?.update(ucc.toByteArray()) + return cipherUtil.getCrypto().signature?.sign()?.toBase64Encoded() ?: "" + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun isBiometricSet(): Boolean { + return !getPublicKey().isNullOrEmpty() && isValidCrypto() + } + + fun preparePrompt( + title: String, + subtitle: String, + description: String, + ): BioMetricUtil { + promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(title) + .setSubtitle(subtitle) + .setDescription(description) + .setNegativeButtonText("Cancel") + .setAllowedAuthenticators(BIOMETRIC_STRONG) + .build() + return this + } +} + +@RequiresApi(Build.VERSION_CODES.O) +fun ByteArray.toBase64Encoded(): String? { + return Base64.getEncoder().encodeToString(this) +} + +@RequiresApi(Build.VERSION_CODES.O) +fun String.toBase64Encoded(): String? { + return Base64.getEncoder().encodeToString(this.toByteArray()) +} + +private fun String.toPemFormat(): String { + val stringBuilder = StringBuilder() + stringBuilder.append("-----BEGIN RSA PUBLIC KEY-----").append("\n") + chunked(64).forEach { + stringBuilder.append(it).append("\n") + } + stringBuilder.append("-----END RSA PUBLIC KEY-----") + return stringBuilder.toString() +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/mifos/shared/CipherUtilImpl.android.kt b/shared/src/androidMain/kotlin/com/mifos/shared/CipherUtilImpl.android.kt new file mode 100644 index 0000000..6f91348 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/mifos/shared/CipherUtilImpl.android.kt @@ -0,0 +1,60 @@ +package com.mifos.shared + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import androidx.biometric.BiometricPrompt +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.PrivateKey +import java.security.PublicKey +import java.security.Signature + +class CipherUtilAndroidImpl: ICipherUtil { + private val KEY_NAME = "biometric_key" + + override fun generateKeyPair(): KeyPair { + val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore") + val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(KEY_NAME, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY).run { + setDigests(KeyProperties.DIGEST_SHA256) + setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + build() + } + keyPairGenerator.initialize(parameterSpec) + return keyPairGenerator.genKeyPair() + } + + override fun getPublicKey(): PublicKey? = getKeyPair()?.public + + private fun getKeyPair(): KeyPair? { + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + keyStore?.getCertificate(KEY_NAME).let { return KeyPair(it?.publicKey, null) } + } + + override fun getCrypto(): Crypto { + val signature = Signature.getInstance("SHA256withRSA") + val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + val key: PrivateKey = if(keyStore.containsAlias(KEY_NAME)) + keyStore.getKey(KEY_NAME, null) as PrivateKey + else + generateKeyPair().private + signature.initSign(key) + return BiometricPrompt.CryptoObject(signature) + } + + override suspend fun removePublicKey() { + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + keyStore?.deleteEntry(KEY_NAME) + } + +} + +actual typealias CommonKeyPair = KeyPair + +actual typealias CommonPublicKey = PublicKey + +actual typealias Crypto = BiometricPrompt.CryptoObject \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/mifos/shared/Platform.android.kt b/shared/src/androidMain/kotlin/com/mifos/shared/Platform.android.kt new file mode 100644 index 0000000..cc23446 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/mifos/shared/Platform.android.kt @@ -0,0 +1,8 @@ +package com.mifos.shared + +class AndroidPlatform : Platform { +// override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}" + override val name: String = "Android" +} + +actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/shared/src/commonMain/composeResources/drawable/ic_delete.xml b/shared/src/commonMain/composeResources/drawable/ic_delete.xml new file mode 100644 index 0000000..4069f83 --- /dev/null +++ b/shared/src/commonMain/composeResources/drawable/ic_delete.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/shared/src/commonMain/composeResources/drawable/mifos_logo.jpg b/shared/src/commonMain/composeResources/drawable/mifos_logo.jpg new file mode 100644 index 0000000..a067a3c Binary files /dev/null and b/shared/src/commonMain/composeResources/drawable/mifos_logo.jpg differ diff --git a/shared/src/commonMain/composeResources/font/lato_black.ttf b/shared/src/commonMain/composeResources/font/lato_black.ttf new file mode 100644 index 0000000..4340502 Binary files /dev/null and b/shared/src/commonMain/composeResources/font/lato_black.ttf differ diff --git a/shared/src/commonMain/composeResources/font/lato_bold.ttf b/shared/src/commonMain/composeResources/font/lato_bold.ttf new file mode 100644 index 0000000..016068b Binary files /dev/null and b/shared/src/commonMain/composeResources/font/lato_bold.ttf differ diff --git a/shared/src/commonMain/composeResources/font/lato_regular.ttf b/shared/src/commonMain/composeResources/font/lato_regular.ttf new file mode 100644 index 0000000..bb2e887 Binary files /dev/null and b/shared/src/commonMain/composeResources/font/lato_regular.ttf differ diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 0000000..a4685cb --- /dev/null +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,34 @@ + + + Biometric + Passcode + hasPasscode + hasDragPasscode + passcode + drag_passcode + Create Passcode + Confirm Passcode + Enter your Passcode + Forgot Passcode + Delete Passcode Key Button + Drag your finger here only in one direction. + Drag your Pattern + Exit + Cancel + Skip + Forgot Passcode, Login Manually + Try again + Passcode do not match! + Are you sure you want to exit? + Use TouchId + Use FaceIdh + Authentication failed + Authentication not set + Feature unavailable + Biometric Registration Successful ! + Do you want to enable app lock ? + Use your existing PIN, pattern, face ID, or fingerprint to unlock this app. + Yes + No + Ok + \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/CipherUtilImpl.kt b/shared/src/commonMain/kotlin/com/mifos/shared/CipherUtilImpl.kt new file mode 100644 index 0000000..8d56f87 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/CipherUtilImpl.kt @@ -0,0 +1,18 @@ +package com.mifos.shared + +interface ICipherUtil { + @Throws(Exception::class) + fun generateKeyPair(): CommonKeyPair + + fun getPublicKey(): CommonPublicKey? + + @Throws(Exception::class) + fun getCrypto(): Crypto + + @Throws(Exception::class) + suspend fun removePublicKey() +} + +expect class CommonKeyPair +expect interface CommonPublicKey +expect class Crypto \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/Greeting.kt b/shared/src/commonMain/kotlin/com/mifos/shared/Greeting.kt new file mode 100644 index 0000000..dbea834 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/Greeting.kt @@ -0,0 +1,12 @@ +package com.mifos.shared + +import com.mifos.shared.Platform +import com.mifos.shared.getPlatform + +class Greeting { + private val platform: Platform = getPlatform() + + fun greet(): String { + return "Hello, ${platform.name}!" + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/PasscodeRepository.kt b/shared/src/commonMain/kotlin/com/mifos/shared/PasscodeRepository.kt new file mode 100644 index 0000000..7bd4cd3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/PasscodeRepository.kt @@ -0,0 +1,7 @@ +package com.mifos.shared + +interface PasscodeRepository { + fun getSavedPasscode(): String + val hasPasscode: Boolean + fun savePasscode(passcode: String) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/PasscodeRepositoryImpl.kt b/shared/src/commonMain/kotlin/com/mifos/shared/PasscodeRepositoryImpl.kt new file mode 100644 index 0000000..adc8d0d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/PasscodeRepositoryImpl.kt @@ -0,0 +1,19 @@ +package com.mifos.shared + +import com.mifos.shared.utility.PreferenceManager + + +class PasscodeRepositoryImpl constructor(private val preferenceManager: PreferenceManager) : + PasscodeRepository { + + override fun getSavedPasscode(): String { + return preferenceManager.getSavedPasscode() + } + + override val hasPasscode: Boolean + get() = preferenceManager.hasPasscode + + override fun savePasscode(passcode: String) { + preferenceManager.savePasscode(passcode) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/Platform.kt b/shared/src/commonMain/kotlin/com/mifos/shared/Platform.kt new file mode 100644 index 0000000..b1dbcfd --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/Platform.kt @@ -0,0 +1,7 @@ +package com.mifos.shared + +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/SetBiometricPublicKeyRepository.kt b/shared/src/commonMain/kotlin/com/mifos/shared/SetBiometricPublicKeyRepository.kt new file mode 100644 index 0000000..2f1f866 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/SetBiometricPublicKeyRepository.kt @@ -0,0 +1,17 @@ +package com.mifos.shared + +import kotlinx.coroutines.delay + +private var publicKeyOnServer = "" +class SetBiometricPublicKeyRepository { + suspend fun set(publicKey: String) { + delay(500) + publicKeyOnServer = publicKey + } +} + +class VerifyBiometric { + suspend fun verify(signedUserId: String): Result { + return Result.success(Unit) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/BackSpace.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/BackSpace.kt new file mode 100644 index 0000000..7e0a8f9 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/BackSpace.kt @@ -0,0 +1,45 @@ +package com.mifos.shared.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.materialIcon +import androidx.compose.material.icons.materialPath +import androidx.compose.ui.graphics.vector.ImageVector + +public val Icons.Filled.Backspace: ImageVector + get() { + if (_backspace != null) { + return _backspace!! + } + _backspace = materialIcon(name = "Filled.Backspace") { + materialPath { + moveTo(22.0f, 3.0f) + lineTo(7.0f, 3.0f) + curveToRelative(-0.69f, 0.0f, -1.23f, 0.35f, -1.59f, 0.88f) + lineTo(0.0f, 12.0f) + lineToRelative(5.41f, 8.11f) + curveToRelative(0.36f, 0.53f, 0.9f, 0.89f, 1.59f, 0.89f) + horizontalLineToRelative(15.0f) + curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f) + lineTo(24.0f, 5.0f) + curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f) + close() + moveTo(19.0f, 15.59f) + lineTo(17.59f, 17.0f) + lineTo(14.0f, 13.41f) + lineTo(10.41f, 17.0f) + lineTo(9.0f, 15.59f) + lineTo(12.59f, 12.0f) + lineTo(9.0f, 8.41f) + lineTo(10.41f, 7.0f) + lineTo(14.0f, 10.59f) + lineTo(17.59f, 7.0f) + lineTo(19.0f, 8.41f) + lineTo(15.41f, 12.0f) + lineTo(19.0f, 15.59f) + close() + } + } + return _backspace!! + } + +private var _backspace: ImageVector? = null diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/MifosIcon.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/MifosIcon.kt new file mode 100644 index 0000000..b2e0e37 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/MifosIcon.kt @@ -0,0 +1,26 @@ +package com.mifos.shared.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.mifos_logo +import org.jetbrains.compose.resources.painterResource + +@Composable +fun MifosIcon(modifier: Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.Center + ) { + Image( + modifier = Modifier.size(180.dp), + painter = painterResource(resource= Res.drawable.mifos_logo), + contentDescription = null + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PassCodeScreen.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PassCodeScreen.kt new file mode 100644 index 0000000..3ef7bd0 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PassCodeScreen.kt @@ -0,0 +1,309 @@ +package com.mifos.shared.component + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.mifos.shared.utility.BioMetricUtil +import com.mifos.shared.utility.PreferenceManager +import com.mifos.shared.theme.blueTint +import com.mifos.shared.utility.Constants.PASSCODE_LENGTH +import com.mifos.shared.utility.ShakeAnimation.performShakeAnimation +import com.mifos.shared.viewmodels.BiometricAuthorizationViewModel +import com.mifos.shared.viewmodels.BiometricEffect +import com.mifos.shared.viewmodels.PasscodeViewModel +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.biometric_registration_success +import com.mifos.shared.resources.ok +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.getString + +/** + * @author pratyush + * @since 15/3/24 + */ + +@Composable +fun PasscodeScreen( + viewModel: PasscodeViewModel = viewModel { PasscodeViewModel() }, + onForgotButton: () -> Unit, + onSkipButton: () -> Unit, + onPasscodeConfirm: (String) -> Unit, + onPasscodeRejected: () -> Unit, + enableBiometric: Boolean = false, + onBiometricAuthSuccess: () -> Unit = {}, + biometricAuthorizationViewModel: BiometricAuthorizationViewModel = viewModel(), + bioMetricUtil: BioMetricUtil? = null, +) { + val preferenceManager = remember { PreferenceManager() } + val activeStep by viewModel.activeStep.collectAsState() + val filledDots by viewModel.filledDots.collectAsState() + val passcodeVisible by viewModel.passcodeVisible.collectAsState() + val currentPasscode by viewModel.currentPasscodeInput.collectAsState() + val xShake = remember { Animatable(initialValue = 0.0F) } + var passcodeRejectedDialogVisible by remember { mutableStateOf(false) } + val biometricState by biometricAuthorizationViewModel.state.collectAsState() + var biometricMessage by rememberSaveable { mutableStateOf("") } + val coroutineScope = rememberCoroutineScope() + var showBiometricDialog by rememberSaveable{ mutableStateOf(false) } + + + if(showBiometricDialog) + { + PasscodeBiometricConfirmDialog( + setBiometric = { + biometricAuthorizationViewModel.setBiometricAuthorization(bioMetricUtil!!) + }, + cancelBiometric = { + showBiometricDialog = false + } + ) + } + + if(enableBiometric) { + biometricState.error?.let { + biometricMessage = it + } + } + + LaunchedEffect(key1 = Unit) { + if(enableBiometric) { + biometricAuthorizationViewModel.effect.collectLatest { + when (it) { + BiometricEffect.BiometricAuthSuccess -> { + onBiometricAuthSuccess.invoke() + showBiometricDialog = false + } + + BiometricEffect.BiometricSetSuccess -> { + biometricMessage = getString(Res.string.biometric_registration_success) + showBiometricDialog = false + } + } + } + } + } + + LaunchedEffect(key1 = viewModel.onPasscodeConfirmed) { + viewModel.onPasscodeConfirmed.collect { + onPasscodeConfirm(it) + } + } + LaunchedEffect(key1 = viewModel.onPasscodeRejected) { + viewModel.onPasscodeRejected.collect { + passcodeRejectedDialogVisible = true +// vibrateFeedback(context) + performShakeAnimation(xShake) + onPasscodeRejected() + } + } + + LaunchedEffect(true) { + if(preferenceManager.hasPasscode && enableBiometric) { + if(bioMetricUtil!!.isBiometricSet()) + biometricAuthorizationViewModel.authorizeBiometric(bioMetricUtil) + else + showBiometricDialog = true + } + } + + val snackBarHostState = remember { + SnackbarHostState() + } + + Scaffold( + snackbarHost = { SnackbarHost(snackBarHostState) } + ) { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PasscodeToolbar(activeStep = activeStep, preferenceManager.hasPasscode) + PasscodeSkipButton( + onSkipButton = { onSkipButton.invoke() }, + hasPassCode = preferenceManager.hasPasscode + ) + MifosIcon(modifier = Modifier.fillMaxWidth()) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, bottom = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + PasscodeHeader( + activeStep = activeStep, + isPasscodeAlreadySet = preferenceManager.hasPasscode + ) + PasscodeView( + filledDots = filledDots, + currentPasscode = currentPasscode, + passcodeVisible = passcodeVisible, + togglePasscodeVisibility = { viewModel.togglePasscodeVisibility() }, + restart = { viewModel.restart() }, + passcodeRejectedDialogVisible = passcodeRejectedDialogVisible, + onDismissDialog = { passcodeRejectedDialogVisible = false }, + xShake = xShake + ) + } + Spacer(modifier = Modifier.height(6.dp)) + PasscodeKeys( + enterKey = { viewModel.enterKey(it) }, + deleteKey = { viewModel.deleteKey() }, + deleteAllKeys = { viewModel.deleteAllKeys() }, + modifier = Modifier.padding(horizontal = 12.dp) + ) + Spacer(modifier = Modifier.height(8.dp)) + PasscodeForgotButton( + onForgotButton = { onForgotButton.invoke() }, + hasPassCode = preferenceManager.hasPasscode + ) + + UseTouchIdButton( + onClick = { + if ( bioMetricUtil!!.isBiometricSet() ) + biometricAuthorizationViewModel.authorizeBiometric(bioMetricUtil) + else + showBiometricDialog = true + }, + hasPassCode = preferenceManager.hasPasscode, + enableBiometric = enableBiometric + ) + + LaunchedEffect ( biometricMessage ) { + if(biometricMessage.isNotEmpty()) { + coroutineScope.launch { + snackBarHostState.showSnackbar( + message = biometricMessage, + duration = SnackbarDuration.Short, + withDismissAction = false, + actionLabel = getString(Res.string.ok) + ) + } + } + } + } + } +} + +@Composable +private fun PasscodeView( + modifier: Modifier = Modifier, + restart: () -> Unit, + togglePasscodeVisibility: () -> Unit, + filledDots: Int, + passcodeVisible: Boolean, + currentPasscode: String, + passcodeRejectedDialogVisible: Boolean, + onDismissDialog: () -> Unit, + xShake: Animatable +) { + PasscodeMismatchedDialog( + visible = passcodeRejectedDialogVisible, + onDismiss = { + onDismissDialog.invoke() + restart() + } + ) + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = modifier.offset(x = xShake.value.dp), + horizontalArrangement = Arrangement.spacedBy( + space = 26.dp, + alignment = Alignment.CenterHorizontally + ), + verticalAlignment = Alignment.CenterVertically + ) { + repeat(PASSCODE_LENGTH) { dotIndex -> + if (passcodeVisible && dotIndex < currentPasscode.length) { + Text( + text = currentPasscode[dotIndex].toString(), + color = blueTint + ) + } else { + val isFilledDot = dotIndex + 1 <= filledDots + val dotColor = animateColorAsState( + if (isFilledDot) blueTint else Color.Gray, label = "" + ) + + Box( + modifier = Modifier + .size(14.dp) + .background( + color = dotColor.value, + shape = CircleShape + ) + ) + } + } + } + IconButton( + onClick = { togglePasscodeVisibility.invoke() }, + modifier = Modifier.padding(start = 10.dp) + ) { + Icon( + imageVector = if (passcodeVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff, + contentDescription = null + ) + } + } +} + +//@Preview(showBackground = true) +//@Composable +//fun PasscodeScreenPreview() { +// PasscodeScreen( +// viewModel = PasscodeViewModel(object : PasscodeRepository { +// override fun getSavedPasscode(): String { +// return "" +// } +// +// override val hasPasscode: Boolean +// get() = false +// +// override fun savePasscode(passcode: String) {} +// +// }), +// {}, {}, {}, {} +// ) +//} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeBiometricConfirmDialog.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeBiometricConfirmDialog.kt new file mode 100644 index 0000000..6d620f1 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeBiometricConfirmDialog.kt @@ -0,0 +1,117 @@ +package com.mifos.shared.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.heightIn +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.White +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import com.mifos.shared.getPlatform +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.enable_biometric_dialog_description +import com.mifos.shared.resources.enable_biometric_dialog_title +import com.mifos.shared.resources.no +import com.mifos.shared.resources.yes +import com.mifos.shared.theme.blueTint +import org.jetbrains.compose.resources.stringResource + +@Composable +fun PasscodeBiometricConfirmDialog( + cancelBiometric: () -> Unit, + setBiometric: () -> Unit +) { + + Dialog(onDismissRequest = { cancelBiometric.invoke() }) { + + Box( + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .background(Color.White) + .padding(16.dp) + ) { + + Column { + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = stringResource(resource = Res.string.enable_biometric_dialog_title), + modifier = Modifier + .padding(8.dp), + fontSize = 20.sp + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = stringResource(resource = Res.string.enable_biometric_dialog_description), + modifier = Modifier + .padding(8.dp), + fontSize = 12.sp + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + ) { + DialogButton( + onClick = { cancelBiometric.invoke() }, + modifier = Modifier + .padding(end = 8.dp) + .weight(1f), + text = stringResource(resource = Res.string.no) + ) + + DialogButton( + onClick = { setBiometric.invoke() }, + modifier = Modifier + .padding(start = 8.dp) + .weight(1f), + text = stringResource(resource = Res.string.yes) + ) + } + } + } + } +} + +@Composable +fun DialogButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Button( + onClick = onClick, + modifier = modifier + .fillMaxWidth() + .height(36.dp), + colors = ButtonDefaults.buttonColors( + containerColor = blueTint, + contentColor = White, + disabledContainerColor = Color.DarkGray, + disabledContentColor = White + ) + ) { + Text(text = text) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeButton.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeButton.kt new file mode 100644 index 0000000..f6fd6ff --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeButton.kt @@ -0,0 +1,92 @@ +package com.mifos.shared.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.mifos.shared.getPlatform +import com.mifos.shared.theme.forgotButtonStyle +import com.mifos.shared.theme.skipButtonStyle +import com.mifos.shared.theme.useTouchIdButtonStyle +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.forgot_passcode_login_manually +import com.mifos.shared.resources.skip +import com.mifos.shared.resources.use_faceId +import com.mifos.shared.resources.use_touchId +import org.jetbrains.compose.resources.stringResource + +@Composable +fun PasscodeSkipButton( + onSkipButton: () -> Unit, + hasPassCode: Boolean +) { + if (!hasPassCode) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = 16.dp), + horizontalArrangement = Arrangement.End + ) { + TextButton( + onClick = { onSkipButton.invoke() } + ) { + Text(text = stringResource(Res.string.skip), style = skipButtonStyle()) + } + } + } + +} + +@Composable +fun PasscodeForgotButton( + onForgotButton: () -> Unit, + hasPassCode: Boolean +) { + if (hasPassCode) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = 16.dp), + horizontalArrangement = Arrangement.Center + ) { + TextButton( + onClick = { onForgotButton.invoke() } + ) { + Text( + text = stringResource(Res.string.forgot_passcode_login_manually), + style = forgotButtonStyle() + ) + } + } + } +} + +@Composable +fun UseTouchIdButton( + onClick: () -> Unit, + hasPassCode: Boolean, + enableBiometric: Boolean +) { + if (hasPassCode && enableBiometric) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = 16.dp), + horizontalArrangement = Arrangement.Center + ) { + TextButton( + onClick = onClick + ) { + if(getPlatform().name == "Android") + Text(text = stringResource(Res.string.use_touchId), style = useTouchIdButtonStyle()) + else + Text(text = stringResource(Res.string.use_faceId), style = useTouchIdButtonStyle()) + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeHeader.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeHeader.kt new file mode 100644 index 0000000..97e6f43 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeHeader.kt @@ -0,0 +1,114 @@ +package com.mifos.shared.component + +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateOffset +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mifos.shared.utility.Step +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.confirm_passcode +import com.mifos.shared.resources.create_passcode +import com.mifos.shared.resources.enter_your_passcode +import org.jetbrains.compose.resources.stringResource + +@Composable +fun PasscodeHeader( + modifier: Modifier = Modifier, + activeStep: Step, + isPasscodeAlreadySet: Boolean, +) { + val transitionState = remember { MutableTransitionState(activeStep) } + transitionState.targetState = activeStep + + val transition: Transition = updateTransition( + transitionState = transitionState, + label = "Headers Transition" + ) + + val offset = 200.0F + val zeroOffset = Offset(x = 0.0F, y = 0.0F) + val negativeOffset = Offset(x = -offset, y = 0.0F) + val positiveOffset = Offset(x = offset, y = 0.0F) + + val xTransitionHeader1 by transition.animateOffset(label = "Transition Offset Header 1") { + if (it == Step.Create) zeroOffset else negativeOffset + } + val xTransitionHeader2 by transition.animateOffset(label = "Transition Offset Header 2") { + if (it == Step.Confirm) zeroOffset else positiveOffset + } + val alphaHeader1 by transition.animateFloat(label = "Transition Alpha Header 1") { + if (it == Step.Create) 1.0F else 0.0F + } + val alphaHeader2 by transition.animateFloat(label = "Transition Alpha Header 2") { + if (it == Step.Confirm) 1.0F else 0.0F + } + val scaleHeader1 by transition.animateFloat(label = "Transition Alpha Header 1") { + if (it == Step.Create) 1.0F else 0.5F + } + val scaleHeader2 by transition.animateFloat(label = "Transition Alpha Header 2") { + if (it == Step.Confirm) 1.0F else 0.5F + } + + Box( + modifier = modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + if (isPasscodeAlreadySet) { + Text( + modifier = Modifier + .offset(x = xTransitionHeader1.x.dp) + .alpha(alpha = alphaHeader1) + .scale(scale = scaleHeader1), + text = stringResource(resource = Res.string.enter_your_passcode), + style = TextStyle(fontSize = 20.sp) + ) + } else { + if (activeStep == Step.Create) { + Text( + modifier = Modifier + .offset(x = xTransitionHeader1.x.dp) + .alpha(alpha = alphaHeader1) + .scale(scale = scaleHeader1), + text = stringResource(resource = Res.string.create_passcode), + style = TextStyle(fontSize = 20.sp) + ) + } else if (activeStep == Step.Confirm) { + Text( + modifier = Modifier + .offset(x = xTransitionHeader2.x.dp) + .alpha(alpha = alphaHeader2) + .scale(scale = scaleHeader2), + text = stringResource(resource = Res.string.confirm_passcode), + style = TextStyle(fontSize = 20.sp) + ) + } + } + } + } +} + +//@Preview +//@Composable +//fun PasscodeHeaderPreview() { +// PasscodeHeader(activeStep = Step.Create, isPasscodeAlreadySet = true) +//} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeKeys.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeKeys.kt new file mode 100644 index 0000000..d132006 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeKeys.kt @@ -0,0 +1,199 @@ +package com.mifos.shared.component + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.mifos.shared.theme.PasscodeKeyButtonStyle +import com.mifos.shared.theme.blueTint + +@Composable +fun PasscodeKeys( + enterKey: (String) -> Unit, + deleteKey: () -> Unit, + deleteAllKeys: () -> Unit, + modifier: Modifier = Modifier, +) { + val onEnterKeyClick = { keyTitle: String -> + enterKey(keyTitle) + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Row(modifier = Modifier.fillMaxWidth()) { + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "1", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "2", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "3", + onClick = onEnterKeyClick + ) + } + Row(modifier = Modifier.fillMaxWidth()) { + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "4", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "5", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "6", + onClick = onEnterKeyClick + ) + } + Row(modifier = Modifier.fillMaxWidth()) { + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "7", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "8", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "9", + onClick = onEnterKeyClick + ) + } + Row(modifier = Modifier.fillMaxWidth()) { + PasscodeKey(modifier = Modifier.weight(weight = 1.0F)) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyTitle = "0", + onClick = onEnterKeyClick + ) + PasscodeKey( + modifier = Modifier.weight(weight = 1.0F), + keyIcon = Icons.Filled.Backspace, + keyIconContentDescription = "Delete Passcode Key Button", + onClick = { + deleteKey() + }, + onLongClick = { + deleteAllKeys() + } + ) + } + } +} + +@Composable +fun PasscodeKey( + modifier: Modifier = Modifier, + keyTitle: String = "", + keyIcon: ImageVector? = null, + keyIconContentDescription: String = "", + onClick: ((String) -> Unit)? = null, + onLongClick: (() -> Unit)? = null +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.Center + ) { + CombinedClickableIconButton( + modifier = Modifier + .padding(all = 4.dp), + onClick = { + onClick?.invoke(keyTitle) + }, + onLongClick = { + onLongClick?.invoke() + } + ) { + if (keyIcon == null) { + Text( + text = keyTitle, + style = PasscodeKeyButtonStyle().copy(color = blueTint) + ) + } else { + Icon( + imageVector = Icons.Default.Backspace, + contentDescription = keyIconContentDescription, + tint = blueTint + ) + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun CombinedClickableIconButton( + onClick: () -> Unit, + onLongClick: () -> Unit, + modifier: Modifier = Modifier, + size: Dp = 48.dp, + rippleRadius: Dp = 36.dp, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable () -> Unit +) { + Column( + modifier = modifier + .size(size = size) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + enabled = enabled, + role = Role.Button, + interactionSource = interactionSource, + indication = rememberRipple( + bounded = false, + radius = rippleRadius, + color = Color.Cyan + ) + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + val contentAlpha = + if (enabled) LocalContentColor.current else LocalContentColor.current.copy(alpha = 0f) + CompositionLocalProvider(LocalContentColor provides contentAlpha, content = content) + } +} + + +//@Preview +//@Composable +//fun PasscodeKeysPreview() { +// PasscodeKeys({}, {}, {}) +//} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeMismatchedDialog.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeMismatchedDialog.kt new file mode 100644 index 0000000..d41d108 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeMismatchedDialog.kt @@ -0,0 +1,37 @@ +package com.mifos.shared.component + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.passcode_do_not_match +import com.mifos.shared.resources.try_again +import org.jetbrains.compose.resources.stringResource + +@Composable +fun PasscodeMismatchedDialog( + visible: Boolean, + onDismiss: () -> Unit +) { + if (visible) { + AlertDialog( + shape = MaterialTheme.shapes.large, + containerColor = Color.White, + title = { + Text( + text = stringResource(Res.string.passcode_do_not_match), + color = Color.Black + ) + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text(text = stringResource(Res.string.try_again), color = Color.Black) + } + }, + onDismissRequest = onDismiss + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeStepIndicator.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeStepIndicator.kt new file mode 100644 index 0000000..a01cde6 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeStepIndicator.kt @@ -0,0 +1,50 @@ +package com.mifos.shared.component + +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.mifos.shared.theme.blueTint +import com.mifos.shared.utility.Constants.STEPS_COUNT +import com.mifos.shared.utility.Step + +@Composable +fun PasscodeStepIndicator( + modifier: Modifier = Modifier, + activeStep: Step +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy( + space = 6.dp, + alignment = Alignment.CenterHorizontally + ) + ) { + repeat(STEPS_COUNT) { step -> + val isActiveStep = step <= activeStep.index + val stepColor = + animateColorAsState(if (isActiveStep) blueTint else Color.Gray, label = "") + + Box( + modifier = Modifier + .size( + width = 72.dp, + height = 4.dp + ) + .background( + color = stepColor.value, + shape = MaterialTheme.shapes.medium + ) + ) + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeToolbar.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeToolbar.kt new file mode 100644 index 0000000..2bc44bd --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/PasscodeToolbar.kt @@ -0,0 +1,80 @@ +package com.mifos.shared.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.mifos.shared.utility.Step +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.are_you_sure_you_want_to_exit +import com.mifos.shared.resources.cancel +import com.mifos.shared.resources.exit +import org.jetbrains.compose.resources.stringResource + +@Composable +fun PasscodeToolbar(activeStep: Step, hasPasscode: Boolean) { + var exitWarningDialogVisible by remember { mutableStateOf(false) } + ExitWarningDialog( + visible = exitWarningDialogVisible, + onConfirm = {}, + onDismiss = { + exitWarningDialogVisible = false + } + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.Center + ) { + if (!hasPasscode) { + PasscodeStepIndicator( + activeStep = activeStep + ) + } + } +} + +@Composable +fun ExitWarningDialog( + visible: Boolean, + onConfirm: () -> Unit, + onDismiss: () -> Unit +) { + if (visible) { + AlertDialog( + shape = MaterialTheme.shapes.large, + containerColor = Color.White, + title = { + Text( + text = stringResource(Res.string.are_you_sure_you_want_to_exit), + color = Color.Black + ) + }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(text = stringResource(Res.string.exit)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(text = stringResource(Res.string.cancel)) + } + }, + onDismissRequest = onDismiss + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/Visibility.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/Visibility.kt new file mode 100644 index 0000000..9264e0b --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/Visibility.kt @@ -0,0 +1,58 @@ +package com.mifos.shared.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.materialIcon +import androidx.compose.material.icons.materialPath +import androidx.compose.ui.graphics.vector.ImageVector + +public val Icons.Filled.VisibilityOff: ImageVector + get() { + if (_visibilityOff != null) { + return _visibilityOff!! + } + _visibilityOff = materialIcon(name = "Filled.VisibilityOff") { + materialPath { + moveTo(12.0f, 7.0f) + curveToRelative(2.76f, 0.0f, 5.0f, 2.24f, 5.0f, 5.0f) + curveToRelative(0.0f, 0.65f, -0.13f, 1.26f, -0.36f, 1.83f) + lineToRelative(2.92f, 2.92f) + curveToRelative(1.51f, -1.26f, 2.7f, -2.89f, 3.43f, -4.75f) + curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f) + curveToRelative(-1.4f, 0.0f, -2.74f, 0.25f, -3.98f, 0.7f) + lineToRelative(2.16f, 2.16f) + curveTo(10.74f, 7.13f, 11.35f, 7.0f, 12.0f, 7.0f) + close() + moveTo(2.0f, 4.27f) + lineToRelative(2.28f, 2.28f) + lineToRelative(0.46f, 0.46f) + curveTo(3.08f, 8.3f, 1.78f, 10.02f, 1.0f, 12.0f) + curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f) + curveToRelative(1.55f, 0.0f, 3.03f, -0.3f, 4.38f, -0.84f) + lineToRelative(0.42f, 0.42f) + lineTo(19.73f, 22.0f) + lineTo(21.0f, 20.73f) + lineTo(3.27f, 3.0f) + lineTo(2.0f, 4.27f) + close() + moveTo(7.53f, 9.8f) + lineToRelative(1.55f, 1.55f) + curveToRelative(-0.05f, 0.21f, -0.08f, 0.43f, -0.08f, 0.65f) + curveToRelative(0.0f, 1.66f, 1.34f, 3.0f, 3.0f, 3.0f) + curveToRelative(0.22f, 0.0f, 0.44f, -0.03f, 0.65f, -0.08f) + lineToRelative(1.55f, 1.55f) + curveToRelative(-0.67f, 0.33f, -1.41f, 0.53f, -2.2f, 0.53f) + curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f) + curveToRelative(0.0f, -0.79f, 0.2f, -1.53f, 0.53f, -2.2f) + close() + moveTo(11.84f, 9.02f) + lineToRelative(3.15f, 3.15f) + lineToRelative(0.02f, -0.16f) + curveToRelative(0.0f, -1.66f, -1.34f, -3.0f, -3.0f, -3.0f) + lineToRelative(-0.17f, 0.01f) + close() + } + } + return _visibilityOff!! + } + +private var _visibilityOff: ImageVector? = null diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/component/VisibilityOff.kt b/shared/src/commonMain/kotlin/com/mifos/shared/component/VisibilityOff.kt new file mode 100644 index 0000000..44fffff --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/component/VisibilityOff.kt @@ -0,0 +1,38 @@ +package com.mifos.shared.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.materialIcon +import androidx.compose.material.icons.materialPath +import androidx.compose.ui.graphics.vector.ImageVector + +public val Icons.Filled.Visibility: ImageVector + get() { + if (_visibility != null) { + return _visibility!! + } + _visibility = materialIcon(name = "Filled.Visibility") { + materialPath { + moveTo(12.0f, 4.5f) + curveTo(7.0f, 4.5f, 2.73f, 7.61f, 1.0f, 12.0f) + curveToRelative(1.73f, 4.39f, 6.0f, 7.5f, 11.0f, 7.5f) + reflectiveCurveToRelative(9.27f, -3.11f, 11.0f, -7.5f) + curveToRelative(-1.73f, -4.39f, -6.0f, -7.5f, -11.0f, -7.5f) + close() + moveTo(12.0f, 17.0f) + curveToRelative(-2.76f, 0.0f, -5.0f, -2.24f, -5.0f, -5.0f) + reflectiveCurveToRelative(2.24f, -5.0f, 5.0f, -5.0f) + reflectiveCurveToRelative(5.0f, 2.24f, 5.0f, 5.0f) + reflectiveCurveToRelative(-2.24f, 5.0f, -5.0f, 5.0f) + close() + moveTo(12.0f, 9.0f) + curveToRelative(-1.66f, 0.0f, -3.0f, 1.34f, -3.0f, 3.0f) + reflectiveCurveToRelative(1.34f, 3.0f, 3.0f, 3.0f) + reflectiveCurveToRelative(3.0f, -1.34f, 3.0f, -3.0f) + reflectiveCurveToRelative(-1.34f, -3.0f, -3.0f, -3.0f) + close() + } + } + return _visibility!! + } + +private var _visibility: ImageVector? = null \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/theme/Color.kt b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Color.kt new file mode 100644 index 0000000..497483a --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Color.kt @@ -0,0 +1,5 @@ +package com.mifos.shared.theme + +import androidx.compose.ui.graphics.Color + +val blueTint = Color(0xFF03A9F4) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/theme/Font.kt b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Font.kt new file mode 100644 index 0000000..f6c2937 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Font.kt @@ -0,0 +1,30 @@ +package com.mifos.shared.theme + +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.lato_black +import com.mifos.shared.resources.lato_bold +import com.mifos.shared.resources.lato_regular +import org.jetbrains.compose.resources.Font + +@Composable +fun LatoFonts() = FontFamily( + Font( + resource = Res.font.lato_regular, + weight = FontWeight.Normal, + style = FontStyle.Normal + ), + Font( + resource = Res.font.lato_bold, + weight = FontWeight.Bold, + style = FontStyle.Normal + ), + Font( + resource = Res.font.lato_black, + weight = FontWeight.Black, + style = FontStyle.Normal + ) +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/theme/Theme.kt b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Theme.kt new file mode 100644 index 0000000..760c6c5 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Theme.kt @@ -0,0 +1,40 @@ +package com.mifos.shared.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.Blue + +private val DarkColorPalette = darkColorScheme( + primary = Color.Cyan, + onPrimary = Color.Cyan, + secondary = Color.Black.copy(alpha = 0.2f), + background = Color.Black +) +private val LightColorPalette = lightColorScheme( + primary = Blue, + onPrimary = Blue, + secondary = Color.Blue.copy(alpha = 0.4f), + background = Color.White +) + +@Composable +fun MifosPasscodeTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colorScheme = colors, + typography = Typography(), + content = content + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/theme/Type.kt b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Type.kt new file mode 100644 index 0000000..af9c8c3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/theme/Type.kt @@ -0,0 +1,57 @@ +package com.mifos.shared.theme + +import androidx.compose.material3.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +@Composable +fun Typography() = Typography().run { + val fontFamily = LatoFonts() + copy( + displayLarge = displayLarge.copy(fontFamily = fontFamily), + displayMedium = displayMedium.copy(fontFamily = fontFamily), + displaySmall = displaySmall.copy(fontFamily = fontFamily), + headlineLarge = headlineLarge.copy(fontFamily = fontFamily), + headlineMedium = headlineMedium.copy(fontFamily = fontFamily), + headlineSmall = headlineSmall.copy(fontFamily = fontFamily), + titleLarge = titleLarge.copy(fontFamily = fontFamily), + titleMedium = titleMedium.copy(fontFamily = fontFamily), + titleSmall = titleSmall.copy(fontFamily = fontFamily), + bodyLarge = bodyLarge.copy(fontFamily = fontFamily), + bodyMedium = bodyMedium.copy(fontFamily = fontFamily), + bodySmall = bodySmall.copy(fontFamily = fontFamily), + labelLarge = labelLarge.copy(fontFamily = fontFamily), + labelMedium = labelMedium.copy(fontFamily = fontFamily), + labelSmall = labelSmall.copy(fontFamily = fontFamily) + ) +} + +@Composable +fun PasscodeKeyButtonStyle() = TextStyle( + fontFamily = LatoFonts(), + fontWeight = FontWeight.Bold, + fontSize = 24.sp +) + +@Composable +fun skipButtonStyle() = TextStyle( + color = blueTint, + fontSize = 20.sp, + fontFamily = LatoFonts() +) + +@Composable +fun forgotButtonStyle() = TextStyle( + color = blueTint, + fontSize = 14.sp, + fontFamily = LatoFonts() +) + +@Composable +fun useTouchIdButtonStyle() = TextStyle( + color = blueTint, + fontSize = 14.sp, + fontFamily = LatoFonts() +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/utility/BioMetricUtil.kt b/shared/src/commonMain/kotlin/com/mifos/shared/utility/BioMetricUtil.kt new file mode 100644 index 0000000..09bd109 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/utility/BioMetricUtil.kt @@ -0,0 +1,21 @@ +package com.mifos.shared.utility + +interface BioMetricUtil { + + suspend fun setAndReturnPublicKey(): String? + suspend fun authenticate(): AuthenticationResult + fun canAuthenticate(): Boolean + fun generatePublicKey(): String? + fun signUserId(ucc: String): String + fun isBiometricSet(): Boolean + fun getPublicKey(): String? + fun isValidCrypto(): Boolean +} + +sealed class AuthenticationResult { + data object Success: AuthenticationResult() + data object Failed: AuthenticationResult() + data object AttemptExhausted: AuthenticationResult() + data object NegativeButtonClick: AuthenticationResult() + data class Error(val error: String): AuthenticationResult() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/utility/Constants.kt b/shared/src/commonMain/kotlin/com/mifos/shared/utility/Constants.kt new file mode 100644 index 0000000..5d277e4 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/utility/Constants.kt @@ -0,0 +1,7 @@ +package com.mifos.shared.utility + +object Constants { + const val STEPS_COUNT = 2 + const val PASSCODE_LENGTH = 4 + const val VIBRATE_FEEDBACK_DURATION = 300L +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/utility/PreferenceManager.kt b/shared/src/commonMain/kotlin/com/mifos/shared/utility/PreferenceManager.kt new file mode 100644 index 0000000..b075c2c --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/utility/PreferenceManager.kt @@ -0,0 +1,31 @@ +package com.mifos.shared.utility + +import com.mifos.shared.resources.Res +import com.mifos.shared.resources.has_passcode +import com.mifos.shared.resources.passcode +import com.russhwolf.settings.Settings + +/** + * @author pratyush + * @since 15/3/24 + */ + +class PreferenceManager( ) +{ + private val settings : Settings by lazy { + Settings() + } + + var hasPasscode: Boolean + get() = settings.getBoolean(Res.string.has_passcode.toString(), false) + set(value) = settings.putBoolean(Res.string.has_passcode.toString(), value) + + fun savePasscode(passcode: String) { + settings.putString(Res.string.passcode.toString(), passcode) + hasPasscode = true + } + + fun getSavedPasscode(): String { + return settings.getString(Res.string.passcode.toString(), "") + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/utility/ShakeAnimation.kt b/shared/src/commonMain/kotlin/com/mifos/shared/utility/ShakeAnimation.kt new file mode 100644 index 0000000..0065d38 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/utility/ShakeAnimation.kt @@ -0,0 +1,28 @@ +package com.mifos.shared.utility + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.keyframes +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +object ShakeAnimation { + + fun CoroutineScope.performShakeAnimation(xShake: Animatable) { + launch { + xShake.animateTo( + targetValue = 0f, // This resets the position after the shake + animationSpec = keyframes { + durationMillis = 280 // Total animation duration + 0f at 0 with LinearOutSlowInEasing // Start position + 20f at 80 with LinearOutSlowInEasing // Move right + -20f at 120 with LinearOutSlowInEasing // Move left + 10f at 160 with LinearOutSlowInEasing // Move right + -10f at 200 with LinearOutSlowInEasing // Move left + 5f at 240 with LinearOutSlowInEasing // Move right + 0f at 280 // End at the original position + } + ) + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/utility/Step.kt b/shared/src/commonMain/kotlin/com/mifos/shared/utility/Step.kt new file mode 100644 index 0000000..ba765b5 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/utility/Step.kt @@ -0,0 +1,6 @@ +package com.mifos.shared.utility + +enum class Step(var index: Int) { + Create(0), + Confirm(1) +} diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/viewmodels/BiometricAuthorizationViewModel.kt b/shared/src/commonMain/kotlin/com/mifos/shared/viewmodels/BiometricAuthorizationViewModel.kt new file mode 100644 index 0000000..5fbaf20 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/viewmodels/BiometricAuthorizationViewModel.kt @@ -0,0 +1,84 @@ +package com.mifos.shared.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mifos.shared.utility.AuthenticationResult +import com.mifos.shared.utility.BioMetricUtil +import com.mifos.shared.SetBiometricPublicKeyRepository +import com.mifos.shared.VerifyBiometric +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class BiometricAuthorizationViewModel: ViewModel() { + private val setBiometricPublicKeyRepository = SetBiometricPublicKeyRepository() + private val verifyBiometric = VerifyBiometric() + + private val _state: MutableStateFlow = + MutableStateFlow(BiometricState(false, null)) + private val _effect: MutableSharedFlow = MutableSharedFlow(replay = 0) + + val state: StateFlow + get() = _state + + val effect: SharedFlow + get() = _effect + + fun setBiometricAuthorization(bioMetricUtil: BioMetricUtil) { + viewModelScope.launch { + _state.value = BiometricState(isLoading = true, error = null) + if (!bioMetricUtil.canAuthenticate()) { + _state.value = BiometricState(isLoading = true, error = "Biometric not available") + return@launch + } + val publicKey = bioMetricUtil.setAndReturnPublicKey() ?: "" + setBiometricPublicKeyRepository.set(publicKey) + _state.value = BiometricState(isLoading = false, error = null) + _effect.emit(BiometricEffect.BiometricSetSuccess) + + } + } + + fun authorizeBiometric(bioMetricUtil: BioMetricUtil) { + viewModelScope.launch { + when(val biometricResult = bioMetricUtil.authenticate()) { + AuthenticationResult.AttemptExhausted -> { + _state.value = BiometricState(isLoading = false, error = "Attempt Exhausted") + } + is AuthenticationResult.Error -> { + _state.value = BiometricState(isLoading = false, error = biometricResult.error) + } + AuthenticationResult.Failed -> { + _state.value = BiometricState(isLoading = false, error = "Biometric Failed") + } + AuthenticationResult.NegativeButtonClick -> { + _state.value = BiometricState(isLoading = false, error = "Biometric Canceled") + } + AuthenticationResult.Success -> { + _state.value = BiometricState(isLoading = true, error = null) + val signedUserId = bioMetricUtil.signUserId("userId") + val result = verifyBiometric.verify(signedUserId) + if (result.isSuccess) { + _state.value = BiometricState(isLoading = false, error = null) + _effect.emit(BiometricEffect.BiometricAuthSuccess) + } else { + _state.value = BiometricState(isLoading = false, error = result.exceptionOrNull()!!.message) + } + } + } + + } + } +} + +data class BiometricState( + val isLoading: Boolean, + val error: String? +) + +sealed class BiometricEffect { + data object BiometricSetSuccess: BiometricEffect() + data object BiometricAuthSuccess: BiometricEffect() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mifos/shared/viewmodels/PasscodeViewModel.kt b/shared/src/commonMain/kotlin/com/mifos/shared/viewmodels/PasscodeViewModel.kt new file mode 100644 index 0000000..5a68f92 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mifos/shared/viewmodels/PasscodeViewModel.kt @@ -0,0 +1,146 @@ +package com.mifos.shared.viewmodels + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mifos.shared.utility.PreferenceManager +import com.mifos.shared.PasscodeRepositoryImpl +import com.mifos.shared.utility.Constants.PASSCODE_LENGTH +import com.mifos.shared.utility.Step +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +/** + * @author pratyush + * @since 15/3/24 + */ + +class PasscodeViewModel : + ViewModel() { + + private val + passcodeRepository = PasscodeRepositoryImpl(PreferenceManager()) + private val _onPasscodeConfirmed = MutableSharedFlow() + private val _onPasscodeRejected = MutableSharedFlow() + + private val _activeStep = MutableStateFlow(Step.Create) + private val _filledDots = MutableStateFlow(0) + + private var createPasscode: StringBuilder = StringBuilder() + private var confirmPasscode: StringBuilder = StringBuilder() + + val onPasscodeConfirmed = _onPasscodeConfirmed.asSharedFlow() + val onPasscodeRejected = _onPasscodeRejected.asSharedFlow() + + val activeStep = _activeStep.asStateFlow() + val filledDots = _filledDots.asStateFlow() + + private val _passcodeVisible = MutableStateFlow(false) + val passcodeVisible = _passcodeVisible.asStateFlow() + + private val _currentPasscodeInput = MutableStateFlow("") + val currentPasscodeInput = _currentPasscodeInput.asStateFlow() + + private var _isPasscodeAlreadySet = mutableStateOf(passcodeRepository.hasPasscode) + + init { + resetData() + } + + private fun emitActiveStep(activeStep: Step) = viewModelScope.launch { + _activeStep.emit(activeStep) + } + + private fun emitFilledDots(filledDots: Int) = viewModelScope.launch { + _filledDots.emit(filledDots) + } + + private fun emitOnPasscodeConfirmed(confirmPassword: String) = viewModelScope.launch { + _onPasscodeConfirmed.emit(confirmPassword) + } + + private fun emitOnPasscodeRejected() = viewModelScope.launch { + _onPasscodeRejected.emit(Unit) + } + + fun togglePasscodeVisibility() { + _passcodeVisible.value = !_passcodeVisible.value + } + + private fun resetData() { + emitActiveStep(Step.Create) + emitFilledDots(0) + + createPasscode.clear() + confirmPasscode.clear() + } + + fun enterKey(key: String) { + if (_filledDots.value >= PASSCODE_LENGTH) { + return + } + + val currentPasscode = + if (_activeStep.value == Step.Create) createPasscode else confirmPasscode + currentPasscode.append(key) + _currentPasscodeInput.value = currentPasscode.toString() + emitFilledDots(currentPasscode.length) + + if (_filledDots.value == PASSCODE_LENGTH) { + if (_isPasscodeAlreadySet.value) { + if (passcodeRepository.getSavedPasscode() == createPasscode.toString()) { + emitOnPasscodeConfirmed(createPasscode.toString()) + createPasscode.clear() + } else { + emitOnPasscodeRejected() + // logic for retires can be written here + } + _currentPasscodeInput.value = "" + } else if (_activeStep.value == Step.Create) { + emitActiveStep(Step.Confirm) + emitFilledDots(0) + _currentPasscodeInput.value = "" + } else { + if (createPasscode.toString() == confirmPasscode.toString()) { + emitOnPasscodeConfirmed(confirmPasscode.toString()) + passcodeRepository.savePasscode(confirmPasscode.toString()) + _isPasscodeAlreadySet.value = true + resetData() + } else { + emitOnPasscodeRejected() + resetData() + } + _currentPasscodeInput.value = "" + } + } + } + + fun deleteKey() { + val currentPasscode = + if (_activeStep.value == Step.Create) createPasscode else confirmPasscode + + if (currentPasscode.isNotEmpty()) { + currentPasscode.deleteAt(currentPasscode.length - 1) + _currentPasscodeInput.value = currentPasscode.toString() + emitFilledDots(currentPasscode.length) + } + } + + + fun deleteAllKeys() { + if (_activeStep.value == Step.Create) { + createPasscode.clear() + } else { + confirmPasscode.clear() + } + _currentPasscodeInput.value = "" + emitFilledDots(0) + } + + fun restart() { + resetData() + _passcodeVisible.value = false + } +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/mifos/shared/App.ios.kt b/shared/src/iosMain/kotlin/com/mifos/shared/App.ios.kt new file mode 100644 index 0000000..7748eca --- /dev/null +++ b/shared/src/iosMain/kotlin/com/mifos/shared/App.ios.kt @@ -0,0 +1,25 @@ +package com.mifos.shared + +import androidx.compose.ui.window.ComposeUIViewController +import com.mifos.shared.utility.BioMetricUtil +import com.mifos.shared.component.PasscodeScreen +import com.mifos.shared.viewmodels.BiometricAuthorizationViewModel +import platform.UIKit.UIViewController + +fun MainViewController( + bioMetricUtil: BioMetricUtil, + biometricViewModel: BiometricAuthorizationViewModel): UIViewController = ComposeUIViewController { + PasscodeScreen( + onPasscodeConfirm = { + }, + onSkipButton = { + }, + onForgotButton = {}, + onPasscodeRejected = {}, + bioMetricUtil = bioMetricUtil, + biometricAuthorizationViewModel = biometricViewModel, + onBiometricAuthSuccess = { + }, + enableBiometric = true + ) +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/mifos/shared/CipherUtilImpl.ios.kt b/shared/src/iosMain/kotlin/com/mifos/shared/CipherUtilImpl.ios.kt new file mode 100644 index 0000000..e2f6e95 --- /dev/null +++ b/shared/src/iosMain/kotlin/com/mifos/shared/CipherUtilImpl.ios.kt @@ -0,0 +1,11 @@ +package com.mifos.shared + +import com.mifos.shared.CommonPublicKey + +actual data class CommonKeyPair(val publicKey: String?, val privateKey: String?) +actual interface CommonPublicKey { + val encoded: String? +} +actual class Crypto + +data class CommonPublicKeyImpl(override val encoded: String): CommonPublicKey \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/mifos/shared/Platform.ios.kt b/shared/src/iosMain/kotlin/com/mifos/shared/Platform.ios.kt new file mode 100644 index 0000000..a5dc26d --- /dev/null +++ b/shared/src/iosMain/kotlin/com/mifos/shared/Platform.ios.kt @@ -0,0 +1,10 @@ +package com.mifos.shared + +import platform.UIKit.UIDevice + +class IOSPlatform: Platform { +// override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion + override val name: String = "Ios" +} + +actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file