Repository: openMF/mifos-mobile
Pull request #2802: refactor: migrate feature/update-password to CMP
Author: login: itsPronay
Created: 2025-03-07T19:35:39Z | Merged: 2025-03-09T15:36:15Z

Fixes - https://mifosforge.jira.com/browse/MM-212

https://github.com/user-attachments/assets/eab62104-9622-4ce5-9f72-716b46e04e00

![image](https://github.com/user-attachments/assets/d1ae02a7-90a4-47d9-a949-c69a3aaf5e41)
![image](https://github.com/user-attachments/assets/9d0fced5-2cb1-4117-9def-12e70faabbbc)

Please Add Screenshots If there are any UI changes.

| Before                                     | After                                  |



|--------------------------------------------|----------------------------------------|
|                                            |                                        |


Please make sure these boxes are checked before submitting your pull request - thanks!

- [ ] Run the static analysis check `./gradlew check` or `ci-prepush.sh` to make sure you didn't break anything

- [ ] If you have multiple commits please combine them into one commit by squashing them.


Diff:
diff --git a/cmp-navigation/build.gradle.kts b/cmp-navigation/build.gradle.kts
index af3a3aa7ac..4ab38725cf 100644
--- a/cmp-navigation/build.gradle.kts
+++ b/cmp-navigation/build.gradle.kts
@@ -27,6 +27,7 @@ kotlin {
             implementation(projects.feature.savingsAccount)
             implementation(projects.feature.about)
             implementation(projects.feature.clientCharge)
+            implementation(projects.feature.updatePassword)
             implementation(projects.feature.thirdPartyTransfer)
             implementation(projects.feature.transferProcess)
             // Core Modules
diff --git a/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt b/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt
index 60a9cddb53..f8ce659962 100644
--- a/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt
+++ b/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt
@@ -28,6 +28,7 @@ import org.mifos.mobile.feature.savingsaccount.di.savingsAccountModule
 import org.mifos.mobile.feature.shareaccount.di.shareAccountModule
 import org.mifos.mobile.feature.third.party.transfer.di.ThirdPartyTransferModule
 import org.mifos.mobile.feature.transfer.process.di.TransferProcessModule
+import org.mifos.mobile.feature.update.password.di.updatePasswordModule
 
 object KoinModules {
     private val commonModules = module {
@@ -56,8 +57,9 @@ object KoinModules {
             shareAccountModule,
             LoanModule,
             ChargeModule,
-            ThirdPartyTransferModule,
             TransferProcessModule,
+            ThirdPartyTransferModule,
+            updatePasswordModule,
         )
     }
     private val LibraryModule = module {
diff --git a/cmp-navigation/src/commonMain/kotlin/cmp/navigation/navigation/FeatureNavHost.kt b/cmp-navigation/src/commonMain/kotlin/cmp/navigation/navigation/FeatureNavHost.kt
index 75a04d76e9..372637a1e5 100644
--- a/cmp-navigation/src/commonMain/kotlin/cmp/navigation/navigation/FeatureNavHost.kt
+++ b/cmp-navigation/src/commonMain/kotlin/cmp/navigation/navigation/FeatureNavHost.kt
@@ -39,6 +39,7 @@ import org.mifos.mobile.feature.third.party.transfer.navigation.navigateToThirdP
 import org.mifos.mobile.feature.third.party.transfer.navigation.thirdPartyTransferNavGraph
 import org.mifos.mobile.feature.transfer.process.navigation.navigateToTransferProcessScreen
 import org.mifos.mobile.feature.transfer.process.navigation.transferProcessNavGraph
+import org.mifos.mobile.feature.update.password.navigation.updatePasswordNavGraph
 
 @Composable
 internal fun FeatureNavHost(
@@ -106,6 +107,10 @@ internal fun FeatureNavHost(
             },
         )
 
+        updatePasswordNavGraph {
+            appState.navController.popBackStack()
+        }
+
         transferProcessNavGraph(
             navigateBack = { appState.navController.popBackStack() },
             onTransferSuccessNavigate = { destination ->
diff --git a/core/model/src/commonMain/kotlin/org/mifos/mobile/core/model/entity/UpdatePasswordPayload.kt b/core/model/src/commonMain/kotlin/org/mifos/mobile/core/model/entity/UpdatePasswordPayload.kt
index 2088c85408..b98033e707 100644
--- a/core/model/src/commonMain/kotlin/org/mifos/mobile/core/model/entity/UpdatePasswordPayload.kt
+++ b/core/model/src/commonMain/kotlin/org/mifos/mobile/core/model/entity/UpdatePasswordPayload.kt
@@ -9,9 +9,12 @@
  */
 package org.mifos.mobile.core.model.entity
 
+import kotlinx.serialization.Serializable
+
 /*
 * Created by saksham on 13/July/2018
 */
+@Serializable
 data class UpdatePasswordPayload(
     val password: String? = null,
     val repeatPassword: String? = null,
diff --git a/feature/update-password/build.gradle.kts b/feature/update-password/build.gradle.kts
index 3bcc9c0704..84f035c7e7 100644
--- a/feature/update-password/build.gradle.kts
+++ b/feature/update-password/build.gradle.kts
@@ -8,15 +8,22 @@
  * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
  */
 plugins {
-    alias(libs.plugins.mifos.android.feature)
-    alias(libs.plugins.mifos.android.library.compose)
+    alias(libs.plugins.mifos.cmp.feature)
+    alias(libs.plugins.kotlin.serialization)
+    alias(libs.plugins.kotlin.parcelize)
 }
 
-
 android {
     namespace = "org.mifos.mobile.feature.update.password"
 }
 
-dependencies {
-    implementation(libs.squareup.okhttp)
-}
\ No newline at end of file
+kotlin {
+    sourceSets{
+        commonMain.dependencies {
+            implementation(compose.components.resources)
+            implementation(compose.material3)
+            implementation(projects.core.ui)
+            implementation(projects.core.model)
+        }
+    }
+}
diff --git a/feature/update-password/src/main/res/drawable/feature_update_password_lock.xml b/feature/update-password/src/commonMain/composeResources/drawable/feature_update_password_lock.xml
similarity index 100%
rename from feature/update-password/src/main/res/drawable/feature_update_password_lock.xml
rename to feature/update-password/src/commonMain/composeResources/drawable/feature_update_password_lock.xml
diff --git a/feature/update-password/src/main/res/values/strings.xml b/feature/update-password/src/commonMain/composeResources/values/strings.xml
similarity index 70%
rename from feature/update-password/src/main/res/values/strings.xml
rename to feature/update-password/src/commonMain/composeResources/values/strings.xml
index f7df8552b5..05f715e1b5 100644
--- a/feature/update-password/src/main/res/values/strings.xml
+++ b/feature/update-password/src/commonMain/composeResources/values/strings.xml
@@ -16,12 +16,13 @@
     <string name="string_changed_successfully">%1$s changed successfully</string>
     <string name="change_password">Change Password</string>
     <string name="confirm_password">Confirm Password</string>
-    <string name="error_validation_blank">%1$s cannot be blank</string>
-    <string name="error_validation_minimum_chars">%1$s cannot be less than %2$d characters</string>
+    <string name="new_password_error_validation_blank">New password cannot be blank</string>
+    <string name="confirm_password_error_validation_blank">Confirm password cannot be blank</string>
+    <string name="new_password_error_validation_minimum_chars">New Password cannot be less than 6 characters</string>
+    <string name="confirm_password_error_validation_minimum_chars">Confirm Password cannot be less than 6 characters</string>
     <string name="dialog_action_ok">OK</string>
     <string name="password_changed_successfully">Password changed successfully</string>
-    <integer name="password_minimum_length">6</integer>
     <string name="no_internet_connection">No Internet Connection</string>
     <string name="error_password_not_match">Password does not match.</string>
-
+    <string name="update_password">Update Password</string>"
 </resources>
\ No newline at end of file
diff --git a/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
new file mode 100644
index 0000000000..0f9a8ac41d
--- /dev/null
+++ b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
@@ -0,0 +1,131 @@
+/*
+ * 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-mobile/blob/master/LICENSE.md
+ */
+package org.mifos.mobile.feature.update.password
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation.Companion.None
+import androidx.compose.ui.unit.dp
+import mifos_mobile.feature.update_password.generated.resources.Res
+import mifos_mobile.feature.update_password.generated.resources.change_password
+import mifos_mobile.feature.update_password.generated.resources.confirm_password
+import mifos_mobile.feature.update_password.generated.resources.new_password
+import org.jetbrains.compose.resources.stringResource
+import org.mifos.mobile.core.designsystem.component.MifosButton
+import org.mifos.mobile.core.designsystem.component.MifosOutlinedTextField
+import org.mifos.mobile.core.designsystem.component.MifosTextFieldConfig
+import org.mifos.mobile.core.designsystem.icon.MifosIcons
+
+@Composable
+internal fun UpdatePasswordContent(
+    state: EditPasswordState,
+    modifier: Modifier = Modifier,
+    onAction: (EditPasswordAction) -> Unit,
+) {
+    val keyboardController = LocalSoftwareKeyboardController.current
+    var newPasswordVisible by rememberSaveable { mutableStateOf(false) }
+    var confirmPasswordVisible by rememberSaveable { mutableStateOf(false) }
+
+    Column(
+        modifier = modifier.fillMaxSize(),
+        verticalArrangement = Arrangement.spacedBy(8.dp),
+    ) {
+        PasswordTextField(
+            value = state.newPasswordInput,
+            onValueChange = {
+                onAction(EditPasswordAction.NewPasswordChange(it))
+            },
+            label = stringResource(Res.string.new_password),
+            isVisible = newPasswordVisible,
+            onVisibilityChange = { newPasswordVisible = it },
+        )
+
+        PasswordTextField(
+            value = state.confirmPasswordInput,
+            onValueChange = {
+                onAction(EditPasswordAction.ConfirmPasswordChange(it))
+            },
+            label = stringResource(Res.string.confirm_password),
+            isVisible = confirmPasswordVisible,
+            onVisibilityChange = { confirmPasswordVisible = it },
+        )
+
+        UpdatePasswordButton(
+            onClick = {
+                keyboardController?.hide()
+                onAction(EditPasswordAction.SubmitClick)
+            },
+        )
+    }
+}
+
+@Composable
+private fun PasswordTextField(
+    value: String,
+    onValueChange: (String) -> Unit,
+    label: String,
+    isVisible: Boolean,
+    onVisibilityChange: (Boolean) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    val mifosTextFieldConfig = MifosTextFieldConfig(
+        trailingIcon = {
+            val image = if (isVisible) MifosIcons.Visibility else MifosIcons.VisibilityOff
+            IconButton(onClick = { onVisibilityChange(!isVisible) }) {
+                Icon(imageVector = image, contentDescription = "password visibility button")
+            }
+        },
+        visualTransformation = if (isVisible) None else PasswordVisualTransformation(),
+        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+    )
+
+    MifosOutlinedTextField(
+        value = value,
+        onValueChange = onValueChange,
+        label = label,
+        config = mifosTextFieldConfig,
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(horizontal = 16.dp),
+    )
+}
+
+@Composable
+private fun UpdatePasswordButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    MifosButton(
+        onClick = onClick,
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(horizontal = 16.dp, vertical = 4.dp),
+        contentPadding = PaddingValues(12.dp),
+    ) {
+        Text(text = stringResource(Res.string.change_password))
+    }
+}
diff --git a/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt
new file mode 100644
index 0000000000..fea20e3e62
--- /dev/null
+++ b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt
@@ -0,0 +1,120 @@
+/*
+ * 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-mobile/blob/master/LICENSE.md
+ */
+package org.mifos.mobile.feature.update.password
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import kotlinx.coroutines.launch
+import mifos_mobile.feature.update_password.generated.resources.Res
+import mifos_mobile.feature.update_password.generated.resources.update_password
+import org.jetbrains.compose.resources.stringResource
+import org.koin.compose.viewmodel.koinViewModel
+import org.mifos.mobile.core.designsystem.component.BasicDialogState
+import org.mifos.mobile.core.designsystem.component.LoadingDialogState
+import org.mifos.mobile.core.designsystem.component.MifosBasicDialog
+import org.mifos.mobile.core.designsystem.component.MifosLoadingDialog
+import org.mifos.mobile.core.designsystem.component.MifosScaffold
+import org.mifos.mobile.core.designsystem.component.MifosTopBar
+import org.mifos.mobile.core.ui.utils.EventsEffect
+
+@Composable
+internal fun UpdatePasswordScreen(
+    navigateBack: () -> Unit,
+    modifier: Modifier = Modifier,
+    viewModel: UpdatePasswordViewModel = koinViewModel(),
+) {
+    val snackbarHostState = remember { SnackbarHostState() }
+    val scope = rememberCoroutineScope()
+    val state by viewModel.stateFlow.collectAsStateWithLifecycle()
+
+    EventsEffect(viewModel.eventFlow) { event ->
+        when (event) {
+            EditPasswordEvent.NavigateBack -> navigateBack()
+            is EditPasswordEvent.ShowToast -> {
+                scope.launch {
+                    snackbarHostState.showSnackbar(event.message)
+                }
+            }
+        }
+    }
+
+    Box(modifier) {
+        EditPasswordDialogs(
+            dialogState = state.dialogState,
+            onDismissRequest = remember(viewModel) {
+                { viewModel.trySendAction(EditPasswordAction.ErrorDialogDismiss) }
+            },
+        )
+
+        UpdatePasswordScreen(
+            state = state,
+            snackbarHostState = snackbarHostState,
+            onAction = remember(viewModel) {
+                { viewModel.trySendAction(it) }
+            },
+        )
+    }
+}
+
+@Composable
+private fun UpdatePasswordScreen(
+    state: EditPasswordState,
+    snackbarHostState: SnackbarHostState,
+    modifier: Modifier = Modifier,
+    onAction: (EditPasswordAction) -> Unit,
+) {
+    MifosScaffold(
+        snackbarHostState = snackbarHostState,
+        modifier = modifier,
+        topBar = {
+            MifosTopBar(
+                topBarTitle = stringResource(Res.string.update_password),
+                backPress = {
+                    onAction(EditPasswordAction.NavigateBackClick)
+                },
+            )
+        },
+    ) { paddingValues ->
+        Box(modifier = Modifier.padding(paddingValues)) {
+            UpdatePasswordContent(
+                onAction = onAction,
+                state = state,
+            )
+        }
+    }
+}
+
+@Composable
+private fun EditPasswordDialogs(
+    dialogState: EditPasswordDialog?,
+    onDismissRequest: () -> Unit,
+) {
+    when (dialogState) {
+        is EditPasswordDialog.Error -> MifosBasicDialog(
+            visibilityState = BasicDialogState.Shown(
+                message = dialogState.message,
+            ),
+            onDismissRequest = onDismissRequest,
+        )
+
+        is EditPasswordDialog.Loading -> MifosLoadingDialog(
+            visibilityState = LoadingDialogState.Shown,
+        )
+
+        null -> Unit
+    }
+}
diff --git a/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordViewModel.kt b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordViewModel.kt
new file mode 100644
index 0000000000..a95986c8fa
--- /dev/null
+++ b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordViewModel.kt
@@ -0,0 +1,236 @@
+/*
+ * 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-mobile/blob/master/LICENSE.md
+ */
+package org.mifos.mobile.feature.update.password
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import org.mifos.mobile.core.common.DataState
+import org.mifos.mobile.core.data.repository.UserAuthRepository
+import org.mifos.mobile.core.model.Parcelable
+import org.mifos.mobile.core.model.Parcelize
+import org.mifos.mobile.core.ui.PasswordStrengthState
+import org.mifos.mobile.core.ui.utils.BaseViewModel
+import org.mifos.mobile.core.ui.utils.PasswordChecker
+import org.mifos.mobile.core.ui.utils.PasswordStrength
+import org.mifos.mobile.core.ui.utils.PasswordStrengthResult
+
+private const val KEY_STATE = "state"
+private const val MIN_PASSWORD_LENGTH = 6
+
+internal class UpdatePasswordViewModel(
+    private val repository: UserAuthRepository,
+    savedStateHandle: SavedStateHandle,
+//    private val clientRepositoryImp: ClientRepository,
+) : BaseViewModel<EditPasswordState, EditPasswordEvent, EditPasswordAction>(
+    initialState = savedStateHandle[KEY_STATE] ?: EditPasswordState(),
+) {
+    private var passwordStrengthJob: Job = Job().apply { complete() }
+
+    init {
+        stateFlow
+            .onEach { savedStateHandle[KEY_STATE] = it }
+            .launchIn(viewModelScope)
+    }
+
+    override fun handleAction(action: EditPasswordAction) {
+        when (action) {
+            EditPasswordAction.SubmitClick -> handleSubmitClick()
+
+            is EditPasswordAction.NavigateBackClick -> {
+                sendEvent(EditPasswordEvent.NavigateBack)
+            }
+
+            is EditPasswordAction.Internal.ReceivePasswordStrengthResult -> {
+                handlePasswordStrengthResult(action)
+            }
+
+            is EditPasswordAction.Internal.ReceiveUpdatePasswordResult -> {
+                handleResult(action)
+            }
+
+            is EditPasswordAction.NewPasswordChange -> {
+                handleNewPasswordInput(action)
+            }
+
+            is EditPasswordAction.ConfirmPasswordChange -> {
+                mutableStateFlow.update {
+                    it.copy(confirmPasswordInput = action.confirmPassword)
+                }
+            }
+
+            EditPasswordAction.ErrorDialogDismiss -> {
+                mutableStateFlow.update {
+                    it.copy(dialogState = null)
+                }
+            }
+        }
+    }
+
+    private fun handleNewPasswordInput(action: EditPasswordAction.NewPasswordChange) {
+        // Update input:
+        mutableStateFlow.update { it.copy(newPasswordInput = action.newPassword) }
+        // Update password strength:
+        passwordStrengthJob.cancel()
+        if (action.newPassword.isEmpty()) {
+            mutableStateFlow.update {
+                it.copy(passwordStrengthState = PasswordStrengthState.NONE)
+            }
+        } else {
+            passwordStrengthJob = viewModelScope.launch {
+                val result = PasswordChecker.getPasswordStrengthResult(action.newPassword)
+                trySendAction(EditPasswordAction.Internal.ReceivePasswordStrengthResult(result))
+            }
+        }
+    }
+
+    private fun handleResult(action: EditPasswordAction.Internal.ReceiveUpdatePasswordResult) {
+        when (val result = action.result) {
+            is DataState.Success -> {
+                mutableStateFlow.update { it.copy(dialogState = null) }
+                sendEvent(EditPasswordEvent.ShowToast(result.data))
+                sendEvent(EditPasswordEvent.NavigateBack)
+            }
+
+            is DataState.Error -> {
+                mutableStateFlow.update {
+                    it.copy(dialogState = EditPasswordDialog.Error(""))
+                }
+            }
+
+            DataState.Loading -> {
+                mutableStateFlow.update { it.copy(dialogState = EditPasswordDialog.Loading) }
+            }
+        }
+    }
+
+    private fun handleSubmitClick() = when {
+        state.newPasswordInput.length < MIN_PASSWORD_LENGTH -> {
+            mutableStateFlow.update {
+                it.copy(
+                    dialogState = EditPasswordDialog.Error(
+                        "Password must be at least $MIN_PASSWORD_LENGTH characters long.",
+                    ),
+                )
+            }
+        }
+
+        !state.isPasswordMatch -> {
+            mutableStateFlow.update {
+                it.copy(dialogState = EditPasswordDialog.Error("Passwords do not match."))
+            }
+        }
+
+        !state.isPasswordStrong -> {
+            mutableStateFlow.update {
+                it.copy(dialogState = EditPasswordDialog.Error("Password is weak."))
+            }
+        }
+
+        else -> initiateUpdatePassword()
+    }
+
+    private fun handlePasswordStrengthResult(action: EditPasswordAction.Internal.ReceivePasswordStrengthResult) {
+        when (val result = action.result) {
+            is PasswordStrengthResult.Success -> {
+                val updatedState = when (result.passwordStrength) {
+                    PasswordStrength.LEVEL_0 -> PasswordStrengthState.WEAK_1
+                    PasswordStrength.LEVEL_1 -> PasswordStrengthState.WEAK_2
+                    PasswordStrength.LEVEL_2 -> PasswordStrengthState.WEAK_3
+                    PasswordStrength.LEVEL_3 -> PasswordStrengthState.GOOD
+                    PasswordStrength.LEVEL_4 -> PasswordStrengthState.STRONG
+                    PasswordStrength.LEVEL_5 -> PasswordStrengthState.VERY_STRONG
+                }
+                mutableStateFlow.update { oldState ->
+                    oldState.copy(passwordStrengthState = updatedState)
+                }
+            }
+
+            is PasswordStrengthResult.Error -> {}
+        }
+    }
+
+    private fun initiateUpdatePassword() {
+        mutableStateFlow.update {
+            it.copy(dialogState = EditPasswordDialog.Loading)
+        }
+
+        updatePassword(state.newPasswordInput, state.confirmPasswordInput)
+    }
+
+    private fun updatePassword(newPassword: String, confirmPassword: String) {
+        viewModelScope.launch {
+            val result = repository.updateAccountPassword(newPassword, confirmPassword)
+
+            sendAction(EditPasswordAction.Internal.ReceiveUpdatePasswordResult(result))
+        }
+    }
+}
+
+@Parcelize
+internal data class EditPasswordState(
+    val newPasswordInput: String = "",
+    val confirmPasswordInput: String = "",
+    val dialogState: EditPasswordDialog? = null,
+    val passwordStrengthState: PasswordStrengthState = PasswordStrengthState.NONE,
+) : Parcelable {
+    val isPasswordStrong: Boolean
+        get() = when (passwordStrengthState) {
+            PasswordStrengthState.NONE,
+            PasswordStrengthState.WEAK_1,
+            PasswordStrengthState.WEAK_2,
+            PasswordStrengthState.WEAK_3,
+            -> false
+
+            PasswordStrengthState.GOOD,
+            PasswordStrengthState.STRONG,
+            PasswordStrengthState.VERY_STRONG,
+            -> true
+        }
+
+    val isPasswordMatch: Boolean
+        get() = newPasswordInput == confirmPasswordInput
+}
+
+internal sealed interface EditPasswordDialog : Parcelable {
+    @Parcelize
+    data object Loading : EditPasswordDialog
+
+    @Parcelize
+    data class Error(val message: String) : EditPasswordDialog
+}
+
+internal sealed interface EditPasswordEvent {
+    data object NavigateBack : EditPasswordEvent
+    data class ShowToast(val message: String) : EditPasswordEvent
+}
+
+internal sealed interface EditPasswordAction {
+    data class NewPasswordChange(val newPassword: String) : EditPasswordAction
+    data class ConfirmPasswordChange(val confirmPassword: String) : EditPasswordAction
+
+    data object SubmitClick : EditPasswordAction
+    data object NavigateBackClick : EditPasswordAction
+    data object ErrorDialogDismiss : EditPasswordAction
+
+    sealed class Internal : EditPasswordAction {
+        data class ReceiveUpdatePasswordResult(
+            val result: DataState<String>,
+        ) : Internal()
+
+        data class ReceivePasswordStrengthResult(
+            val result: PasswordStrengthResult,
+        ) : Internal()
+    }
+}
diff --git a/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/di/UpdatePasswordModule.kt b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/di/UpdatePasswordModule.kt
new file mode 100644
index 0000000000..8e5db0367f
--- /dev/null
+++ b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/di/UpdatePasswordModule.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2025 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-mobile/blob/master/LICENSE.md
+ */
+package org.mifos.mobile.feature.update.password.di
+
+import org.koin.core.module.dsl.viewModelOf
+import org.koin.dsl.module
+import org.mifos.mobile.feature.update.password.UpdatePasswordViewModel
+
+val updatePasswordModule = module {
+    viewModelOf(::UpdatePasswordViewModel)
+}
diff --git a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/navigation/UpdatePasswordNavGraph.kt b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/navigation/UpdatePasswordNavGraph.kt
similarity index 100%
rename from feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/navigation/UpdatePasswordNavGraph.kt
rename to feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/navigation/UpdatePasswordNavGraph.kt
diff --git a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/navigation/UserProfileNavigation.kt b/feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/navigation/UserProfileNavigation.kt
similarity index 100%
rename from feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/navigation/UserProfileNavigation.kt
rename to feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/navigation/UserProfileNavigation.kt
diff --git a/feature/update-password/src/main/AndroidManifest.xml b/feature/update-password/src/main/AndroidManifest.xml
deleted file mode 100644
index 1f9b243f0f..0000000000
--- a/feature/update-password/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    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-mobile/blob/master/LICENSE.md
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-</manifest>
\ No newline at end of file
diff --git a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt b/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
deleted file mode 100644
index 71cf63ab24..0000000000
--- a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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-mobile/blob/master/LICENSE.md
- */
-package org.mifos.mobile.feature.update.password
-
-import androidx.annotation.StringRes
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.PasswordVisualTransformation
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.text.input.VisualTransformation.Companion.None
-import androidx.compose.ui.unit.dp
-import org.mifos.mobile.core.designsystem.components.MifosButton
-import org.mifos.mobile.core.designsystem.components.MifosOutlinedTextField
-import org.mifos.mobile.core.designsystem.icons.MifosIcons
-
-@Composable
-internal fun UpdatePasswordContent(
-    updatePasswordButtonClicked: () -> Unit,
-    validateAndUpdatePassword: (PasswordValidationParams) -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    var newPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
-        mutableStateOf(TextFieldValue(""))
-    }
-    var confirmPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
-        mutableStateOf(TextFieldValue(""))
-    }
-
-    var newPasswordVisible by rememberSaveable { mutableStateOf(false) }
-    var confirmPasswordVisible by rememberSaveable { mutableStateOf(false) }
-
-    var newPasswordError by rememberSaveable { mutableStateOf(false) }
-    var confirmPasswordError by rememberSaveable { mutableStateOf(false) }
-
-    var newPasswordErrorContent by rememberSaveable { mutableStateOf("") }
-    var confirmPasswordErrorContent by rememberSaveable { mutableStateOf("") }
-
-    val keyboardController = LocalSoftwareKeyboardController.current
-
-    Column(
-        modifier = modifier.fillMaxSize(),
-        verticalArrangement = Arrangement.spacedBy(8.dp),
-    ) {
-        PasswordTextField(
-            value = newPassword,
-            onValueChange = {
-                newPassword = it
-                newPasswordError = false
-            },
-            label = R.string.new_password,
-            errorContent = newPasswordErrorContent,
-            isError = newPasswordError,
-            isVisible = newPasswordVisible,
-            onVisibilityChange = { newPasswordVisible = it },
-        )
-
-        PasswordTextField(
-            value = confirmPassword,
-            onValueChange = {
-                confirmPassword = it
-                confirmPasswordError = false
-            },
-            label = R.string.confirm_password,
-            errorContent = confirmPasswordErrorContent,
-            isError = confirmPasswordError,
-            isVisible = confirmPasswordVisible,
-            onVisibilityChange = { confirmPasswordVisible = it },
-        )
-
-        UpdatePasswordButton(
-            onClick = {
-                keyboardController?.hide()
-                updatePasswordButtonClicked()
-                validateAndUpdatePassword(
-                    PasswordValidationParams(
-                        newPassword = newPassword.text,
-                        confirmPassword = confirmPassword.text,
-                        setNewPasswordError = { newPasswordError = it },
-                        setConfirmPasswordError = { confirmPasswordError = it },
-                        setNewPasswordErrorContent = { newPasswordErrorContent = it },
-                        setConfirmPasswordErrorContent = { confirmPasswordErrorContent = it },
-                    ),
-                )
-            },
-        )
-    }
-}
-
-@Composable
-private fun PasswordTextField(
-    value: TextFieldValue,
-    onValueChange: (TextFieldValue) -> Unit,
-    @StringRes label: Int,
-    errorContent: String,
-    isError: Boolean,
-    isVisible: Boolean,
-    onVisibilityChange: (Boolean) -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    MifosOutlinedTextField(
-        value = value,
-        onValueChange = onValueChange,
-        label = label,
-        supportingText = errorContent,
-        icon = R.drawable.feature_update_password_lock,
-        trailingIcon = {
-            val image = if (isVisible) MifosIcons.Visibility else MifosIcons.VisibilityOff
-            if (!isError) {
-                IconButton(onClick = { onVisibilityChange(!isVisible) }) {
-                    Icon(imageVector = image, contentDescription = "password visibility button")
-                }
-            } else {
-                Icon(imageVector = MifosIcons.Error, contentDescription = null)
-            }
-        },
-        error = isError,
-        visualTransformation = if (isVisible) None else PasswordVisualTransformation(),
-        keyboardType = KeyboardType.Password,
-        modifier = modifier
-            .fillMaxWidth()
-            .padding(horizontal = 16.dp),
-    )
-}
-
-@Composable
-private fun UpdatePasswordButton(
-    onClick: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    MifosButton(
-        onClick = onClick,
-        modifier = modifier
-            .fillMaxWidth()
-            .padding(horizontal = 16.dp, vertical = 4.dp),
-        contentPadding = PaddingValues(12.dp),
-        colors = ButtonDefaults.buttonColors(
-            containerColor = MaterialTheme.colorScheme.primary,
-        ),
-    ) {
-        Text(text = stringResource(id = R.string.change_password))
-    }
-}
-
-data class PasswordValidationParams(
-    val newPassword: String,
-    val confirmPassword: String,
-    val setNewPasswordError: (Boolean) -> Unit,
-    val setConfirmPasswordError: (Boolean) -> Unit,
-    val setNewPasswordErrorContent: (String) -> Unit,
-    val setConfirmPasswordErrorContent: (String) -> Unit,
-)
diff --git a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt b/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt
deleted file mode 100644
index 33fabc0d56..0000000000
--- a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * 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-mobile/blob/master/LICENSE.md
- */
-package org.mifos.mobile.feature.update.password
-
-import android.content.Context
-import android.widget.Toast
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SnackbarDuration
-import androidx.compose.material3.SnackbarHost
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.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.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.PreviewParameter
-import androidx.compose.ui.tooling.preview.PreviewParameterProvider
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import org.mifos.mobile.core.common.Network
-import org.mifos.mobile.core.designsystem.components.MifosTopBar
-import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
-import org.mifos.mobile.core.ui.component.MifosProgressIndicator
-import org.mifos.mobile.core.ui.utils.DevicePreviews
-
-@Composable
-internal fun UpdatePasswordScreen(
-    navigateBack: () -> Unit,
-    modifier: Modifier = Modifier,
-    viewModel: UpdatePasswordViewModel = hiltViewModel(),
-) {
-    val uiState by viewModel.updatePasswordUiState.collectAsStateWithLifecycle()
-    val context = LocalContext.current
-
-    UpdatePasswordScreen(
-        uiState = uiState,
-        navigateBack = navigateBack,
-        modifier = modifier,
-        validateAndUpdatePassword = { params ->
-            validateAndUpdatePassword(
-                context = context,
-                params = params,
-                updateAccountPassword = viewModel::updateAccountPassword,
-            )
-        },
-    )
-}
-
-@Composable
-private fun UpdatePasswordScreen(
-    uiState: UpdatePasswordUiState,
-    navigateBack: () -> Unit,
-    validateAndUpdatePassword: (PasswordValidationParams) -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val snackbarHostState = remember { SnackbarHostState() }
-    var updatePasswordButtonClicked by remember { mutableStateOf(false) }
-
-    Scaffold(
-        snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
-        modifier = modifier,
-        topBar = {
-            MifosTopBar(
-                title = { Text(stringResource(R.string.change_password)) },
-                navigateBack = navigateBack,
-            )
-        },
-    ) { paddingValues ->
-        Box(modifier = Modifier.padding(paddingValues)) {
-            UpdatePasswordContent(
-                updatePasswordButtonClicked = { updatePasswordButtonClicked = true },
-                validateAndUpdatePassword = validateAndUpdatePassword,
-            )
-
-            HandleUpdatePasswordState(
-                uiState = uiState,
-                updatePasswordButtonClicked = updatePasswordButtonClicked,
-                snackbarHostState = snackbarHostState,
-                navigateBack = navigateBack,
-            )
-        }
-    }
-}
-
-@Composable
-@Suppress("ModifierMissing")
-private fun HandleUpdatePasswordState(
-    uiState: UpdatePasswordUiState,
-    updatePasswordButtonClicked: Boolean,
-    snackbarHostState: SnackbarHostState,
-    navigateBack: () -> Unit,
-) {
-    val context = LocalContext.current
-
-    when (uiState) {
-        is UpdatePasswordUiState.Loading -> {
-            MifosProgressIndicator(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .background(MaterialTheme.colorScheme.background.copy(alpha = 0.8f)),
-            )
-        }
-
-        is UpdatePasswordUiState.Error -> {
-            if (updatePasswordButtonClicked) {
-                LaunchedEffect(snackbarHostState) {
-                    snackbarHostState.showSnackbar(
-                        message = context.getString(R.string.could_not_update_password_error),
-                        actionLabel = context.getString(R.string.dialog_action_ok),
-                        duration = SnackbarDuration.Short,
-                    )
-                }
-            }
-        }
-
-        is UpdatePasswordUiState.Success -> {
-            LaunchedEffect(snackbarHostState) {
-                snackbarHostState.showSnackbar(
-                    message = context.getString(R.string.password_changed_successfully),
-                    actionLabel = context.getString(R.string.dialog_action_ok),
-                    duration = SnackbarDuration.Short,
-                )
-                navigateBack()
-            }
-        }
-
-        is UpdatePasswordUiState.Initial -> Unit
-    }
-}
-
-private fun validateAndUpdatePassword(
-    context: Context,
-    params: PasswordValidationParams,
-    updateAccountPassword: (newPassword: String, confirmPassword: String) -> Unit,
-) {
-    with(params) {
-        val newPasswordErrorContent =
-            getPasswordError(newPassword, context.getString(R.string.new_password), context)
-        val confirmPasswordErrorContent =
-            getPasswordError(confirmPassword, context.getString(R.string.confirm_password), context)
-
-        setNewPasswordErrorContent(newPasswordErrorContent)
-        setConfirmPasswordErrorContent(confirmPasswordErrorContent)
-
-        when {
-            newPasswordErrorContent.isEmpty() && confirmPasswordErrorContent.isEmpty() -> {
-                if (validatePasswordMatch(newPassword, confirmPassword) &&
-                    Network.isConnected(context)
-                ) {
-                    updateAccountPassword(newPassword, confirmPassword)
-                } else {
-                    showErrorToast(context)
-                }
-            }
-
-            newPasswordErrorContent.isEmpty() && confirmPasswordErrorContent.isNotEmpty() -> {
-                setConfirmPasswordError(true)
-            }
-
-            newPasswordErrorContent.isNotEmpty() && confirmPasswordErrorContent.isEmpty() -> {
-                setNewPasswordError(true)
-            }
-
-            else -> {
-                setNewPasswordError(true)
-                setConfirmPasswordError(true)
-            }
-        }
-    }
-}
-
-private fun getPasswordError(
-    password: String,
-    passwordType: String,
-    context: Context,
-): String = when {
-    password.isEmpty() -> context.getString(R.string.error_validation_blank, passwordType)
-    password.length < context.resources.getInteger(R.integer.password_minimum_length) ->
-        context.getString(
-            R.string.error_validation_minimum_chars,
-            passwordType,
-            context.resources.getInteger(R.integer.password_minimum_length),
-        )
-
-    else -> ""
-}
-
-private fun showErrorToast(context: Context) {
-    val errorMessage = if (!Network.isConnected(context)) {
-        context.getString(R.string.no_internet_connection)
-    } else {
-        context.getString(R.string.error_password_not_match)
-    }
-    Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
-}
-
-private fun validatePasswordMatch(newPassword: String, confirmPassword: String): Boolean {
-    return newPassword == confirmPassword
-}
-
-internal class UiStatesParameterProvider : PreviewParameterProvider<UpdatePasswordUiState> {
-    override val values: Sequence<UpdatePasswordUiState>
-        get() = sequenceOf(
-            UpdatePasswordUiState.Initial,
-            UpdatePasswordUiState.Error(1),
-            UpdatePasswordUiState.Loading,
-            UpdatePasswordUiState.Success,
-        )
-}
-
-@Composable
-@DevicePreviews
-private fun UpdatePasswordScreenPreview(
-    @PreviewParameter(UiStatesParameterProvider::class)
-    updatePasswordUiState: UpdatePasswordUiState,
-) {
-    MifosMobileTheme {
-        UpdatePasswordScreen(
-            uiState = updatePasswordUiState,
-            navigateBack = {},
-            validateAndUpdatePassword = {
-                PasswordValidationParams(
-                    newPassword = "",
-                    confirmPassword = "",
-                    setNewPasswordError = {},
-                    setConfirmPasswordError = {},
-                    setNewPasswordErrorContent = {},
-                    setConfirmPasswordErrorContent = {},
-                )
-            },
-        )
-    }
-}
diff --git a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordViewModel.kt b/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordViewModel.kt
deleted file mode 100644
index 7b2f3b9349..0000000000
--- a/feature/update-password/src/main/java/org/mifos/mobile/feature/update/password/UpdatePasswordViewModel.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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-mobile/blob/master/LICENSE.md
- */
-package org.mifos.mobile.feature.update.password
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.launch
-import org.mifos.mobile.core.data.repository.ClientRepository
-import org.mifos.mobile.core.data.repository.UserAuthRepository
-import org.mifos.mobile.feature.update.password.UpdatePasswordUiState.Initial
-import javax.inject.Inject
-
-@HiltViewModel
-internal class UpdatePasswordViewModel @Inject constructor(
-    private val userAuthRepositoryImp: UserAuthRepository,
-    private val clientRepositoryImp: ClientRepository,
-) : ViewModel() {
-
-    private val _updatePasswordUiState = MutableStateFlow<UpdatePasswordUiState>(Initial)
-    val updatePasswordUiState = _updatePasswordUiState.asStateFlow()
-
-    fun updateAccountPassword(newPassword: String, confirmPassword: String) {
-        viewModelScope.launch {
-            _updatePasswordUiState.value = UpdatePasswordUiState.Loading
-            userAuthRepositoryImp.updateAccountPassword(newPassword, confirmPassword).catch {
-                _updatePasswordUiState.value =
-                    UpdatePasswordUiState.Error(R.string.could_not_update_password_error)
-            }.collect {
-                _updatePasswordUiState.value = UpdatePasswordUiState.Success
-                clientRepositoryImp.updateAuthenticationToken(newPassword)
-            }
-        }
-    }
-}
-
-internal sealed class UpdatePasswordUiState {
-    data class Error(val exception: Int) : UpdatePasswordUiState()
-    data object Success : UpdatePasswordUiState()
-    data object Loading : UpdatePasswordUiState()
-    data object Initial : UpdatePasswordUiState()
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b2084ce8b3..2b5e94f53a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -72,7 +72,7 @@ include(":feature:help")
 include(":feature:about")
 //include(":feature:settings")
 include(":feature:auth")
-//include(":feature:update-password")
+include(":feature:update-password")
 include(":feature:home")
 //include(":feature:user-profile")
 include(":feature:accounts")


Reviews:
user: login: Nagarjuna0033
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T20:20:43Z
user: login: Nagarjuna0033
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T11:35:37Z
user: login: niyajali
body: @itsPronay all states related logic, lamdas, validation logic always should be handled in ViewModel not in UI code. UI is only for showing the content all actions/event should be handled in ViewModel, Move all these types of things on there and use that directly from ViewModel.

And you're about to fix the app build issue on **android -client**,  did you fixed? App is running as expected!
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T12:07:37Z
user: login: niyajali
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-09T15:35:37Z

Review comments:
diff_hunk: @@ -146,35 +142,30 @@ private fun HandleUpdatePasswordState(
 }
 
 private fun validateAndUpdatePassword(
path: feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordScreen.kt
user: login: Nagarjuna0033
body: @itsPronay move this logic into viewModel
created_at: 2025-03-07T20:19:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -162,7 +175,7 @@ private fun UpdatePasswordButton(
             containerColor = MaterialTheme.colorScheme.primary,
path: feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
user: login: Nagarjuna0033
body: @itsPronay remove colors for composables
created_at: 2025-03-08T11:35:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,37 +28,38 @@ import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
-import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.input.VisualTransformation.Companion.None
 import androidx.compose.ui.unit.dp
-import org.mifos.mobile.core.designsystem.components.MifosButton
-import org.mifos.mobile.core.designsystem.components.MifosOutlinedTextField
-import org.mifos.mobile.core.designsystem.icons.MifosIcons
+import mifos_mobile.feature.update_password.generated.resources.Res
+import mifos_mobile.feature.update_password.generated.resources.change_password
+import mifos_mobile.feature.update_password.generated.resources.confirm_password
+import mifos_mobile.feature.update_password.generated.resources.new_password
+import org.jetbrains.compose.resources.StringResource
+import org.jetbrains.compose.resources.stringResource
+import org.mifos.mobile.core.designsystem.component.MifosButton
+import org.mifos.mobile.core.designsystem.component.MifosOutlinedTextField
+import org.mifos.mobile.core.designsystem.component.MifosTextFieldConfig
+import org.mifos.mobile.core.designsystem.icon.MifosIcons
 
 @Composable
 internal fun UpdatePasswordContent(
     updatePasswordButtonClicked: () -> Unit,
     validateAndUpdatePassword: (PasswordValidationParams) -> Unit,
     modifier: Modifier = Modifier,
 ) {
-    var newPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
-        mutableStateOf(TextFieldValue(""))
-    }
-    var confirmPassword by rememberSaveable(stateSaver = TextFieldValue.Saver) {
-        mutableStateOf(TextFieldValue(""))
-    }
+    var newPassword by rememberSaveable { mutableStateOf("") }
path: feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
user: login: niyajali
body: Move all states like this in ViewModel
created_at: 2025-03-08T11:58:53Z
author_association: COLLABORATOR
diff_hunk: @@ -98,12 +103,16 @@ internal fun UpdatePasswordContent(
                 updatePasswordButtonClicked()
                 validateAndUpdatePassword(
                     PasswordValidationParams(
-                        newPassword = newPassword.text,
-                        confirmPassword = confirmPassword.text,
+                        newPassword = newPassword,
+                        confirmPassword = confirmPassword,
                         setNewPasswordError = { newPasswordError = it },
                         setConfirmPasswordError = { confirmPasswordError = it },
                         setNewPasswordErrorContent = { newPasswordErrorContent = it },
                         setConfirmPasswordErrorContent = { confirmPasswordErrorContent = it },
+                        setPasswordDoesNotMatchOnBothError = {
path: feature/update-password/src/commonMain/kotlin/org/mifos/mobile/feature/update/password/UpdatePasswordContent.kt
user: login: niyajali
body: We do have Password Validation Composable use that..refer mobile-wallet for more information or SignUp screen 
created_at: 2025-03-08T12:00:00Z
author_association: COLLABORATOR
diff_hunk: @@ -123,6 +123,8 @@ androidPackageNamespace = "org.mifos.mobile"
 androidPackageVersion = "1.0.0"
 materialVersion = "1.12.0"
 uiAndroidVersion = "1.7.8"
+material3AndroidVersion = "1.3.1"
+uiAndroid = "1.7.8"
path: gradle/libs.versions.toml
user: login: niyajali
body: Why you're adding these dependencies we already included all required dependencies and always prevent adding new dependencies or upgrading version during ongoing migration/development unless necessary.
created_at: 2025-03-08T12:02:37Z
author_association: COLLABORATOR

PR comments:
user: login: Nagarjuna0033
created_at: 2025-03-07T20:22:11Z
body: @itsPronay upload image for android
author_association: CONTRIBUTOR
user: login: niyajali
created_at: 2025-03-08T17:20:11Z
body: @itsPronay let me know after this changes, we'll proceed with merge 
author_association: COLLABORATOR
user: login: itsPronay
created_at: 2025-03-08T18:48:43Z
body: > @itsPronay all states related logic, lamdas, validation logic always should be handled in ViewModel not in UI code. UI is only for showing the content all actions/event should be handled in ViewModel, Move all these types of things on there and use that directly from ViewModel.
> 
> And you're about to fix the app build issue on **android -client**, did you fixed? App is running as expected!

On it @niyajali bhaiya...

I was moved here to help Arjun with the migration
author_association: MEMBER