Skip to content

Commit b2e9e2d

Browse files
Reimplemented the whole passcode handeling logic implemented and the ability to change passcode. (#56)
* Refactor passcode management and introduce `PasscodeManager` * Introduced `PasscodeManager`, `PasscodeStorageAdapter`, and `PasscodeAction` to provide a more robust state management system for passcode operations. * Renamed `PasscodeEvent` to `PasscodeEvents` and `PasscodeState` to `PasscodeStates` to avoid naming collisions with new manager components. * Added a `Change` step to the passcode utility and updated `PasscodeHeader` to support the change passcode flow. * Updated `PasscodeSaver` with `resetPasscode` functionality and renamed callback parameters for clarity (`saveNewPasscode`, `clearCurrentPasscode`). * Modified `SampleAppNavigation` and `HomeScreen` to support the "Change Passcode" feature. * Added `.claude` to `.gitignore`. * Imported `VisibilityOff` icon for use in passcode components. * Refactor passcode management and UI configuration * Replaced `PasscodeSaver` and `PasscodeRepository` with a new `PasscodeManager` and `PasscodeStorageAdapter` for improved state handling and persistence. * Replaced custom `PreferenceDataStore` with `com.russhwolf.settings.Settings` throughout the shared module. * Introduced `PasscodeScreenConfig.kt` containing data classes (`PasscodeAppearanceConfig`, `PasscodeLogoConfig`, etc.) to provide granular styling control for the passcode screen. * Updated `PasscodeScreen` to use the new configuration classes and collect state from `PasscodeManager` via `collectAsStateWithLifecycle`. * Refactored `PasscodeStep` and related logic to support a clearer state machine (Unset, Enter, Create, Confirm, ChangeVerify). * Updated `SampleAppNavigation` and `HomeScreen` to integrate with the new `PasscodeManager` and action-based event handling. * Removed unused components and files, including `PasscodeRepository.kt` and `PreferenceDataStore.kt`. * Improved `PasscodeHeader` and button components to respond to the new `PasscodeStep` architecture. * Clean up passcode logic and refactor build configurations * Removed deprecated `PasscodeSaver.kt` and `Step.kt` in favor of the new `PasscodeManager` implementation. * Updated `PasscodeScreen.kt` to handle `PasscodeEvent` updates and improved code formatting. * Commented out `PasscodeStepIndicator` and `PasscodeToolbar` pending compatibility fixes with the new passcode logic. * Added copyright headers and improved formatting in `PasscodeStorageAdapterImpl.kt` and `PasscodeManager.kt`. * Standardized trailing commas and whitespace across multiple files in `cmp-sample-shared`. * Removed unused imports in `PasscodeScreenConfig.kt`, `PasscodeHeader.kt`, and `SampleAppNavigation.kt`. * Update passcode change flow and state management * Added `confirm_old_passcode` string resource and integrated it into `PasscodeHeader` for the `ChangeVerify` step. * Implemented unique animation transitions (offset, alpha, and scale) for the `ChangeVerify` header in `PasscodeHeader`. * Renamed `clearState` to `clearStates` in `PasscodeManager` and ensured consistent calls across passcode handling methods. * Fixed passcode builder logic in `PasscodeManager` to properly clear `creationPasscodeBuilder` and `finalConfirmationPasscodeBuilder` during the creation and verification flows. * Updated `handleChangeVerifyPasscode` to transition to the `Create` step upon successful verification of the old passcode. * Update .gitignore and PULL_REQUEST_TEMPLATE.md * Removed `.claude` from `.gitignore`. * Removed Jira ticket references and links from `PULL_REQUEST_TEMPLATE.md`. * Updated the static analysis check instructions in the PR template to remove the reference to `ci-prepush.sh`.
1 parent 8c311a9 commit b2e9e2d

20 files changed

Lines changed: 867 additions & 846 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
Fixes - [Jira-#Issue_Number](https://mifosforge.jira.com/browse/MIFOSAC-)
2-
3-
Didn't create a Jira ticket, click [here](https://mifosforge.jira.com/jira/software/c/projects/MIFOSAC/issues/) to create new.
4-
51
Please Add Screenshots If there are any UI changes.
62

73
| Before | After |
@@ -10,6 +6,6 @@ Please Add Screenshots If there are any UI changes.
106

117
Please make sure these boxes are checked before submitting your pull request - thanks!
128

13-
- [ ] Run the static analysis check `./gradlew check` or `ci-prepush.sh` to make sure you didn't break anything
9+
- [ ] Run the static analysis check `./gradlew check` to make sure you didn't break anything
1410

1511
- [ ] If you have multiple commits please combine them into one commit by squashing them.

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/App.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,31 @@ import androidx.compose.material3.MaterialTheme
1313
import androidx.compose.runtime.Composable
1414
import cmp.sample.shared.chooseAuthOption.ChooseAuthOptionRepository
1515
import cmp.sample.shared.chooseAuthOption.ChooseAuthOptionScreenViewmodel
16-
import cmp.sample.shared.kmpDataStore.PreferenceDataStoreImpl
1716
import cmp.sample.shared.navigation.SampleAppNavigation
18-
import cmp.sample.shared.passcode.PasscodeRepository
1917
import cmp.sample.shared.platformAuthentication.AuthenticationScreenViewModel
18+
import com.russhwolf.settings.Settings
2019
import org.mifos.authenticator.biometrics.LibraryLocalCompositionProvider
2120

2221
@Composable
2322
fun App() {
2423
LibraryLocalCompositionProvider {
2524
MaterialTheme {
26-
val kmpDataStore = PreferenceDataStoreImpl()
25+
val settings = Settings()
2726

28-
val chooseAuthOptionRepository = ChooseAuthOptionRepository(kmpDataStore)
27+
val chooseAuthOptionRepository = ChooseAuthOptionRepository(settings)
2928
val chooseAuthOptionScreenViewmodel = ChooseAuthOptionScreenViewmodel(
3029
chooseAuthOptionRepository,
3130
)
3231

3332
val platformAuthOptionScreenViewmodel = AuthenticationScreenViewModel(
3433
chooseAuthOptionRepository = chooseAuthOptionRepository,
35-
preferenceDataStore = kmpDataStore,
34+
settings = settings,
3635
)
3736

38-
val passcodeRepository = PasscodeRepository(kmpDataStore)
37+
val passcodeStorageAdapter = PasscodeStorageAdapterImpl(settings)
3938

4039
SampleAppNavigation(
41-
passcodeRepository,
40+
passcodeStorageAdapter,
4241
chooseAuthOptionScreenViewmodel,
4342
platformAuthOptionScreenViewmodel,
4443
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-passcode-cmp/blob/development/LICENSE
9+
*/
10+
package cmp.sample.shared
11+
12+
import com.russhwolf.settings.Settings
13+
import org.mifos.authenticator.passcode.PasscodeStorageAdapter
14+
15+
const val PASSCODE_KEY = "org.mifos.authenticator.passcode"
16+
17+
class PasscodeStorageAdapterImpl(
18+
private val settings: Settings,
19+
) : PasscodeStorageAdapter {
20+
override fun savePasscode(passcode: String) {
21+
settings.putString(PASSCODE_KEY, passcode)
22+
}
23+
24+
override fun loadPasscode(): String? {
25+
return settings.getStringOrNull(PASSCODE_KEY)
26+
}
27+
28+
override fun deletePasscode() {
29+
settings.remove(PASSCODE_KEY)
30+
}
31+
}

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/chooseAuthOption/ChooseAuthOptionRepository.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,42 @@
1010
package cmp.sample.shared.chooseAuthOption
1111

1212
import cmp.sample.shared.chooseAuthOption.utils.Helpers
13-
import cmp.sample.shared.kmpDataStore.PreferenceDataStore
13+
import com.russhwolf.settings.Settings
1414

1515
const val APP_LOCK_KEY = "auth_method"
1616
const val REGISTRATION_DATA = "REGISTRATION_DATA"
1717

1818
class ChooseAuthOptionRepository(
19-
private val preferenceDataStore: PreferenceDataStore,
19+
private val settings: Settings,
2020
) {
2121

2222
fun setAuthOption(option: AppLockOption) {
23-
preferenceDataStore.putData(
23+
settings.putString(
2424
APP_LOCK_KEY,
2525
Helpers.authOptionToStringMapperFunction(option),
2626
)
2727
}
2828

2929
fun getAuthOption(): AppLockOption {
3030
return Helpers.stringToAuthOptionMapperFunction(
31-
preferenceDataStore.getSavedData(
31+
settings.getString(
3232
APP_LOCK_KEY,
3333
"",
3434
),
3535
)
3636
}
3737

3838
fun clearAuthOption() {
39-
preferenceDataStore.clearData(APP_LOCK_KEY)
39+
settings.remove(APP_LOCK_KEY)
4040
}
4141

4242
fun saveRegistrationData(registrationData: String) {
43-
preferenceDataStore.putData(REGISTRATION_DATA, registrationData)
43+
settings.putString(REGISTRATION_DATA, registrationData)
4444
}
4545

46-
fun getRegistrationData() = preferenceDataStore.getSavedData(REGISTRATION_DATA, "")
46+
fun getRegistrationData() = settings.getString(REGISTRATION_DATA, "")
4747

4848
fun clearRegistrationData() {
49-
preferenceDataStore.clearData(REGISTRATION_DATA)
49+
settings.remove(REGISTRATION_DATA)
5050
}
5151
}

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/kmpDataStore/PreferenceDataStore.kt

Lines changed: 0 additions & 19 deletions
This file was deleted.

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/kmpDataStore/PreferenceDataStoreImpl.kt

Lines changed: 0 additions & 44 deletions
This file was deleted.

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/navigation/SampleAppNavigation.kt

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import androidx.compose.runtime.Composable
2020
import androidx.compose.runtime.getValue
2121
import androidx.compose.runtime.mutableStateOf
2222
import androidx.compose.runtime.remember
23-
import androidx.compose.runtime.saveable.rememberSaveable
24-
import androidx.compose.runtime.setValue
23+
import androidx.compose.runtime.rememberCoroutineScope
2524
import androidx.compose.ui.Alignment
2625
import androidx.compose.ui.Modifier
2726
import androidx.compose.ui.text.font.FontWeight
@@ -33,55 +32,42 @@ import androidx.navigation.compose.rememberNavController
3332
import cmp.sample.shared.chooseAuthOption.AppLockOption
3433
import cmp.sample.shared.chooseAuthOption.ChooseAuthOptionScreen
3534
import cmp.sample.shared.chooseAuthOption.ChooseAuthOptionScreenViewmodel
36-
import cmp.sample.shared.passcode.PasscodeRepository
3735
import cmp.sample.shared.platformAuthentication.AuthenticationScreen
3836
import cmp.sample.shared.platformAuthentication.AuthenticationScreenViewModel
3937
import org.mifos.authenticator.biometrics.Platform
4038
import org.mifos.authenticator.biometrics.getPlatform
41-
import org.mifos.authenticator.passcode.rememberPasscodeSaver
39+
import org.mifos.authenticator.passcode.PasscodeAction
40+
import org.mifos.authenticator.passcode.PasscodeStorageAdapter
41+
import org.mifos.authenticator.passcode.rememberPasscodeManager
4242
import org.mifos.authenticator.passcode.screen.PasscodeScreen
4343

4444
@Composable
4545
fun SampleAppNavigation(
46-
passcodeRepository: PasscodeRepository,
46+
passcodeStorageAdapter: PasscodeStorageAdapter,
4747
chooseAuthOptionScreenViewmodel: ChooseAuthOptionScreenViewmodel,
4848
platformAuthOptionScreenViewmodel: AuthenticationScreenViewModel,
4949
) {
5050
val navController = rememberNavController()
5151

5252
val currentAppLock = chooseAuthOptionScreenViewmodel.getAppLock()
5353

54-
var isPasscodeSet by rememberSaveable {
55-
mutableStateOf(passcodeRepository.isPasscodeSet())
56-
}
57-
var savedPasscode by rememberSaveable {
58-
mutableStateOf(passcodeRepository.getPasscode())
59-
}
54+
val scope = rememberCoroutineScope()
6055

61-
val passcodeSaver = rememberPasscodeSaver(
62-
currentPasscode = savedPasscode,
63-
isPasscodeSet = isPasscodeSet,
64-
savePasscode = { passcode ->
65-
passcodeRepository.savePasscode(passcode)
66-
},
67-
clearPasscode = {
68-
passcodeRepository.clearPasscode()
69-
},
70-
currentPasscodeLength = passcodeRepository.getPasscodeLength(),
71-
savePasscodeLength = { passcodeLength ->
72-
passcodeRepository.savePasscodeLength(passcodeLength.length)
73-
},
56+
val passcodeManager = rememberPasscodeManager(
57+
passcodeStorageAdapter,
58+
scope,
7459
)
7560

61+
val isUsingPasscode = !passcodeStorageAdapter.loadPasscode().isNullOrBlank()
62+
7663
val startDestination by remember {
7764
mutableStateOf(
7865
when (currentAppLock) {
7966
AppLockOption.MifosPasscode -> {
80-
if (passcodeRepository.isPasscodeSet()) {
67+
if (isUsingPasscode) {
8168
Route.PasscodeScreen
8269
} else {
8370
chooseAuthOptionScreenViewmodel.clearAppLock()
84-
passcodeRepository.clearPasscodeLength()
8571
Route.LoginScreen
8672
}
8773
}
@@ -115,20 +101,14 @@ fun SampleAppNavigation(
115101

116102
composable<Route.PasscodeScreen> {
117103
PasscodeScreen(
118-
passcodeSaver = passcodeSaver,
104+
passcodeManager = passcodeManager,
119105
onPasscodeConfirm = {
120-
passcodeRepository.savePasscode(
121-
it,
122-
)
123106
navController.popBackStack()
124107
navController.navigate(Route.HomeScreen) {
125108
popUpTo(0)
126109
}
127110
},
128111
onForgotButton = {
129-
passcodeSaver.forgetPasscode()
130-
savedPasscode = passcodeRepository.getPasscode()
131-
isPasscodeSet = passcodeRepository.isPasscodeSet()
132112
navController.navigate(Route.LoginScreen) {
133113
popUpTo(0)
134114
}
@@ -139,6 +119,13 @@ fun SampleAppNavigation(
139119
popUpTo(0)
140120
}
141121
},
122+
onPasscodeCreation = {
123+
navController.popBackStack()
124+
navController.navigate(Route.HomeScreen) {
125+
popUpTo(0)
126+
}
127+
},
128+
onPasscodeRejected = {},
142129
)
143130
}
144131

@@ -149,16 +136,21 @@ fun SampleAppNavigation(
149136
}
150137

151138
composable<Route.HomeScreen> {
152-
HomeScreen {
153-
chooseAuthOptionScreenViewmodel.clearAppLock()
154-
chooseAuthOptionScreenViewmodel.clearRegistrationData()
155-
passcodeSaver.forgetPasscode()
156-
savedPasscode = passcodeRepository.getPasscode()
157-
isPasscodeSet = passcodeRepository.isPasscodeSet()
158-
navController.navigate(Route.LoginScreen) {
159-
popUpTo(0)
160-
}
161-
}
139+
HomeScreen(
140+
usingPasscode = !passcodeStorageAdapter.loadPasscode().isNullOrBlank(),
141+
onLogoutClick = {
142+
chooseAuthOptionScreenViewmodel.clearAppLock()
143+
chooseAuthOptionScreenViewmodel.clearRegistrationData()
144+
passcodeManager.trySendAction(PasscodeAction.DeletePasscode)
145+
navController.navigate(Route.LoginScreen) {
146+
popUpTo(0)
147+
}
148+
},
149+
changePasscode = {
150+
passcodeManager.trySendAction(PasscodeAction.ChangePasscode)
151+
navController.navigate(Route.PasscodeScreen)
152+
},
153+
)
162154
}
163155

164156
composable<Route.DeviceAuthScreen> {
@@ -199,7 +191,9 @@ fun LoginScreen(
199191

200192
@Composable
201193
fun HomeScreen(
194+
usingPasscode: Boolean,
202195
onLogoutClick: () -> Unit,
196+
changePasscode: () -> Unit,
203197
) {
204198
Column(
205199
modifier = Modifier.fillMaxSize(),
@@ -222,5 +216,19 @@ fun HomeScreen(
222216
"Log Out",
223217
)
224218
}
219+
220+
if (usingPasscode) {
221+
Spacer(modifier = Modifier.height(20.dp))
222+
223+
Button(
224+
onClick = {
225+
changePasscode()
226+
},
227+
) {
228+
Text(
229+
"Change Passcode",
230+
)
231+
}
232+
}
225233
}
226234
}

0 commit comments

Comments
 (0)