diff --git a/.gitignore b/.gitignore
index a674bf8..b371fcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
*.class
# Generated files
+.kotlin/
bin/
gen/
out/
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 65d4004..91df435 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,14 +6,13 @@ espressoCoreVersion = "3.6.0-alpha01"
gradle = "8.3.1"
hiltCompiler = "2.48.1"
hiltNavigationComposeVersion = "1.2.0"
-kotlin = "2.0.0"
+kotlin = "2.1.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"
@@ -31,6 +30,13 @@ appcompatVersion = "1.7.0"
testng = "6.9.6"
monitor = "1.6.1"
runnerVersion = "1.5.2"
+koin = "4.0.1-RC1"
+protobuf = "4.26.0"
+protobufPlugin = "0.9.4"
+kotlinxSerializationJson = "1.7.3"
+multiplatformSettings = "1.2.0"
+kotlinxCoroutines = "1.9.0"
+gradle-plugin = "8.2.0"
[libraries]
android-maven-gradle-plugin = { module = "com.github.dcendents:android-maven-gradle-plugin", version.ref = "androidMavenGradlePlugin" }
@@ -68,6 +74,36 @@ 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" }
+protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" }
+
+kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" }
+
+jb-kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
+
+kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerializationJson" }
+
+multiplatform-settings = { group = "com.russhwolf", name = "multiplatform-settings-no-arg", version.ref = "multiplatformSettings" }
+multiplatform-settings-coroutines = { group = "com.russhwolf", name = "multiplatform-settings-coroutines", version.ref = "multiplatformSettings" }
+multiplatform-settings-serialization = { group = "com.russhwolf", name = "multiplatform-settings-serialization", version.ref = "multiplatformSettings" }
+
+kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
+
+kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinxCoroutines" }
+
+protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
+
+koin-compose = { group = "io.insert-koin", name = "koin-compose", version.ref = "koin" }
+koin-compose-viewmodel = { group = "io.insert-koin", name = "koin-compose-viewmodel", version.ref = "koin" }
+
+android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "gradle-plugin" }
+gradle-plugin-development = { module = "org.gradle:gradle-plugin-development", version.ref = "gradle-plugin" }
+
+androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
+androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" }
+compose-gradlePlugin = { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" }
+
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
@@ -78,3 +114,10 @@ kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref =
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" }
+
+protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
+
+kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+
+mifospay-cmp-feature = { id = "mifospay.cmp.feature", version = "unspecified" }
diff --git a/mifos-passcode/.gitignore b/mifos-passcode/.gitignore
index 796b96d..42afabf 100644
--- a/mifos-passcode/.gitignore
+++ b/mifos-passcode/.gitignore
@@ -1 +1 @@
-/build
+/build
\ No newline at end of file
diff --git a/mifos-passcode/README.md b/mifos-passcode/README.md
new file mode 100644
index 0000000..41d45be
--- /dev/null
+++ b/mifos-passcode/README.md
@@ -0,0 +1,6 @@
+# :feature:passcode module
+## Dependency graph
+
+# :libs:mifos-passcode module
+## Dependency graph
+
diff --git a/mifos-passcode/build.gradle b/mifos-passcode/build.gradle
deleted file mode 100644
index 2fc17de..0000000
--- a/mifos-passcode/build.gradle
+++ /dev/null
@@ -1,90 +0,0 @@
-plugins {
- alias(libs.plugins.androidLibrary)
- alias(libs.plugins.kotlinAndroid)
-}
-
-ext {
- bintrayRepo = 'maven'
- bintrayName = 'mifos-passcode'
-
- publishedGroupId = 'com.mifos.mobile'
- libraryName = 'mifos-passcode'
- artifact = 'mifos-passcode' // artifact name and library name should be same.
-
- libraryDescription = 'A Library as feature of passcode'
-
- siteUrl = 'https://github.com/openMF/mobile-passcode'
- gitUrl = 'https://github.com/openMF/mobile-passcode.git'
-
- libraryVersion = '1.0.0'
-
- developerId = 'mifos'
- developerName = 'Mifos Initiative'
- developerEmail = 'info@mifos.org'
-
- licenseName = 'The Apache Software License, Version 2.0'
- licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- allLicenses = ["Apache-2.0"]
-}
-
-android {
- compileSdk 34
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
- }
- defaultConfig {
- namespace "com.mifos.mobile.passcode"
- minSdk 24
- targetSdk 34
- versionCode 2
- versionName "1.0.0"
- vectorDrawables.useSupportLibrary = true
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
- buildFeatures {
- viewBinding = true
- }
- kotlinOptions {
- jvmTarget = '17'
- }
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-
- implementation libs.androidx.appcompat
- implementation libs.material
-
- 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) {
- excludes = ['**/*.kt']
- options.addStringOption('Xdoclint:none', '-quiet')
- options.addStringOption('encoding', 'UTF-8')
- options.addStringOption('charSet', 'UTF-8')
-}
-
-repositories {
- mavenCentral()
-}
-// Used this article to release the library
-// https://inthecheesefactory.com/blog/how-to-upload-library-to-jcenter-maven-central-as-dependency/en
-
-// Add these line in local.properties to direct release on bintray
-//bintray.user=YOUR_BINTRAY_USERNAME
-//bintray.apikey=YOUR_BINTRAY_API_KEY
-//bintray.gpg.password=YOUR_GPG_PASSWORD
\ No newline at end of file
diff --git a/mifos-passcode/build.gradle.kts b/mifos-passcode/build.gradle.kts
new file mode 100644
index 0000000..d142fca
--- /dev/null
+++ b/mifos-passcode/build.gradle.kts
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+plugins {
+ alias(libs.plugins.mifospay.cmp.feature)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.protobuf)
+}
+
+android {
+ namespace = "com.mifos.library.passcode"
+}
+
+kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation(compose.ui)
+ implementation(compose.foundation)
+ implementation(compose.material3)
+ implementation(compose.materialIconsExtended)
+ implementation(compose.components.resources)
+ implementation(compose.components.uiToolingPreview)
+
+ implementation(libs.koin.compose.viewmodel)
+ implementation(libs.koin.compose)
+
+ implementation(libs.jb.kotlin.stdlib)
+ implementation(libs.kotlin.reflect)
+
+ api(libs.protobuf.kotlin.lite)
+ implementation(libs.kotlinx.serialization.core)
+
+ implementation(libs.multiplatform.settings)
+ implementation(libs.multiplatform.settings.serialization)
+ implementation(libs.multiplatform.settings.coroutines)
+
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.kotlinx.serialization.core)
+
+ implementation(project(":core:ui"))
+ implementation(project(":core:designsystem"))
+ implementation(project(":core:data"))
+
+ implementation(libs.findLibrary("jb.composeRuntime").get())
+ implementation(libs.findLibrary("jb.composeViewmodel").get())
+ implementation(libs.findLibrary("jb.lifecycleViewmodel").get())
+ implementation(libs.findLibrary("jb.lifecycleViewmodelSavedState").get())
+ implementation(libs.findLibrary("jb.savedstate").get())
+ implementation(libs.findLibrary("jb.bundle").get())
+ implementation(libs.findLibrary("jb.composeNavigation").get())
+ implementation(libs.findLibrary("kotlinx.collections.immutable").get())
+ }
+
+ androidMain.dependencies {
+ implementation(libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
+ implementation(libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
+ implementation(libs.findLibrary("androidx.tracing.ktx").get())
+
+ implementation(platform(libs.findLibrary("koin-bom").get()))
+ implementation(libs.findLibrary("koin-android").get())
+ implementation(libs.findLibrary("koin.androidx.compose").get())
+ implementation(libs.findLibrary("koin.android").get())
+ implementation(libs.findLibrary("koin.androidx.navigation").get())
+ implementation(libs.findLibrary("koin.androidx.compose").get())
+ implementation(libs.findLibrary("koin.core.viewmodel").get())
+ }
+
+ androidInstrumentedTest.dependencies {
+ implementation(libs.findLibrary("androidx.navigation.testing").get())
+ implementation(libs.findLibrary("androidx.compose.ui.test").get())
+ implementation(libs.findLibrary("androidx.lifecycle.runtimeTesting").get())
+ }
+
+ androidTest.dependencies {
+ implementation(libs.findLibrary("koin.test.junit4").get())
+ }
+
+ desktopMain.dependencies {
+ implementation(libs.kotlinx.coroutines.swing)
+ }
+ }
+}
+
+// Setup protobuf configuration, generating lite Java and Kotlin classes
+protobuf {
+ protoc {
+ artifact = libs.protobuf.protoc.get().toString()
+ }
+ generateProtoTasks {
+ all().forEach { task ->
+ task.builtins {
+ register("kotlin") {
+ option("lite")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/mifos-passcode/proguard-rules.pro b/mifos-passcode/proguard-rules.pro
deleted file mode 100644
index f1b4245..0000000
--- a/mifos-passcode/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/mifos-passcode/src/androidMain/AndroidManifest.xml b/mifos-passcode/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000..b9b046d
--- /dev/null
+++ b/mifos-passcode/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mifos-passcode/src/commonMain/composeResources/drawable/lib_mifos_passcode_mifos_logo.jpg b/mifos-passcode/src/commonMain/composeResources/drawable/lib_mifos_passcode_mifos_logo.jpg
new file mode 100644
index 0000000..a067a3c
Binary files /dev/null and b/mifos-passcode/src/commonMain/composeResources/drawable/lib_mifos_passcode_mifos_logo.jpg differ
diff --git a/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_black.ttf b/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_black.ttf
new file mode 100644
index 0000000..4340502
Binary files /dev/null and b/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_black.ttf differ
diff --git a/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_bold.ttf b/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_bold.ttf
new file mode 100644
index 0000000..016068b
Binary files /dev/null and b/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_bold.ttf differ
diff --git a/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_regular.ttf b/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_regular.ttf
new file mode 100644
index 0000000..bb2e887
Binary files /dev/null and b/mifos-passcode/src/commonMain/composeResources/font/lib_mifos_passcode_lato_regular.ttf differ
diff --git a/mifos-passcode/src/commonMain/composeResources/values/strings.xml b/mifos-passcode/src/commonMain/composeResources/values/strings.xml
new file mode 100644
index 0000000..4398748
--- /dev/null
+++ b/mifos-passcode/src/commonMain/composeResources/values/strings.xml
@@ -0,0 +1,34 @@
+
+
+
+ Passcode will be reset, are you sure?
+ Cancel
+ Yes
+ 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
+ Skip
+ Forgot Passcode, Login Manually
+ Try again
+ Passcode do not match!
+ Are you sure you want to exit?
+
\ No newline at end of file
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/PassCodeScreen.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/PassCodeScreen.kt
new file mode 100644
index 0000000..ba9cf7b
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/PassCodeScreen.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode
+
+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.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.VisibilityOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+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.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import kotlinx.coroutines.launch
+import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.koin.compose.viewmodel.koinViewModel
+import org.mifos.library.passcode.component.MifosIcon
+import org.mifos.library.passcode.component.PasscodeForgotButton
+import org.mifos.library.passcode.component.PasscodeHeader
+import org.mifos.library.passcode.component.PasscodeKeys
+import org.mifos.library.passcode.component.PasscodeMismatchedDialog
+import org.mifos.library.passcode.component.PasscodeSkipButton
+import org.mifos.library.passcode.component.PasscodeToolbar
+import org.mifos.library.passcode.theme.blueTint
+import org.mifos.library.passcode.utility.Constants.PASSCODE_LENGTH
+import org.mifos.library.passcode.utility.ShakeAnimation.performShakeAnimation
+import org.mifos.library.passcode.viewmodels.PasscodeAction
+import org.mifos.library.passcode.viewmodels.PasscodeEvent
+import org.mifos.library.passcode.viewmodels.PasscodeViewModel
+import org.mifospay.core.ui.utils.EventsEffect
+
+@Composable
+internal fun PasscodeScreen(
+ onForgotButton: () -> Unit,
+ onSkipButton: () -> Unit,
+ onPasscodeConfirm: (String) -> Unit,
+ onPasscodeRejected: () -> Unit,
+ modifier: Modifier = Modifier,
+ viewModel: PasscodeViewModel = koinViewModel(),
+) {
+ val scope = rememberCoroutineScope()
+ val state by viewModel.stateFlow.collectAsStateWithLifecycle()
+
+ val xShake = remember { Animatable(initialValue = 0.0F) }
+ var passcodeRejectedDialogVisible by remember { mutableStateOf(false) }
+
+ EventsEffect(viewModel) { event ->
+ when (event) {
+ is PasscodeEvent.PasscodeConfirmed -> {
+ onPasscodeConfirm(event.passcode)
+ }
+
+ is PasscodeEvent.PasscodeRejected -> {
+ passcodeRejectedDialogVisible = true
+ scope.launch {
+ performShakeAnimation(xShake)
+ }
+ onPasscodeRejected()
+ }
+ }
+ }
+
+ Scaffold(
+ modifier = modifier
+ .fillMaxSize(),
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White)
+ .padding(paddingValues),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ PasscodeToolbar(activeStep = state.activeStep, state.hasPasscode)
+
+ PasscodeSkipButton(
+ hasPassCode = state.hasPasscode,
+ onSkipButton = onSkipButton,
+ )
+
+ MifosIcon(modifier = Modifier.fillMaxWidth())
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp, bottom = 24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ PasscodeHeader(
+ activeStep = state.activeStep,
+ isPasscodeAlreadySet = state.hasPasscode,
+ )
+ PasscodeView(
+ restart = remember(viewModel) {
+ { viewModel.trySendAction(PasscodeAction.Restart) }
+ },
+ togglePasscodeVisibility = remember(viewModel) {
+ { viewModel.trySendAction(PasscodeAction.TogglePasscodeVisibility) }
+ },
+ filledDots = state.filledDots,
+ passcodeVisible = state.passcodeVisible,
+ currentPasscode = state.currentPasscodeInput,
+ passcodeRejectedDialogVisible = passcodeRejectedDialogVisible,
+ onDismissDialog = { passcodeRejectedDialogVisible = false },
+ xShake = xShake,
+ )
+ }
+
+ Spacer(modifier = Modifier.height(6.dp))
+
+ PasscodeKeys(
+ enterKey = remember(viewModel) {
+ { viewModel.trySendAction(PasscodeAction.EnterKey(it)) }
+ },
+ deleteKey = remember(viewModel) {
+ { viewModel.trySendAction(PasscodeAction.DeleteKey) }
+ },
+ deleteAllKeys = remember(viewModel) {
+ { viewModel.trySendAction(PasscodeAction.DeleteAllKeys) }
+ },
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ PasscodeForgotButton(
+ hasPassCode = state.hasPasscode,
+ onForgotButton = onForgotButton,
+ )
+ }
+ }
+}
+
+@Composable
+private fun PasscodeView(
+ restart: () -> Unit,
+ togglePasscodeVisibility: () -> Unit,
+ filledDots: Int,
+ passcodeVisible: Boolean,
+ currentPasscode: String,
+ passcodeRejectedDialogVisible: Boolean,
+ onDismissDialog: () -> Unit,
+ xShake: Animatable,
+ modifier: Modifier = Modifier,
+) {
+ PasscodeMismatchedDialog(
+ visible = passcodeRejectedDialogVisible,
+ onDismiss = {
+ onDismissDialog.invoke()
+ restart()
+ },
+ )
+
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Row(
+ modifier = Modifier.offset { IntOffset(xShake.value.toInt(), 0) },
+ 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,
+ modifier = Modifier.padding(start = 10.dp),
+ ) {
+ Icon(
+ imageVector = if (passcodeVisible) {
+ Icons.Filled.Visibility
+ } else {
+ Icons.Filled.VisibilityOff
+ },
+ contentDescription = null,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PasscodeScreenPreview() {
+ PasscodeScreen(
+ onForgotButton = {},
+ onSkipButton = {},
+ onPasscodeConfirm = {},
+ onPasscodeRejected = {},
+ )
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/PasscodeNavigation.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/PasscodeNavigation.kt
new file mode 100644
index 0000000..eab00ee
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/PasscodeNavigation.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+
+const val PASSCODE_SCREEN = "passcode_screen"
+
+fun NavGraphBuilder.passcodeRoute(
+ onForgotButton: () -> Unit,
+ onSkipButton: () -> Unit,
+ onPasscodeConfirm: (String) -> Unit,
+ onPasscodeRejected: () -> Unit,
+) {
+ composable(
+ route = PASSCODE_SCREEN,
+ ) {
+ PasscodeScreen(
+ onForgotButton = onForgotButton,
+ onSkipButton = onSkipButton,
+ onPasscodeConfirm = onPasscodeConfirm,
+ onPasscodeRejected = onPasscodeRejected,
+ )
+ }
+}
+
+fun NavController.navigateToPasscodeScreen(options: NavOptions? = null) {
+ navigate(PASSCODE_SCREEN, options)
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/MifosIcon.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/MifosIcon.kt
new file mode 100644
index 0000000..986fa2e
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/MifosIcon.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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 mobile_wallet.libs.mifos_passcode.generated.resources.Res
+import mobile_wallet.libs.mifos_passcode.generated.resources.lib_mifos_passcode_mifos_logo
+import org.jetbrains.compose.resources.painterResource
+
+@Composable
+internal fun MifosIcon(modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ Image(
+ modifier = Modifier.size(180.dp),
+ painter = painterResource(resource = Res.drawable.lib_mifos_passcode_mifos_logo),
+ contentDescription = null,
+ )
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeButton.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeButton.kt
new file mode 100644
index 0000000..bb7ecfd
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeButton.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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 mobile_wallet.libs.mifos_passcode.generated.resources.Res
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_login_manually
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_skip
+import org.jetbrains.compose.resources.stringResource
+import org.mifos.library.passcode.theme.forgotButtonStyle
+import org.mifos.library.passcode.theme.skipButtonStyle
+
+@Composable
+internal fun PasscodeSkipButton(
+ hasPassCode: Boolean,
+ onSkipButton: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ if (!hasPassCode) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(end = 16.dp),
+ horizontalArrangement = Arrangement.End,
+ ) {
+ TextButton(
+ onClick = { onSkipButton.invoke() },
+ ) {
+ Text(
+ text = stringResource(Res.string.library_mifos_passcode_skip),
+ style = skipButtonStyle,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+internal fun PasscodeForgotButton(
+ hasPassCode: Boolean,
+ onForgotButton: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ if (hasPassCode) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(end = 16.dp),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ TextButton(
+ onClick = { onForgotButton.invoke() },
+ ) {
+ Text(
+ text = stringResource(Res.string.library_mifos_passcode_login_manually),
+ style = forgotButtonStyle,
+ )
+ }
+ }
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeHeader.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeHeader.kt
new file mode 100644
index 0000000..81eaa19
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeHeader.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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.rememberTransition
+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 mobile_wallet.libs.mifos_passcode.generated.resources.Res
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_confirm_passcode
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_create_passcode
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_enter_your_passcode
+import org.jetbrains.compose.resources.stringResource
+import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mifos.library.passcode.utility.Step
+
+@Composable
+internal fun PasscodeHeader(
+ activeStep: Step,
+ isPasscodeAlreadySet: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val transitionState = remember { MutableTransitionState(activeStep) }
+ transitionState.targetState = activeStep
+
+ val transition: Transition = rememberTransition(
+ 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(Res.string.library_mifos_passcode_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(Res.string.library_mifos_passcode_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(Res.string.library_mifos_passcode_confirm_passcode),
+ style = TextStyle(fontSize = 20.sp),
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PasscodeHeaderPreview() {
+ PasscodeHeader(activeStep = Step.Create, isPasscodeAlreadySet = true)
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeKeys.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeKeys.kt
new file mode 100644
index 0000000..521b958
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeKeys.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.component
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+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.icons.automirrored.filled.Backspace
+import androidx.compose.material.icons.filled.Delete
+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.ui.Alignment
+import androidx.compose.ui.Modifier
+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 org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mifos.library.passcode.theme.PasscodeKeyButtonStyle
+import org.mifos.library.passcode.theme.blueTint
+
+@Composable
+internal 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.Default.Delete,
+ keyIconContentDescription = "Delete Passcode Key Button",
+ onClick = {
+ deleteKey()
+ },
+ onLongClick = {
+ deleteAllKeys()
+ },
+ )
+ }
+ }
+}
+
+@Composable
+internal 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(
+ onClick = {
+ onClick?.invoke(keyTitle)
+ },
+ onLongClick = {
+ onLongClick?.invoke()
+ },
+ modifier = Modifier
+ .padding(all = 4.dp),
+ ) {
+ if (keyIcon == null) {
+ Text(
+ text = keyTitle,
+ style = PasscodeKeyButtonStyle.copy(color = blueTint),
+ )
+ } else {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.Backspace,
+ contentDescription = keyIconContentDescription,
+ tint = blueTint,
+ )
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun CombinedClickableIconButton(
+ onClick: () -> Unit,
+ onLongClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ size: Dp = 48.dp,
+ enabled: Boolean = true,
+ content: @Composable () -> Unit,
+) {
+ Column(
+ modifier = modifier
+ .size(size = size)
+ .combinedClickable(
+ onClick = onClick,
+ onLongClick = onLongClick,
+ enabled = enabled,
+ role = Role.Button,
+ ),
+ 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
+private fun PasscodeKeysPreview() {
+ PasscodeKeys({}, {}, {})
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeMismatchedDialog.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeMismatchedDialog.kt
new file mode 100644
index 0000000..6cfe56b
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeMismatchedDialog.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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 mobile_wallet.libs.mifos_passcode.generated.resources.Res
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_do_not_match
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_try_again
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+internal fun PasscodeMismatchedDialog(
+ visible: Boolean,
+ onDismiss: () -> Unit,
+) {
+ if (visible) {
+ AlertDialog(
+ shape = MaterialTheme.shapes.large,
+ containerColor = Color.White,
+ title = {
+ Text(
+ text = stringResource(Res.string.library_mifos_passcode_do_not_match),
+ color = Color.Black,
+ )
+ },
+ confirmButton = {
+ TextButton(onClick = onDismiss) {
+ Text(
+ text = stringResource(Res.string.library_mifos_passcode_try_again),
+ color = Color.Black,
+ )
+ }
+ },
+ onDismissRequest = onDismiss,
+ )
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeStepIndicator.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeStepIndicator.kt
new file mode 100644
index 0000000..43d582a
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeStepIndicator.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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 org.mifos.library.passcode.theme.blueTint
+import org.mifos.library.passcode.utility.Constants.STEPS_COUNT
+import org.mifos.library.passcode.utility.Step
+
+@Composable
+internal fun PasscodeStepIndicator(
+ activeStep: Step,
+ modifier: Modifier = Modifier,
+) {
+ 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,
+ ),
+ )
+ }
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeToolbar.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeToolbar.kt
new file mode 100644
index 0000000..eebeb1b
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/component/PasscodeToolbar.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.component
+
+import androidx.compose.animation.AnimatedVisibility
+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 mobile_wallet.libs.mifos_passcode.generated.resources.Res
+import mobile_wallet.libs.mifos_passcode.generated.resources.lib_mifos_passcode_cancel
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_exit
+import mobile_wallet.libs.mifos_passcode.generated.resources.library_mifos_passcode_exit_message
+import org.jetbrains.compose.resources.stringResource
+import org.mifos.library.passcode.utility.Step
+
+@Composable
+internal fun PasscodeToolbar(
+ activeStep: Step,
+ hasPasscode: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ var exitWarningDialogVisible by remember { mutableStateOf(false) }
+
+ AnimatedVisibility(exitWarningDialogVisible) {
+ ExitWarningDialog(
+ visible = exitWarningDialogVisible,
+ onConfirm = {},
+ onDismiss = {
+ exitWarningDialogVisible = false
+ },
+ )
+ }
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ if (!hasPasscode) {
+ PasscodeStepIndicator(
+ activeStep = activeStep,
+ )
+ }
+ }
+}
+
+@Composable
+private fun ExitWarningDialog(
+ visible: Boolean,
+ onConfirm: () -> Unit,
+ onDismiss: () -> Unit,
+) {
+ if (visible) {
+ AlertDialog(
+ shape = MaterialTheme.shapes.large,
+ containerColor = Color.White,
+ title = {
+ Text(
+ text = stringResource(Res.string.library_mifos_passcode_exit_message),
+ color = Color.Black,
+ )
+ },
+ confirmButton = {
+ TextButton(onClick = onConfirm) {
+ Text(text = stringResource(Res.string.library_mifos_passcode_exit))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = onDismiss) {
+ Text(text = stringResource(Res.string.lib_mifos_passcode_cancel))
+ }
+ },
+ onDismissRequest = onDismiss,
+ )
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/di/PasscodeModule.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/di/PasscodeModule.kt
new file mode 100644
index 0000000..92ca27c
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/di/PasscodeModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.di
+
+import org.koin.core.module.dsl.viewModelOf
+import org.koin.dsl.module
+import org.mifos.library.passcode.viewmodels.PasscodeViewModel
+import proto.org.mifos.library.passcode.di.PasscodePreferenceModule
+
+val PasscodeModule = module {
+ includes(PasscodePreferenceModule)
+
+ viewModelOf(::PasscodeViewModel)
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Color.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Color.kt
new file mode 100644
index 0000000..87e3798
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Color.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.theme
+
+import androidx.compose.ui.graphics.Color
+
+internal val blueTint = Color(0xFF03A9F4)
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Font.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Font.kt
new file mode 100644
index 0000000..33528be
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Font.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+import mobile_wallet.libs.mifos_passcode.generated.resources.Res
+import mobile_wallet.libs.mifos_passcode.generated.resources.lib_mifos_passcode_lato_black
+import mobile_wallet.libs.mifos_passcode.generated.resources.lib_mifos_passcode_lato_bold
+import mobile_wallet.libs.mifos_passcode.generated.resources.lib_mifos_passcode_lato_regular
+import org.jetbrains.compose.resources.Font
+
+@Composable
+fun getFontFamily() = FontFamily(
+ Font(Res.font.lib_mifos_passcode_lato_regular, FontWeight.Normal, FontStyle.Normal),
+ Font(Res.font.lib_mifos_passcode_lato_bold, FontWeight.Bold, FontStyle.Normal),
+ Font(Res.font.lib_mifos_passcode_lato_black, FontWeight.Black, FontStyle.Normal),
+)
+
+@Composable
+fun getTypography(): Typography {
+ return Typography(
+ displayLarge = TextStyle(
+ fontFamily = getFontFamily(),
+ fontWeight = FontWeight.Black,
+ fontSize = 34.sp,
+ ),
+ )
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Theme.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Theme.kt
new file mode 100644
index 0000000..39b181f
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Theme.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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
+internal fun MifosPasscodeTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit,
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colorScheme = colors,
+ typography = getTypography(),
+ content = content,
+ )
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Type.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Type.kt
new file mode 100644
index 0000000..1325f60
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/theme/Type.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.theme
+
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+internal val PasscodeKeyButtonStyle = TextStyle(
+ fontWeight = FontWeight.Bold,
+ fontSize = 24.sp,
+)
+
+internal val skipButtonStyle = TextStyle(
+ color = blueTint,
+ fontSize = 20.sp,
+)
+
+internal val forgotButtonStyle = TextStyle(
+ color = blueTint,
+ fontSize = 14.sp,
+)
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/Constants.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/Constants.kt
new file mode 100644
index 0000000..4cb95a4
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/Constants.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.utility
+
+internal object Constants {
+ const val STEPS_COUNT = 2
+ const val PASSCODE_LENGTH = 4
+ const val VIBRATE_FEEDBACK_DURATION = 300L
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/ShakeAnimation.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/ShakeAnimation.kt
new file mode 100644
index 0000000..88e75cf
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/ShakeAnimation.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.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
+
+internal object ShakeAnimation {
+
+ fun CoroutineScope.performShakeAnimation(xShake: Animatable) {
+ launch {
+ xShake.animateTo(
+ targetValue = 0f,
+ animationSpec = keyframes {
+ durationMillis = 280
+ 0f at 0 using LinearOutSlowInEasing
+ 20f at 80 using LinearOutSlowInEasing
+ 20f at 120 using LinearOutSlowInEasing
+ 10f at 160 using LinearOutSlowInEasing
+ 10f at 200 using LinearOutSlowInEasing
+ 5f at 240 using LinearOutSlowInEasing
+ 0f at 280
+ },
+ )
+ }
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/Step.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/Step.kt
new file mode 100644
index 0000000..6c66457
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/utility/Step.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.utility
+
+enum class Step(var index: Int) {
+ Create(0),
+ Confirm(1),
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/viewmodels/PasscodeViewModel.kt b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/viewmodels/PasscodeViewModel.kt
new file mode 100644
index 0000000..b6c5cdd
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/org/mifos/library/passcode/viewmodels/PasscodeViewModel.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package org.mifos.library.passcode.viewmodels
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import org.mifos.library.passcode.utility.Step
+import org.mifospay.core.common.Parcelable
+import org.mifospay.core.common.Parcelize
+import org.mifospay.core.ui.utils.BaseViewModel
+import proto.org.mifos.library.passcode.data.PasscodeManager
+
+private const val KEY_STATE = "passcode_state"
+private const val PASSCODE_LENGTH = 4
+
+class PasscodeViewModel(
+ private val passcodeRepository: PasscodeManager,
+ savedStateHandle: SavedStateHandle,
+) : BaseViewModel(
+ initialState = savedStateHandle[KEY_STATE] ?: PasscodeState(),
+) {
+ private var createPasscode: StringBuilder = StringBuilder()
+ private var confirmPasscode: StringBuilder = StringBuilder()
+
+ init {
+ observePasscodeRepository()
+ }
+
+ private fun observePasscodeRepository() {
+ viewModelScope.launch {
+ passcodeRepository.hasPasscode.collect { hasPasscode ->
+ mutableStateFlow.update {
+ it.copy(
+ hasPasscode = hasPasscode,
+ isPasscodeAlreadySet = hasPasscode,
+ )
+ }
+ }
+ }
+ }
+
+ override fun handleAction(action: PasscodeAction) {
+ when (action) {
+ is PasscodeAction.EnterKey -> enterKey(action.key)
+ is PasscodeAction.DeleteKey -> deleteKey()
+ is PasscodeAction.DeleteAllKeys -> deleteAllKeys()
+ is PasscodeAction.TogglePasscodeVisibility -> togglePasscodeVisibility()
+ is PasscodeAction.Restart -> restart()
+ is PasscodeAction.Internal.ProcessCompletedPasscode -> processCompletedPasscode()
+ }
+ }
+
+ private fun enterKey(key: String) {
+ if (state.filledDots >= PASSCODE_LENGTH) return
+
+ val currentPasscode =
+ if (state.activeStep == Step.Create) createPasscode else confirmPasscode
+ currentPasscode.append(key)
+
+ mutableStateFlow.update {
+ it.copy(
+ currentPasscodeInput = currentPasscode.toString(),
+ filledDots = currentPasscode.length,
+ )
+ }
+
+ if (state.filledDots == PASSCODE_LENGTH) {
+ viewModelScope.launch {
+ sendAction(PasscodeAction.Internal.ProcessCompletedPasscode)
+ }
+ }
+ }
+
+ private fun deleteKey() {
+ val currentPasscode =
+ if (state.activeStep == Step.Create) createPasscode else confirmPasscode
+ if (currentPasscode.isNotEmpty()) {
+ currentPasscode.deleteAt(currentPasscode.length - 1)
+ mutableStateFlow.update {
+ it.copy(
+ currentPasscodeInput = currentPasscode.toString(),
+ filledDots = currentPasscode.length,
+ )
+ }
+ }
+ }
+
+ private fun deleteAllKeys() {
+ if (state.activeStep == Step.Create) {
+ createPasscode.clear()
+ } else {
+ confirmPasscode.clear()
+ }
+ mutableStateFlow.update {
+ it.copy(
+ currentPasscodeInput = "",
+ filledDots = 0,
+ )
+ }
+ }
+
+ private fun togglePasscodeVisibility() {
+ mutableStateFlow.update { it.copy(passcodeVisible = !it.passcodeVisible) }
+ }
+
+ private fun restart() {
+ resetState()
+ }
+
+ private fun processCompletedPasscode() {
+ viewModelScope.launch {
+ when {
+ state.isPasscodeAlreadySet -> validateExistingPasscode()
+ state.activeStep == Step.Create -> moveToConfirmStep()
+ else -> validateNewPasscode()
+ }
+ }
+ }
+
+ private suspend fun validateExistingPasscode() {
+ val savedPasscode = passcodeRepository.getPasscode.first()
+ if (savedPasscode == createPasscode.toString()) {
+ sendEvent(PasscodeEvent.PasscodeConfirmed(createPasscode.toString()))
+ createPasscode.clear()
+ } else {
+ sendEvent(PasscodeEvent.PasscodeRejected)
+ }
+ mutableStateFlow.update { it.copy(currentPasscodeInput = "") }
+ }
+
+ private fun moveToConfirmStep() {
+ mutableStateFlow.update {
+ it.copy(
+ activeStep = Step.Confirm,
+ filledDots = 0,
+ currentPasscodeInput = "",
+ )
+ }
+ }
+
+ private suspend fun validateNewPasscode() {
+ if (createPasscode.toString() == confirmPasscode.toString()) {
+ passcodeRepository.savePasscode(confirmPasscode.toString())
+ sendEvent(PasscodeEvent.PasscodeConfirmed(confirmPasscode.toString()))
+ resetState()
+ } else {
+ sendEvent(PasscodeEvent.PasscodeRejected)
+ resetState()
+ }
+ }
+
+ private fun resetState() {
+ mutableStateFlow.update {
+ PasscodeState(
+ hasPasscode = it.hasPasscode,
+ isPasscodeAlreadySet = it.isPasscodeAlreadySet,
+ )
+ }
+ createPasscode.clear()
+ confirmPasscode.clear()
+ }
+}
+
+@Parcelize
+data class PasscodeState(
+ val hasPasscode: Boolean = false,
+ val activeStep: Step = Step.Create,
+ val filledDots: Int = 0,
+ val passcodeVisible: Boolean = false,
+ val currentPasscodeInput: String = "",
+ val isPasscodeAlreadySet: Boolean = false,
+) : Parcelable
+
+sealed class PasscodeEvent {
+ data class PasscodeConfirmed(val passcode: String) : PasscodeEvent()
+ data object PasscodeRejected : PasscodeEvent()
+}
+
+sealed class PasscodeAction {
+ data class EnterKey(val key: String) : PasscodeAction()
+ data object DeleteKey : PasscodeAction()
+ data object DeleteAllKeys : PasscodeAction()
+ data object TogglePasscodeVisibility : PasscodeAction()
+ data object Restart : PasscodeAction()
+
+ sealed class Internal : PasscodeAction() {
+ data object ProcessCompletedPasscode : Internal()
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/data/PasscodeManager.kt b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/data/PasscodeManager.kt
new file mode 100644
index 0000000..2d6a3dc
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/data/PasscodeManager.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package proto.org.mifos.library.passcode.data
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+import proto.org.mifos.library.passcode.model.PasscodePreferencesProto
+
+class PasscodeManager(
+ private val source: PasscodePreferencesDataSource,
+ dispatcher: CoroutineDispatcher,
+) {
+ private val coroutineScope = CoroutineScope(dispatcher)
+
+ val getPasscode = source.passcode.stateIn(
+ scope = coroutineScope,
+ started = SharingStarted.Eagerly,
+ initialValue = "",
+ )
+
+ val hasPasscode = source.hasPasscode.stateIn(
+ scope = coroutineScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ suspend fun savePasscode(passcode: String) {
+ source.updatePasscodeSettings(
+ PasscodePreferencesProto(
+ passcode = passcode,
+ hasPasscode = passcode.isNotEmpty(),
+ ),
+ )
+ }
+
+ suspend fun clearPasscode() {
+ source.clearInfo()
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/data/PasscodePreferencesDataSource.kt b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/data/PasscodePreferencesDataSource.kt
new file mode 100644
index 0000000..9bf273f
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/data/PasscodePreferencesDataSource.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+@file:OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
+
+package proto.org.mifos.library.passcode.data
+
+import com.russhwolf.settings.ExperimentalSettingsApi
+import com.russhwolf.settings.Settings
+import com.russhwolf.settings.serialization.decodeValue
+import com.russhwolf.settings.serialization.decodeValueOrNull
+import com.russhwolf.settings.serialization.encodeValue
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.ExperimentalSerializationApi
+import proto.org.mifos.library.passcode.model.PasscodePreferencesProto
+
+private const val PASSCODE_INFO_KEY = "passcodeInfo"
+
+class PasscodePreferencesDataSource(
+ private val settings: Settings,
+ private val dispatcher: CoroutineDispatcher,
+) {
+ private val passcodeSettings = MutableStateFlow(
+ settings.decodeValue(
+ key = PASSCODE_INFO_KEY,
+ serializer = PasscodePreferencesProto.serializer(),
+ defaultValue = settings.decodeValueOrNull(
+ key = PASSCODE_INFO_KEY,
+ serializer = PasscodePreferencesProto.serializer(),
+ ) ?: PasscodePreferencesProto.DEFAULT,
+ ),
+ )
+
+ val passcode = passcodeSettings.map { it.passcode }
+ val hasPasscode = passcodeSettings.map { it.hasPasscode }
+
+ suspend fun updatePasscodeSettings(passcodePreferences: PasscodePreferencesProto) {
+ withContext(dispatcher) {
+ settings.putPasscodePreference(passcodePreferences)
+ passcodeSettings.value = passcodePreferences
+ }
+ }
+
+ suspend fun clearInfo() {
+ withContext(dispatcher) {
+ settings.remove(PASSCODE_INFO_KEY)
+ }
+ }
+}
+
+private fun Settings.putPasscodePreference(preference: PasscodePreferencesProto) {
+ encodeValue(
+ key = PASSCODE_INFO_KEY,
+ serializer = PasscodePreferencesProto.serializer(),
+ value = preference,
+ )
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/di/PreferenceModule.kt b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/di/PreferenceModule.kt
new file mode 100644
index 0000000..575121f
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/di/PreferenceModule.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package proto.org.mifos.library.passcode.di
+
+import com.russhwolf.settings.Settings
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+import org.mifospay.core.common.MifosDispatchers
+import proto.org.mifos.library.passcode.data.PasscodeManager
+import proto.org.mifos.library.passcode.data.PasscodePreferencesDataSource
+
+val PasscodePreferenceModule = module {
+ factory { Settings() }
+ factory { PasscodePreferencesDataSource(get(), get(named(MifosDispatchers.IO.name))) }
+ factory { PasscodeManager(get(), get(named(MifosDispatchers.Unconfined.name))) }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/model/PasscodePreferencesProto.kt b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/model/PasscodePreferencesProto.kt
new file mode 100644
index 0000000..0f7b38b
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/model/PasscodePreferencesProto.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
+ */
+package proto.org.mifos.library.passcode.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PasscodePreferencesProto(
+ val passcode: String,
+ val hasPasscode: Boolean,
+) {
+ companion object {
+ val DEFAULT = PasscodePreferencesProto(passcode = "", hasPasscode = false)
+ }
+}
diff --git a/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/passcode_preferences.proto b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/passcode_preferences.proto
new file mode 100644
index 0000000..7ff555c
--- /dev/null
+++ b/mifos-passcode/src/commonMain/kotlin/proto/org/mifos/library/passcode/passcode_preferences.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+
+option java_package = "org.mifos.library.passcode.proto";
+option java_multiple_files = true;
+
+message PasscodePreferences {
+ string passcode = 1;
+}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/AndroidManifest.xml b/mifos-passcode/src/main/AndroidManifest.xml
deleted file mode 100644
index 7f0c06c..0000000
--- a/mifos-passcode/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/BasePassCodeActivity.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/BasePassCodeActivity.kt
deleted file mode 100644
index f42d852..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/BasePassCodeActivity.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.mifos.mobile.passcode
-
-import androidx.activity.ComponentActivity
-import androidx.appcompat.app.AppCompatActivity
-import com.mifos.mobile.passcode.utils.ForegroundChecker
-import com.mifos.mobile.passcode.utils.ForegroundChecker.Companion.get
-
-/**
- * Created by dilpreet on 19/01/18.
- */
-abstract class BasePassCodeActivity : ComponentActivity(), ForegroundChecker.Listener {
- override fun onResume() {
- super.onResume()
- get()!!.addListener(this)
- get()!!.onActivityResumed()
- }
-
- override fun onPause() {
- super.onPause()
- get()!!.onActivityPaused()
- }
-
- override fun onBecameForeground() {
- MifosPassCodeActivity.startMifosPassCodeActivity(
- this, passCodeClass,
- false
- )
- }
-
- abstract val passCodeClass: Class<*>?
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthCallback.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthCallback.kt
deleted file mode 100644
index 9b423d6..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthCallback.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.mifos.mobile.passcode
-
-interface FpAuthCallback {
- fun onFpAuthSuccess()
- fun onCancel()
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthDialog.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthDialog.kt
deleted file mode 100644
index 76916c0..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthDialog.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.mifos.mobile.passcode
-
-import android.app.AlertDialog
-import android.content.Context
-import android.util.Log
-import android.view.LayoutInflater
-import android.widget.TextView
-import android.widget.Toast
-import androidx.appcompat.widget.AppCompatButton
-
-class FpAuthDialog(private val context: Context) {
- private var dialogTitle = context.getString(R.string.fp_dialog_title)
- private var dialogMessage = context.getString(R.string.fp_dialog_message)
- private var dialogCancelText = context.getString(R.string.fp_dialog_cancel)
-
- private var layoutInflater = LayoutInflater.from(context)
- private var dialogView = layoutInflater.inflate(R.layout.dialog_fingerprint_auth, null)
- private var tvDialogTitle = dialogView.findViewById(R.id.fingerprint_dialog_title)
- private var tvDialogMessage = dialogView.findViewById(R.id.fingerprint_dialog_message)
- private var tvDialogStatus = dialogView.findViewById(R.id.fingerprint_dialog_status)
- private var btnCancel = dialogView.findViewById(R.id.fingerprint_dialog_cancel)
-
- private var dialogBuilder = AlertDialog.Builder(context)
- private var dialog: AlertDialog? = null
-
- private var fpAuthCallback: FpAuthCallback = object : FpAuthCallback {
- override fun onFpAuthSuccess() {
- Toast.makeText(context, context.getString(R.string.authentication_successful), Toast.LENGTH_SHORT).show()
- }
-
- override fun onCancel() {
- Toast.makeText(context, context.getString(R.string.authentication_cancelled), Toast.LENGTH_SHORT).show()
- }
-
- }
- private var fpAuthHelper = FpAuthHelper(context, fpAuthCallback, this)
-
- fun setTitle(title: String): FpAuthDialog {
- dialogTitle = title
- return this
- }
-
- fun setTitle(resTitle: Int): FpAuthDialog {
- dialogTitle = context.resources.getString(resTitle)
- return this
- }
-
- fun setMessage(message: String): FpAuthDialog {
- dialogMessage = message
- return this
- }
-
- fun setMessage(resMessage: Int): FpAuthDialog {
- dialogMessage = context.resources.getString(resMessage)
- return this
- }
-
- fun setCancelText(cancelText: String): FpAuthDialog {
- dialogCancelText = cancelText
- return this
- }
-
- fun setCancelText(resCancelText: Int): FpAuthDialog {
- dialogCancelText = context.resources.getString(resCancelText)
- return this
- }
-
- fun setCallback(fpAuthCallback: FpAuthCallback): FpAuthDialog {
- this.fpAuthCallback = fpAuthCallback
- fpAuthHelper = FpAuthHelper(context, fpAuthCallback, this)
- return this
- }
-
- internal fun setStatusText(statusText: String) {
- tvDialogStatus.text = statusText
- }
-
- internal fun setStatusIcon(resIcon: Int) {
- tvDialogStatus.setCompoundDrawablesWithIntrinsicBounds(resIcon, 0, 0, 0)
- }
-
- fun dismiss() {
- fpAuthHelper.stopFpAuth()
- dialog!!.dismiss()
- }
-
- fun show() {
- if (!FpAuthSupport.checkAvailabiltyAndIfFingerprintRegistered(context)) {
- Log.e("FingerprintAuth", "Device not Suitable for Fingerprint Authentication")
- return
- }
- tvDialogTitle.text = dialogTitle
- tvDialogMessage.text = dialogMessage
- btnCancel.text = dialogCancelText
-
- dialog = dialogBuilder.setView(dialogView).create()
- dialog!!.setCancelable(false)
- dialog!!.show()
-
- fpAuthHelper.startFpAuth()
-
- btnCancel.setOnClickListener {
- fpAuthHelper.stopFpAuth()
- dialog!!.dismiss()
- fpAuthCallback.onCancel()
- }
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthHelper.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthHelper.kt
deleted file mode 100644
index 4f61947..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthHelper.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.mifos.mobile.passcode
-
-import android.content.Context
-import android.os.Handler
-import android.widget.Toast
-import androidx.core.hardware.fingerprint.FingerprintManagerCompat
-import androidx.core.os.CancellationSignal
-
-class FpAuthHelper(private val context: Context, private val fpAuthCallback: FpAuthCallback,
- private val fpAuthDialog: FpAuthDialog) {
- private var cancellationSignal: CancellationSignal? = null
- private var handler: Handler = Handler()
-
- companion object {
- const val AUTH_FAILED_DELAY: Long = 1000
- const val AUTH_SUCCESS_DELAY: Long = 500
- }
-
- private val startScanning = Runnable {
- fpAuthDialog.run {
- setStatusText(context.getString(R.string.touch_the_sensor))
- setStatusIcon(R.drawable.ic_fingerprint_blue_48dp)
- }
- }
-
- fun startFpAuth() {
- if (!FpAuthSupport.checkAvailabiltyAndIfFingerprintRegistered(context)) {
- return
- }
- if (cancellationSignal == null) {
- cancellationSignal = CancellationSignal()
- }
-
-// val fingerprintManager = FingerprintManagerCompat.from(context)
-//
-// fingerprintManager.authenticate(
-// null, 0, cancellationSignal,
-// object : FingerprintManagerCompat.AuthenticationCallback() {
-//
-// override fun onAuthenticationHelp(helpMsgId: Int, helpString: CharSequence?) {
-// super.onAuthenticationHelp(helpMsgId, helpString)
-// Toast.makeText(context, helpString, Toast.LENGTH_SHORT).show()
-// }
-//
-// override fun onAuthenticationFailed() {
-// super.onAuthenticationFailed()
-// fpAuthDialog.run {
-// setStatusIcon(R.drawable.ic_cancel_red_48dp)
-// setStatusText(context.getString(R.string.finger_print_not_recognized))
-// }
-// handler.postDelayed(startScanning, AUTH_FAILED_DELAY)
-// }
-//
-// override fun onAuthenticationSucceeded(result: FingerprintManagerCompat.AuthenticationResult?) {
-// super.onAuthenticationSucceeded(result)
-// fpAuthDialog.run {
-// setStatusIcon(R.drawable.ic_check_circle_green_48dp)
-// setStatusText(context.getString(R.string.authentication_successful))
-// }
-// handler.postDelayed({
-// fpAuthDialog.dismiss()
-// fpAuthCallback.onFpAuthSuccess()
-// }, AUTH_SUCCESS_DELAY)
-// }
-// }, null
-// )
- }
-
- fun stopFpAuth() {
- cancellationSignal?.run {
- cancel()
- cancellationSignal = null
- }
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthSupport.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthSupport.kt
deleted file mode 100644
index 5cf2118..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/FpAuthSupport.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.mifos.mobile.passcode
-
-import android.content.Context
-import android.os.Build
-import androidx.core.hardware.fingerprint.FingerprintManagerCompat
-
-object FpAuthSupport {
-
- @JvmStatic
- fun checkAvailability(context: Context): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
- FingerprintManagerCompat.from(context).isHardwareDetected
- }
-
- @JvmStatic
- fun isFingerprintRegistered(context: Context): Boolean {
- return FingerprintManagerCompat.from(context).hasEnrolledFingerprints()
- }
-
- @JvmStatic
- fun checkAvailabiltyAndIfFingerprintRegistered(context: Context): Boolean {
- val fingerprintManagerCompat = FingerprintManagerCompat.from(context)
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
- fingerprintManagerCompat.isHardwareDetected &&
- fingerprintManagerCompat.hasEnrolledFingerprints()
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/MifosPassCodeActivity.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/MifosPassCodeActivity.kt
deleted file mode 100644
index 13d0eb8..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/MifosPassCodeActivity.kt
+++ /dev/null
@@ -1,357 +0,0 @@
-package com.mifos.mobile.passcode
-
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.os.Bundle
-import android.view.View
-import android.view.animation.Animation
-import android.view.animation.AnimationUtils
-import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
-import com.mifos.mobile.passcode.FpAuthSupport.checkAvailabiltyAndIfFingerprintRegistered
-import com.mifos.mobile.passcode.databinding.ActivityPassCodeBinding
-import com.mifos.mobile.passcode.utils.EncryptionUtil
-import com.mifos.mobile.passcode.utils.EncryptionUtil.TYPE
-import com.mifos.mobile.passcode.utils.PassCodeConstants
-import com.mifos.mobile.passcode.utils.PassCodeNetworkChecker.isConnected
-import com.mifos.mobile.passcode.utils.PasscodePreferencesHelper
-
-abstract class MifosPassCodeActivity : AppCompatActivity(), PassCodeListener {
-
- var shakeAnimation: Animation? = null
- private var counter = 0
- private var isInitialScreen = false
- private var isPassCodeVerified = false
- private var strPassCodeEntered: String? = null
- private var passcodePreferencesHelper: PasscodePreferencesHelper? = null
- private var resetPasscode = false
- abstract val logo: Int
- abstract val fpDialogTitle: String?
- abstract fun startNextActivity()
- abstract fun startLoginActivity()
- abstract fun showToaster(view: View?, msg: Int)
-
- private lateinit var binding: ActivityPassCodeBinding
-
- @get:TYPE
- abstract val encryptionType: Int
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = ActivityPassCodeBinding.inflate(layoutInflater)
- setContentView(binding.root)
- shakeAnimation = AnimationUtils.loadAnimation(this, R.anim.shake)
- binding.ivLogo.setImageResource(logo)
- passcodePreferencesHelper = PasscodePreferencesHelper(this)
- isInitialScreen = intent.getBooleanExtra(
- PassCodeConstants.PASSCODE_INITIAL_LOGIN,
- false
- )
-
- //Show Prompt Dialog if device Support Fingerprint Authentication and has fingerprint
- // registered
- if (checkAvailabiltyAndIfFingerprintRegistered(this)
- && passcodePreferencesHelper!!.fingerprintEnableDialogState
- ) {
- val builder = AlertDialog.Builder(
- this,
- R.style.MaterialAlertDialogStyle
- )
- builder.setTitle(R.string.fingerprint)
- builder.setIcon(R.drawable.ic_fingerprint_blue_48dp)
- builder.setMessage(R.string.FingerprintEnableMessage)
- builder.setPositiveButton("Yes", object : DialogInterface.OnClickListener {
- override fun onClick(dialogInterface: DialogInterface, i: Int) {
- passcodePreferencesHelper!!.fingerprintEnableDialogState = false
- passcodePreferencesHelper!!.authType = "fpauth"
- fpDialogTitle?.let {
- FpAuthDialog(this@MifosPassCodeActivity)
- .setTitle(it)
- .setCallback(object : FpAuthCallback {
- override fun onFpAuthSuccess() {
- startHomeActivity()
- }
-
- override fun onCancel() {
- cancelFingerprintAuth()
- }
- }).show()
- }
- }
- })
- builder.setNegativeButton("No") { dialogInterface, i ->
- passcodePreferencesHelper!!.fingerprintEnableDialogState = false
- passcodePreferencesHelper!!.authType = "passcode"
- }
- val alertDialog = builder.create()
- alertDialog.setCancelable(false)
- alertDialog.show()
- }
- if (passcodePreferencesHelper!!.authType.equals("passcode", ignoreCase = true)) {
- resetPasscode = intent.getBooleanExtra(PassCodeConstants.RESET_PASSCODE, false)
- isPassCodeVerified = false
- strPassCodeEntered = ""
- if (!passcodePreferencesHelper!!.passCode!!.isEmpty()) {
- binding.btnSkip.visibility = View.GONE
- binding.btnSave.visibility = View.GONE
- binding.tvPasscode.visibility = View.GONE
- binding.btnForgotPasscode.visibility = View.VISIBLE
- //enabling passCodeListener only when user has already setup PassCode
- binding.pvPasscode.setPassCodeListener(this)
- }
- if (resetPasscode) {
- binding.btnSkip.visibility = View.GONE
- binding.btnSave.visibility = View.GONE
- binding.tvPasscode.visibility = View.VISIBLE
- binding.tvPasscode.text = getString(R.string.confirm_passcode)
- }
- }
- }
-
- private fun encryptPassCode(passCode: String): String? {
- @TYPE val type = encryptionType
- var encryptedPassCode: String? = null
- when (type) {
- EncryptionUtil.MOBILE_BANKING -> encryptedPassCode =
- EncryptionUtil.getMobileBankingHash(passCode)
-
- EncryptionUtil.ANDROID_CLIENT -> encryptedPassCode =
- EncryptionUtil.getAndroidClientHash(passCode)
-
- EncryptionUtil.FINERACT_CN -> encryptedPassCode =
- EncryptionUtil.getFineractCNHash(passCode)
-
- EncryptionUtil.DEFAULT -> encryptedPassCode = EncryptionUtil.getDefaultHash(passCode)
- }
- return encryptedPassCode
- }
-
- fun clearTokenPreferences() {
- passcodePreferencesHelper!!.clear()
- }
-
- fun skip(v: View?) {
- startHomeActivity()
- }
-
- /**
- * Saves the passcode by encrypting it which we got from [MifosPassCodeView]
- *
- * @param view Passcode View
- */
- fun savePassCode(view: View?) {
- if (isPassCodeLengthCorrect) {
- if (isPassCodeVerified) {
- if (strPassCodeEntered!!.compareTo(binding.pvPasscode.passcode) == 0) {
- passcodePreferencesHelper!!.savePassCode(encryptPassCode(binding.pvPasscode.passcode))
- startHomeActivity()
- } else {
- showToaster(binding.clRootview, R.string.passcode_does_not_match)
- binding.pvPasscode.clearPasscodeField()
- }
- } else {
- binding.btnSkip.visibility = View.INVISIBLE
- binding.btnSave.text = getString(R.string.save)
- binding.tvPasscode.text = getString(R.string.reenter_passcode)
- strPassCodeEntered = binding.pvPasscode.passcode
- binding.pvPasscode.clearPasscodeField()
- isPassCodeVerified = true
- }
- }
- }
-
- /**
- * It is a callback for [MifosPassCodeView], provides with the passcode entered by user
- *
- * @param passcode Passcode that is entered by user.
- */
- override fun passCodeEntered(passcode: String?) {
- if (!isInternetAvailable) {
- binding.pvPasscode.clearPasscodeField()
- return
- }
- if (counter == 3) {
- Toast.makeText(
- applicationContext, R.string.incorrect_passcode_more_than_three,
- Toast.LENGTH_SHORT
- ).show()
- clearTokenPreferences()
- startLoginActivity()
- return
- }
- if (isPassCodeLengthCorrect) {
- val passwordEntered = encryptPassCode(binding.pvPasscode.passcode)
- if (passcodePreferencesHelper!!.passCode == passwordEntered) {
- if (resetPasscode) {
- resetPasscode()
- return
- }
- startHomeActivity()
- } else {
- binding.pvPasscode.startAnimation(shakeAnimation)
- counter++
- binding.pvPasscode.clearPasscodeField()
- showToaster(binding.clRootview, R.string.incorrect_passcode)
- }
- }
- }
-
- fun forgotPassCode(v: View?) {
- clearTokenPreferences()
- startLoginActivity()
- }
-
- fun cancelFingerprintAuth() {
- clearTokenPreferences()
- startLoginActivity()
- finish()
- }
-
- private val isInternetAvailable: Boolean
- /**
- * Checks for internet availability
- *
- * @return Returns true if connected else returns false
- */
- get() = if (isConnected(this)) {
- true
- } else {
- showToaster(binding.clRootview, R.string.no_internet_connection)
- false
- }
-
- fun clickedOne(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.one))
- }
-
- fun clickedTwo(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.two))
- }
-
- fun clickedThree(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.three))
- }
-
- fun clickedFour(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.four))
- }
-
- fun clickedFive(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.five))
- }
-
- fun clickedSix(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.six))
- }
-
- fun clickedSeven(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.seven))
- }
-
- fun clickedEight(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.eight))
- }
-
- fun clickedNine(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.nine))
- }
-
- fun clickedZero(v: View?) {
- binding.pvPasscode.enterCode(getString(R.string.zero))
- }
-
- fun clickedBackSpace(v: View?) {
- binding.pvPasscode.backSpace()
- }
-
- /**
- * @param view PasscodeView that changes to text if it was hidden and vice a versa
- */
- fun visibilityChange(view: View?) {
- binding.pvPasscode.revertPassCodeVisibility()
- if (!binding.pvPasscode.passcodeVisible()) {
- binding.ivVisibility.setColorFilter(
- ContextCompat.getColor(
- this@MifosPassCodeActivity,
- R.color.light_grey
- )
- )
- } else {
- binding.ivVisibility.setColorFilter(
- ContextCompat.getColor(
- this@MifosPassCodeActivity,
- R.color.gray_dark
- )
- )
- }
- }
-
- private val isPassCodeLengthCorrect: Boolean
- /**
- * Checks whether passcode entered is of correct length
- *
- * @return Returns true if passcode lenght is 4 else shows message
- */
- private get() {
- if (binding.pvPasscode.passcode.length == 4) {
- return true
- }
- showToaster(binding.clRootview, R.string.error_passcode)
- return false
- }
-
- private fun startHomeActivity() {
- if (isInitialScreen) {
- startNextActivity()
- }
- finish()
- }
-
- override fun onBackPressed() {
- if (isInitialScreen) {
- super.onBackPressed()
- }
- }
-
- private fun resetPasscode() {
- resetPasscode = false
- binding.btnSkip.visibility = View.VISIBLE
- binding.btnSave.visibility = View.VISIBLE
- binding.tvPasscode.setText(R.string.passcode_setup)
- counter = 0
- binding.pvPasscode.clearPasscodeField()
- binding.pvPasscode.setPassCodeListener(null)
- passcodePreferencesHelper!!.clear()
- }
-
- override fun onResume() {
- super.onResume()
- if (passcodePreferencesHelper!!.authType.equals("fpauth", ignoreCase = true)) {
- FpAuthDialog(this@MifosPassCodeActivity)
- .setTitle(fpDialogTitle!!)
- .setCallback(object : FpAuthCallback {
- override fun onFpAuthSuccess() {
- startHomeActivity()
- }
-
- override fun onCancel() {
- cancelFingerprintAuth()
- }
- }).show()
- }
- }
-
- companion object {
- @JvmOverloads
- fun startMifosPassCodeActivity(
- context: Context, clazz: Class<*>?,
- isInitialLogin: Boolean = true
- ) {
- val intent = Intent(context, clazz)
- intent.putExtra(PassCodeConstants.PASSCODE_INITIAL_LOGIN, isInitialLogin)
- context.startActivity(intent)
- }
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/MifosPassCodeView.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/MifosPassCodeView.kt
deleted file mode 100644
index 279f108..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/MifosPassCodeView.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.mifos.mobile.passcode
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.util.AttributeSet
-import android.view.View
-
-/**
- * Created by dilpreet on 15/7/17.
- */
-class MifosPassCodeView : View {
- private var emptyCirclePaint: Paint? = null
- private var fillCirclePaint: Paint? = null
- private val PASSWORD_LENGTH = 4
- private var passwordList: MutableList? = null
- private var isPasscodeVisible = false
- private var passCodeListener: PassCodeListener? = null
-
- constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
- init(attrs)
- }
-
- constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
- context,
- attrs,
- defStyleAttr
- ) {
- init(attrs)
- }
-
- fun setPassCodeListener(passCodeListener: PassCodeListener?) {
- this.passCodeListener = passCodeListener
- }
-
- private fun init(attrs: AttributeSet?) {
- val attributes = context.obtainStyledAttributes(attrs, R.styleable.MifosPassCodeView)
- val defaultTextSize = (12 * context.resources.displayMetrics.density).toInt()
- val color = attributes.getColor(R.styleable.MifosPassCodeView_color, Color.WHITE)
- emptyCirclePaint = Paint()
- emptyCirclePaint!!.color = color
- emptyCirclePaint!!.isAntiAlias = true
- emptyCirclePaint!!.style = Paint.Style.STROKE
- emptyCirclePaint!!.strokeWidth = 1f
- fillCirclePaint = Paint()
- fillCirclePaint!!.color = color
- fillCirclePaint!!.isAntiAlias = true
- fillCirclePaint!!.textSize =
- attributes.getDimensionPixelSize(
- R.styleable.MifosPassCodeView_text_size,
- defaultTextSize
- )
- .toFloat()
- fillCirclePaint!!.style = Paint.Style.FILL
- attributes.recycle()
- passwordList = ArrayList()
- isPasscodeVisible = false
- }
-
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- val stackSize = passwordList!!.size
- var xPosition = width / (PASSWORD_LENGTH * 2)
- for (i in 1..PASSWORD_LENGTH) {
- if (stackSize >= i) {
- if (!isPasscodeVisible) {
- canvas.drawCircle(
- xPosition.toFloat(),
- (height / 2).toFloat(),
- 8f,
- fillCirclePaint!!
- )
- } else {
- canvas.drawText(
- passwordList!![i - 1], xPosition.toFloat(), (height / 2 +
- height / 8).toFloat(), fillCirclePaint!!
- )
- }
- } else {
- canvas.drawCircle(
- xPosition.toFloat(),
- (height / 2).toFloat(),
- 8f,
- emptyCirclePaint!!
- )
- }
- xPosition += width / PASSWORD_LENGTH
- }
- }
-
- fun enterCode(character: String) {
- if (passwordList!!.size < PASSWORD_LENGTH) {
- passwordList!!.add(character)
- invalidate()
- }
- if (passwordList!!.size == PASSWORD_LENGTH && passCodeListener != null) {
- passCodeListener!!.passCodeEntered(passcode)
- }
- }
-
- val passcode: String
- get() {
- val builder = StringBuilder()
- for (character in passwordList!!) {
- builder.append(character)
- }
- return builder.toString()
- }
-
- fun clearPasscodeField() {
- passwordList!!.clear()
- invalidate()
- }
-
- fun backSpace() {
- if (passwordList!!.size > 0) {
- passwordList!!.removeAt(passwordList!!.size - 1)
- invalidate()
- }
- }
-
- fun revertPassCodeVisibility() {
- isPasscodeVisible = !isPasscodeVisible
- invalidate()
- }
-
- fun passcodeVisible(): Boolean {
- return isPasscodeVisible
- }
-
-
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/PassCodeListener.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/PassCodeListener.kt
deleted file mode 100644
index 2ac613c..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/PassCodeListener.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.mifos.mobile.passcode
-
-interface PassCodeListener {
- fun passCodeEntered(passcode: String?)
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/EncryptionUtil.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/EncryptionUtil.kt
deleted file mode 100644
index af2906d..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/EncryptionUtil.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.mifos.mobile.passcode.utils
-
-import android.util.Log
-import androidx.annotation.IntDef
-
-object EncryptionUtil {
-
- const val DEFAULT = 1
- const val MOBILE_BANKING = 2
- const val ANDROID_CLIENT = 3
- const val FINERACT_CN = 4
-
-
- @IntDef(DEFAULT, MOBILE_BANKING, ANDROID_CLIENT, FINERACT_CN)
- @Retention(AnnotationRetention.SOURCE)
- annotation class TYPE
-
- init {
- try {
- System.loadLibrary("encryption")
- } catch (e: UnsatisfiedLinkError) {
- Log.e("LoadJniLib", "Error: Could not load native library: ${e.message}")
- }
- }
-
- external fun getPassCodeHash(passcode: String): String
-
- fun getDefaultHash(passCode: String): String {
- return getPassCodeHash(passCode)
- }
-
- fun getMobileBankingHash(passCode: String): String {
- return getPassCodeHash(passCode)
- }
-
- fun getAndroidClientHash(passCode: String): String {
- return getPassCodeHash(passCode)
- }
-
- fun getFineractCNHash(passCode: String): String {
- return getPassCodeHash(passCode)
- }
-}
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/ForegroundChecker.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/ForegroundChecker.kt
deleted file mode 100644
index c88942c..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/ForegroundChecker.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.mifos.mobile.passcode.utils
-
-import android.content.Context
-import android.os.Handler
-import com.mifos.mobile.passcode.utils.ForegroundChecker
-
-/**
- * Created by dilpreet on 18/7/17.
- */
-class ForegroundChecker private constructor(context: Context) {
- interface Listener {
- fun onBecameForeground()
- }
-
- /**
- * Returns True if application is in foreground
- * @return State of Application
- */
- var isForeground = false
- private set
- private var paused = true
- private val handler = Handler()
- private var listener: Listener? = null
- private var check: Runnable? = null
- private var backgroundTimeStart: Long
- private val passcodePreferencesHelper: PasscodePreferencesHelper
-
- /**
- * Initializes [ForegroundChecker]
- * @param context Application Context
- */
- init {
- backgroundTimeStart = -1
- passcodePreferencesHelper = PasscodePreferencesHelper(context)
- }
-
- val isBackground: Boolean
- /**
- * Returns True if application is in background
- * @return State of Application
- */
- get() = !isForeground
-
- fun addListener(listener: Listener?) {
- this.listener = listener
- }
-
- /**
- * It calls `onBecameForeground()` if `secondsInBackground` >=
- * `MIN_BACKGROUND_THRESHOLD`
- */
- fun onActivityResumed() {
- paused = false
- val wasBackground = !isForeground
- isForeground = true
- if (check != null) handler.removeCallbacks(check!!)
- if (wasBackground) {
- val secondsInBackground = ((System.currentTimeMillis() - backgroundTimeStart) /
- 1000).toInt()
- if (backgroundTimeStart != -1L && secondsInBackground >= MIN_BACKGROUND_THRESHOLD && listener != null && passcodePreferencesHelper.passCode?.isEmpty() == true) {
- listener!!.onBecameForeground()
- }
- }
- }
-
- /**
- * It executes a Handler after `CHECK_DELAY` and then sets `foreground` to false
- */
- fun onActivityPaused() {
- paused = true
- if (check != null) handler.removeCallbacks(check!!)
- handler.postDelayed(object : Runnable {
- override fun run() {
- if (isForeground && paused) {
- isForeground = false
- backgroundTimeStart = System.currentTimeMillis()
- }
- }
- }.also { check = it }, CHECK_DELAY)
- }
-
- companion object {
- const val CHECK_DELAY: Long = 500
- const val MIN_BACKGROUND_THRESHOLD = 60
- val TAG = ForegroundChecker::class.java.name
- private var instance: ForegroundChecker? = null
-
- /**
- * Used to initialize `instance` of [ForegroundChecker]
- * @param context Application Content
- * @return Instance of [ForegroundChecker]
- */
- @JvmStatic
- fun init(context: Context): ForegroundChecker? {
- if (instance == null) {
- instance = ForegroundChecker(context)
- }
- return instance
- }
-
- /**
- * Provides instance of [ForegroundChecker]
- * @return Instance of [ForegroundChecker]
- */
- @JvmStatic
- fun get(): ForegroundChecker? {
- return instance
- }
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PassCodeConstants.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PassCodeConstants.kt
deleted file mode 100644
index db51517..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PassCodeConstants.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.mifos.mobile.passcode.utils
-
-/**
- * Created by dilpreet on 19/01/18.
- */
-object PassCodeConstants {
- const val PASSCODE_INITIAL_LOGIN = "initial_login"
- const val RESET_PASSCODE = "reset_passcode"
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PassCodeNetworkChecker.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PassCodeNetworkChecker.kt
deleted file mode 100644
index 2dd4433..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PassCodeNetworkChecker.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.mifos.mobile.passcode.utils
-
-import android.content.Context
-import android.net.ConnectivityManager
-import android.net.NetworkInfo
-import android.telephony.TelephonyManager
-
-/**
- * Created by rishabhkhanna on 07/03/17.
- */
-object PassCodeNetworkChecker {
- /**
- * Get the network info
- *
- * @param context Context
- * @return NetworkInfo
- */
- fun getNetworkInfo(context: Context): NetworkInfo? {
- val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
- return cm.activeNetworkInfo
- }
-
- /**
- * Check if there is any connectivity
- *
- * @param context Context
- * @return state of network
- */
- @JvmStatic
- fun isConnected(context: Context): Boolean {
- val info = getNetworkInfo(context)
- return info != null && info.isConnected
- }
-
- /**
- * Check if there is any connectivity to a Wifi network
- *
- * @param context Context
- * @return state if wifi connection
- */
- fun isConnectedWifi(context: Context): Boolean {
- val info = getNetworkInfo(context)
- return info != null && info.isConnected && info.type == ConnectivityManager.TYPE_WIFI
- }
-
- /**
- * Check if there is any connectivity to a mobile network
- *
- * @param context Context
- * @return mobile connected to network or not
- */
- fun isConnectedMobile(context: Context): Boolean {
- val info = getNetworkInfo(context)
- return info != null && info.isConnected && info.type == ConnectivityManager.TYPE_MOBILE
- }
-
- /**
- * Check if there is fast connectivity
- *
- * @param context Context
- * @return connection is fast or not
- */
- fun isConnectedFast(context: Context): Boolean {
- val info = getNetworkInfo(context)
- return info != null && info.isConnected &&
- isConnectionFast(info.type, info.subtype)
- }
-
- /**
- * Check if the connection is fast
- *
- * @param type Type of connection
- * @param subType SubType of Connection
- * @return connection is fast or not
- */
- fun isConnectionFast(type: Int, subType: Int): Boolean {
- return if (type == ConnectivityManager.TYPE_WIFI) {
- true
- } else if (type == ConnectivityManager.TYPE_MOBILE) {
- when (subType) {
- TelephonyManager.NETWORK_TYPE_1xRTT -> false // ~ 50-100 kbps
- TelephonyManager.NETWORK_TYPE_CDMA -> false // ~ 14-64 kbps
- TelephonyManager.NETWORK_TYPE_EDGE -> false // ~ 50-100 kbps
- TelephonyManager.NETWORK_TYPE_EVDO_0 -> true // ~ 400-1000 kbps
- TelephonyManager.NETWORK_TYPE_EVDO_A -> true // ~ 600-1400 kbps
- TelephonyManager.NETWORK_TYPE_GPRS -> false // ~ 100 kbps
- TelephonyManager.NETWORK_TYPE_HSDPA -> true // ~ 2-14 Mbps
- TelephonyManager.NETWORK_TYPE_HSPA -> true // ~ 700-1700 kbps
- TelephonyManager.NETWORK_TYPE_HSUPA -> true // ~ 1-23 Mbps
- TelephonyManager.NETWORK_TYPE_UMTS -> true // ~ 400-7000 kbps
- TelephonyManager.NETWORK_TYPE_EHRPD -> true // ~ 1-2 Mbps
- TelephonyManager.NETWORK_TYPE_EVDO_B -> true // ~ 5 Mbps
- TelephonyManager.NETWORK_TYPE_HSPAP -> true // ~ 10-20 Mbps
- TelephonyManager.NETWORK_TYPE_IDEN -> false // ~25 kbps
- TelephonyManager.NETWORK_TYPE_LTE -> true // ~ 10+ Mbps
- TelephonyManager.NETWORK_TYPE_UNKNOWN -> false
- else -> false
- }
- } else {
- false
- }
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PasscodePreferencesHelper.kt b/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PasscodePreferencesHelper.kt
deleted file mode 100644
index 372f263..0000000
--- a/mifos-passcode/src/main/java/com/mifos/mobile/passcode/utils/PasscodePreferencesHelper.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.mifos.mobile.passcode.utils
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.preference.PreferenceManager
-
-/**
- * Created by dilpreet on 19/01/18.
- */
-class PasscodePreferencesHelper(context: Context?) {
- private val sharedPreferences: SharedPreferences
- private val TOKEN = "preferences_mifos_passcode_string"
- private val FINGERPRINTENABLER = "fingerprint_enable_dialog"
- private val AUTHTYPE = "auth_type"
-
- init {
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
- }
-
- fun savePassCode(token: String?) {
- sharedPreferences.edit().putString(TOKEN, token).apply()
- }
-
- val passCode: String?
- get() = sharedPreferences.getString(TOKEN, "")
- var fingerprintEnableDialogState: Boolean
- get() = sharedPreferences.getBoolean(FINGERPRINTENABLER, true)
- set(show) {
- sharedPreferences.edit().putBoolean(FINGERPRINTENABLER, show).apply()
- }
- var authType: String?
- get() = sharedPreferences.getString(AUTHTYPE, "")
- set(authType) {
- sharedPreferences.edit().putString(AUTHTYPE, authType).apply()
- }
-
- fun clear() {
- sharedPreferences.edit().clear().apply()
- }
-}
\ No newline at end of file
diff --git a/mifos-passcode/src/main/jniLibs/arm64-v8a/libencryption.so b/mifos-passcode/src/main/jniLibs/arm64-v8a/libencryption.so
deleted file mode 100644
index 3beba0d..0000000
Binary files a/mifos-passcode/src/main/jniLibs/arm64-v8a/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/jniLibs/armeabi-v7a/libencryption.so b/mifos-passcode/src/main/jniLibs/armeabi-v7a/libencryption.so
deleted file mode 100644
index f7bb7ef..0000000
Binary files a/mifos-passcode/src/main/jniLibs/armeabi-v7a/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/jniLibs/armeabi/libencryption.so b/mifos-passcode/src/main/jniLibs/armeabi/libencryption.so
deleted file mode 100644
index 64484d8..0000000
Binary files a/mifos-passcode/src/main/jniLibs/armeabi/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/jniLibs/mips/libencryption.so b/mifos-passcode/src/main/jniLibs/mips/libencryption.so
deleted file mode 100644
index 35f5e0b..0000000
Binary files a/mifos-passcode/src/main/jniLibs/mips/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/jniLibs/mips64/libencryption.so b/mifos-passcode/src/main/jniLibs/mips64/libencryption.so
deleted file mode 100644
index 922e2b1..0000000
Binary files a/mifos-passcode/src/main/jniLibs/mips64/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/jniLibs/x86/libencryption.so b/mifos-passcode/src/main/jniLibs/x86/libencryption.so
deleted file mode 100644
index 7e6ab86..0000000
Binary files a/mifos-passcode/src/main/jniLibs/x86/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/jniLibs/x86_64/libencryption.so b/mifos-passcode/src/main/jniLibs/x86_64/libencryption.so
deleted file mode 100644
index 0e3461f..0000000
Binary files a/mifos-passcode/src/main/jniLibs/x86_64/libencryption.so and /dev/null differ
diff --git a/mifos-passcode/src/main/res/anim/shake.xml b/mifos-passcode/src/main/res/anim/shake.xml
deleted file mode 100644
index 1b5fd73..0000000
--- a/mifos-passcode/src/main/res/anim/shake.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/drawable/ic_backspace_48px.xml b/mifos-passcode/src/main/res/drawable/ic_backspace_48px.xml
deleted file mode 100644
index 6be920a..0000000
--- a/mifos-passcode/src/main/res/drawable/ic_backspace_48px.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/mifos-passcode/src/main/res/drawable/ic_cancel_red_48dp.xml b/mifos-passcode/src/main/res/drawable/ic_cancel_red_48dp.xml
deleted file mode 100644
index 73beaa6..0000000
--- a/mifos-passcode/src/main/res/drawable/ic_cancel_red_48dp.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/drawable/ic_check_circle_green_48dp.xml b/mifos-passcode/src/main/res/drawable/ic_check_circle_green_48dp.xml
deleted file mode 100644
index fb0884a..0000000
--- a/mifos-passcode/src/main/res/drawable/ic_check_circle_green_48dp.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/drawable/ic_fingerprint_blue_48dp.xml b/mifos-passcode/src/main/res/drawable/ic_fingerprint_blue_48dp.xml
deleted file mode 100644
index 7a75ae6..0000000
--- a/mifos-passcode/src/main/res/drawable/ic_fingerprint_blue_48dp.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/drawable/ic_visibility_48px.xml b/mifos-passcode/src/main/res/drawable/ic_visibility_48px.xml
deleted file mode 100644
index 44f9d4f..0000000
--- a/mifos-passcode/src/main/res/drawable/ic_visibility_48px.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/mifos-passcode/src/main/res/layout/activity_pass_code.xml b/mifos-passcode/src/main/res/layout/activity_pass_code.xml
deleted file mode 100644
index 6b15861..0000000
--- a/mifos-passcode/src/main/res/layout/activity_pass_code.xml
+++ /dev/null
@@ -1,226 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mifos-passcode/src/main/res/layout/dialog_fingerprint_auth.xml b/mifos-passcode/src/main/res/layout/dialog_fingerprint_auth.xml
deleted file mode 100644
index 4d835da..0000000
--- a/mifos-passcode/src/main/res/layout/dialog_fingerprint_auth.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mifos-passcode/src/main/res/values/attr.xml b/mifos-passcode/src/main/res/values/attr.xml
deleted file mode 100644
index 612c58d..0000000
--- a/mifos-passcode/src/main/res/values/attr.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/values/colors.xml b/mifos-passcode/src/main/res/values/colors.xml
deleted file mode 100644
index c5936fe..0000000
--- a/mifos-passcode/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- #BB666666
- #03A9F4
- #ffd1d1d1
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/values/dimens.xml b/mifos-passcode/src/main/res/values/dimens.xml
deleted file mode 100644
index ac4778a..0000000
--- a/mifos-passcode/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
- 16dp
- 72dp
- 56dp
- 20dp
- 8dp
- 20sp
- 16dp
- 24dp
- 20dp
- 16sp
- 28dp
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/values/strings.xml b/mifos-passcode/src/main/res/values/strings.xml
deleted file mode 100644
index 8fadea8..0000000
--- a/mifos-passcode/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
- Mobile-Passcode
- Enter 4 digit Passcode
- Passcode should be of 4 digit
- Incorrect Passcode
- You have entered incorrect Passcode more than 3 times
- Skip
- Save
- Proceed
- Setup a passcode to login
- Please re-enter your passcode
- Passcode does not match.
- Forgot passcode, login manually
- No Internet Connection
- Confirm your passcode
-
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 0
- MainActivity
-
- Touch the Sensor
- Use your fingerprint to access the app
- Login to Mifos
- Use password instead
- Touch the Sensor
- Fingerprint Not Recognized
- Authentication Successful
- Authentication Cancelled
- Fingerprint
- Do you want to enable Fingerprint Authentication?
-
\ No newline at end of file
diff --git a/mifos-passcode/src/main/res/values/styles.xml b/mifos-passcode/src/main/res/values/styles.xml
deleted file mode 100644
index e52906d..0000000
--- a/mifos-passcode/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mifos-passcode/src/test/java/com/mifos/passcode/ExampleUnitTest.java b/mifos-passcode/src/test/java/com/mifos/passcode/ExampleUnitTest.java
deleted file mode 100644
index d7157ff..0000000
--- a/mifos-passcode/src/test/java/com/mifos/passcode/ExampleUnitTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.mifos.passcode;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void additionisCorrect() throws Exception {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 791ecb3..80b19d3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -12,6 +12,7 @@ dependencyResolutionManagement {
mavenCentral()
}
}
+
include ':androidApp'
include ':mifos-passcode'
include ':compose'