From a4360f2ff81cbf1faa141e3784b99174cf608fe8 Mon Sep 17 00:00:00 2001 From: Sk Niyaj Ali Date: Sat, 12 Oct 2024 11:03:28 +0530 Subject: [PATCH] Feat: Migrated Edit Password Module to KMP (#1787) --- .../AndroidApplicationConventionPlugin.kt | 1 - .../kotlin/AndroidLibraryConventionPlugin.kt | 1 - .../main/kotlin/CMPFeatureConventionPlugin.kt | 3 + .../repository/SavingsAccountRepository.kt | 9 +- .../core/data/repository/UserRepository.kt | 2 + .../SavingsAccountRepositoryImpl.kt | 29 +- .../data/repositoryImp/UserRepositoryImpl.kt | 14 + core/designsystem/build.gradle.kts | 4 - .../component/MifosBasicDialog.kt | 52 ++++ .../designsystem/component/MifosScaffold.kt | 10 +- .../designsystem/component/MifosTopBar.kt | 26 +- .../component/OutlineTextField.kt | 11 +- .../core/designsystem/component/TextField.kt | 216 +++++++++++++- .../core/designsystem/icon/MifosIcons.kt | 10 + .../accounts/SavingAccountsListResponse.kt | 3 +- .../savings/BlockUnblockResponseEntity.kt | 26 ++ .../entity/user/UpdateUserEntityPassword.kt | 19 +- .../services/SavingsAccountsService.kt | 3 +- .../core/network/services/UserService.kt | 8 + .../org/mifospay/core/ui/FaqItemScreen.kt | 97 ------- .../mifospay/core/ui/MifosPasswordField.kt | 11 +- .../core/ui}/PasswordStrengthIndicator.kt | 2 +- .../feature/auth/signup/SignupScreen.kt | 1 + .../feature/auth/signup/SignupViewModel.kt | 1 + feature/editpassword/build.gradle.kts | 16 +- feature/editpassword/consumer-rules.pro | 0 feature/editpassword/proguard-rules.pro | 21 -- .../{main => androidMain}/AndroidManifest.xml | 0 .../composeResources}/values/strings.xml | 0 .../editpassword/EditPasswordScreen.kt | 197 +++++++++++++ .../editpassword/EditPasswordViewModel.kt | 267 ++++++++++++++++++ .../editpassword/di/EditPasswordModule.kt | 11 +- .../EditPasswordScreenNavigation.kt | 8 +- .../editpassword/EditPasswordScreen.kt | 225 --------------- .../editpassword/EditPasswordViewModel.kt | 124 -------- feature/faq/build.gradle.kts | 16 +- feature/faq/consumer-rules.pro | 0 feature/faq/proguard-rules.pro | 21 -- .../{main => androidMain}/AndroidManifest.xml | 0 .../composeResources}/values/strings.xml | 2 +- .../kotlin/org/mifospay/feature/faq/FAQ.kt | 7 +- .../org/mifospay/feature/faq/FAQViewModel.kt | 75 +++++ .../org/mifospay/feature/faq/FaqScreen.kt | 203 +++++++++++++ .../org/mifospay/feature/faq/di/FaqModule.kt | 7 +- .../feature/faq/navigation/FAQNavigation.kt | 0 .../org/mifospay/feature/faq/FAQViewModel.kt | 34 --- .../org/mifospay/feature/faq/FaqScreen.kt | 79 ------ feature/settings/build.gradle.kts | 19 +- feature/settings/consumer-rules.pro | 0 feature/settings/proguard-rules.pro | 21 -- .../{main => androidMain}/AndroidManifest.xml | 0 .../drawable/outline_logout.xml | 15 + .../drawable/outline_password.xml | 15 + .../composeResources/drawable/outline_pin.xml | 21 ++ .../composeResources}/values/strings.xml | 1 + .../feature/settings/SettingsScreen.kt | 245 ++++++++++++++++ .../feature/settings/SettingsViewModel.kt | 189 +++++++++++++ .../feature/settings/di/SettingsModule.kt | 10 +- .../settings/navigation/SettingsNavigation.kt | 8 +- .../feature/settings/DialogManager.kt | 49 ---- .../feature/settings/SettingsScreen.kt | 201 ------------- .../feature/settings/SettingsViewModel.kt | 45 --- .../prodReleaseRuntimeClasspath.tree.txt | 101 ++++++- .../prodReleaseRuntimeClasspath.txt | 3 + .../java/org/mifospay/KoinModulesCheck.kt | 76 ----- mifospay-shared/build.gradle.kts | 3 + .../org/mifospay/shared/di/KoinModules.kt | 6 + .../shared/navigation/MifosNavHost.kt | 23 ++ .../kotlin/org/mifospay/shared/ui/MifosApp.kt | 102 +------ .../org/mifospay/shared/ui/MifosAppState.kt | 5 +- 70 files changed, 1838 insertions(+), 1192 deletions(-) create mode 100644 core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/savings/BlockUnblockResponseEntity.kt delete mode 100644 core/ui/src/commonMain/kotlin/org/mifospay/core/ui/FaqItemScreen.kt rename {feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup => core/ui/src/commonMain/kotlin/org/mifospay/core/ui}/PasswordStrengthIndicator.kt (99%) delete mode 100644 feature/editpassword/consumer-rules.pro delete mode 100644 feature/editpassword/proguard-rules.pro rename feature/editpassword/src/{main => androidMain}/AndroidManifest.xml (100%) rename feature/editpassword/src/{main/res => commonMain/composeResources}/values/strings.xml (100%) create mode 100644 feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt create mode 100644 feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt rename feature/editpassword/src/{main => commonMain}/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt (65%) rename feature/editpassword/src/{main => commonMain}/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt (85%) delete mode 100644 feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt delete mode 100644 feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt delete mode 100644 feature/faq/consumer-rules.pro delete mode 100644 feature/faq/proguard-rules.pro rename feature/faq/src/{main => androidMain}/AndroidManifest.xml (100%) rename feature/faq/src/{main/res => commonMain/composeResources}/values/strings.xml (93%) rename feature/faq/src/{main => commonMain}/kotlin/org/mifospay/feature/faq/FAQ.kt (73%) create mode 100644 feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQViewModel.kt create mode 100644 feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FaqScreen.kt rename feature/faq/src/{main => commonMain}/kotlin/org/mifospay/feature/faq/di/FaqModule.kt (83%) rename feature/faq/src/{main => commonMain}/kotlin/org/mifospay/feature/faq/navigation/FAQNavigation.kt (100%) delete mode 100644 feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQViewModel.kt delete mode 100644 feature/faq/src/main/kotlin/org/mifospay/feature/faq/FaqScreen.kt delete mode 100644 feature/settings/consumer-rules.pro delete mode 100644 feature/settings/proguard-rules.pro rename feature/settings/src/{main => androidMain}/AndroidManifest.xml (100%) create mode 100644 feature/settings/src/commonMain/composeResources/drawable/outline_logout.xml create mode 100644 feature/settings/src/commonMain/composeResources/drawable/outline_password.xml create mode 100644 feature/settings/src/commonMain/composeResources/drawable/outline_pin.xml rename feature/settings/src/{main/res => commonMain/composeResources}/values/strings.xml (96%) create mode 100644 feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsScreen.kt create mode 100644 feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt rename feature/settings/src/{main => commonMain}/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt (68%) rename feature/settings/src/{main => commonMain}/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt (80%) delete mode 100644 feature/settings/src/main/kotlin/org/mifospay/feature/settings/DialogManager.kt delete mode 100644 feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsScreen.kt delete mode 100644 feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt delete mode 100644 mifospay-android/src/test/java/org/mifospay/KoinModulesCheck.kt diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt index b4f907e05..38f007945 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -19,7 +19,6 @@ class AndroidApplicationConventionPlugin : Plugin { apply("com.dropbox.dependency-guard") apply("mifos.detekt.plugin") apply("mifos.spotless.plugin") - apply("mifos.ktlint.plugin") apply("mifos.git.hooks") } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index edea58609..d13fabbc7 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -20,7 +20,6 @@ class AndroidLibraryConventionPlugin : Plugin { apply("mifospay.android.lint") apply("mifos.detekt.plugin") apply("mifos.spotless.plugin") - apply("mifos.ktlint.plugin") apply("mifospay.android.koin") } diff --git a/build-logic/convention/src/main/kotlin/CMPFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/CMPFeatureConventionPlugin.kt index f4f34d30a..44559cacd 100644 --- a/build-logic/convention/src/main/kotlin/CMPFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/CMPFeatureConventionPlugin.kt @@ -19,6 +19,9 @@ class CMPFeatureConventionPlugin : Plugin { add("commonMainImplementation", project(":core:designsystem")) add("commonMainImplementation", project(":core:data")) + add("commonMainImplementation", libs.findLibrary("koin.compose").get()) + add("commonMainImplementation", libs.findLibrary("koin.compose.viewmodel").get()) + add("commonMainImplementation", libs.findLibrary("jb.composeRuntime").get()) add("commonMainImplementation", libs.findLibrary("jb.composeViewmodel").get()) add("commonMainImplementation", libs.findLibrary("jb.lifecycleViewmodel").get()) diff --git a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/SavingsAccountRepository.kt b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/SavingsAccountRepository.kt index 608e10ce3..16c724bc3 100644 --- a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/SavingsAccountRepository.kt +++ b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/SavingsAccountRepository.kt @@ -27,10 +27,13 @@ interface SavingsAccountRepository { suspend fun createSavingsAccount(savingAccount: SavingAccountEntity): Flow> - suspend fun blockUnblockAccount( + suspend fun unblockAccount( accountId: Long, - command: String?, - ): Flow> + ): Result + + suspend fun blockAccount( + accountId: Long, + ): Result suspend fun getSavingAccountTransaction( accountId: Long, diff --git a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/UserRepository.kt b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/UserRepository.kt index 915761d86..2ad38fea8 100644 --- a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/UserRepository.kt +++ b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/UserRepository.kt @@ -25,6 +25,8 @@ interface UserRepository { suspend fun updateUser(userId: Int, updatedUser: NewUser): Flow> + suspend fun updateUserPassword(userId: Long, password: String): Result + suspend fun deleteUser(userId: Int): Result suspend fun assignClientToUser(userId: Int, clientId: Int): Result diff --git a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/SavingsAccountRepositoryImpl.kt b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/SavingsAccountRepositoryImpl.kt index 9bb4b36ad..588e52240 100644 --- a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/SavingsAccountRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/SavingsAccountRepositoryImpl.kt @@ -65,13 +65,30 @@ class SavingsAccountRepositoryImpl( .asResult().flowOn(ioDispatcher) } - override suspend fun blockUnblockAccount( + override suspend fun unblockAccount( accountId: Long, - command: String?, - ): Flow> { - return apiManager.savingsAccountsApi - .blockUnblockAccount(accountId, command) - .asResult().flowOn(ioDispatcher) + ): Result { + return try { + withContext(ioDispatcher) { + apiManager.savingsAccountsApi.blockUnblockAccount(accountId, "unblock") + } + + Result.Success("Account unblocked successfully") + } catch (e: Exception) { + Result.Error(e) + } + } + + override suspend fun blockAccount(accountId: Long): Result { + return try { + withContext(ioDispatcher) { + apiManager.savingsAccountsApi.blockUnblockAccount(accountId, "block") + } + + Result.Success("Account blocked successfully") + } catch (e: Exception) { + Result.Error(e) + } } override suspend fun getSavingAccountTransaction( diff --git a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/UserRepositoryImpl.kt b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/UserRepositoryImpl.kt index 0a2541c25..23726d7ef 100644 --- a/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/UserRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/UserRepositoryImpl.kt @@ -22,6 +22,7 @@ import org.mifospay.core.network.FineractApiManager import org.mifospay.core.network.model.CommonResponse import org.mifospay.core.network.model.GenericResponse import org.mifospay.core.network.model.entity.UserWithRole +import org.mifospay.core.network.model.entity.user.UpdateUserEntityPassword class UserRepositoryImpl( private val apiManager: FineractApiManager, @@ -56,6 +57,19 @@ class UserRepositoryImpl( .asResult().flowOn(ioDispatcher) } + override suspend fun updateUserPassword(userId: Long, password: String): Result { + return try { + apiManager.userApi.updateUserPassword( + userId = userId, + updateUserEntity = UpdateUserEntityPassword(password, password), + ) + + Result.Success("Password updated successfully") + } catch (e: Exception) { + Result.Error(e) + } + } + override suspend fun deleteUser(userId: Int): Result { return try { val result = withContext(ioDispatcher) { diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 14746ab93..470795aa2 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -63,8 +63,4 @@ kotlin { compose.resources { publicResClass = true generateResClass = always -} - -dependencies { - lintPublish(projects.lint) } \ No newline at end of file diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosBasicDialog.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosBasicDialog.kt index a9ccfe48e..72d841a72 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosBasicDialog.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosBasicDialog.kt @@ -62,6 +62,58 @@ fun MifosBasicDialog( } } +@Composable +fun MifosBasicDialog( + visibilityState: BasicDialogState, + onConfirm: () -> Unit, + onDismissRequest: () -> Unit, +): Unit = when (visibilityState) { + BasicDialogState.Hidden -> Unit + is BasicDialogState.Shown -> { + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = { + MifosTextButton( + content = { + Text(text = "Ok") + }, + onClick = onConfirm, + modifier = Modifier.testTag("AcceptAlertButton"), + ) + }, + dismissButton = { + MifosTextButton( + content = { + Text(text = "Cancel") + }, + onClick = onDismissRequest, + modifier = Modifier.testTag("DismissAlertButton"), + ) + }, + title = visibilityState.title.let { + { + Text( + text = it, + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.testTag("AlertTitleText"), + ) + } + }, + text = { + Text( + text = visibilityState.message, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.testTag("AlertContentText"), + ) + }, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + modifier = Modifier.semantics { + testTag = "AlertPopup" + }, + ) + } +} + @Preview @Composable private fun MifosBasicDialog_preview() { diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt index 078b307e6..6a1356a74 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt @@ -22,7 +22,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -35,8 +34,8 @@ fun MifosScaffold( topBarTitle: String? = null, floatingActionButtonContent: FloatingActionButtonContent? = null, snackbarHost: @Composable () -> Unit = {}, - scaffoldContent: @Composable (PaddingValues) -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, + content: @Composable (PaddingValues) -> Unit = {}, ) { Scaffold( topBar = { @@ -58,7 +57,8 @@ fun MifosScaffold( } }, snackbarHost = snackbarHost, - content = scaffoldContent, + containerColor = Color.Transparent, + content = content, modifier = modifier, ) } @@ -71,8 +71,8 @@ fun MifosScaffold( floatingActionButton: @Composable () -> Unit = {}, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, floatingActionButtonPosition: FabPosition = FabPosition.End, - containerColor: Color = MaterialTheme.colorScheme.background, - contentColor: Color = contentColorFor(containerColor), + containerColor: Color = Color.Transparent, + contentColor: Color = MaterialTheme.colorScheme.onSurface, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, content: @Composable (PaddingValues) -> Unit, ) { diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt index beabb5148..0a8e004d6 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt @@ -10,15 +10,14 @@ package org.mifospay.core.designsystem.component import androidx.compose.foundation.layout.RowScope +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import org.mifospay.core.designsystem.icon.MifosIcons @OptIn(ExperimentalMaterial3Api::class) @@ -29,26 +28,21 @@ fun MifosTopBar( modifier: Modifier = Modifier, actions: @Composable RowScope.() -> Unit = {}, ) { - TopAppBar( + CenterAlignedTopAppBar( title = { Text( text = topBarTitle, - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleMedium, ) }, navigationIcon = { - IconButton(onClick = { backPress.invoke() }) { - Icon( - imageVector = MifosIcons.ArrowBack, - contentDescription = "Back", - tint = MaterialTheme.colorScheme.onSurface, - ) - } + IconBox( + icon = MifosIcons.ArrowBack2, + onClick = backPress, + ) }, - colors = - TopAppBarDefaults.mediumTopAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent, ), actions = actions, modifier = modifier, diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/OutlineTextField.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/OutlineTextField.kt index e5e1313f4..fe9e6d5db 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/OutlineTextField.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/OutlineTextField.kt @@ -12,9 +12,6 @@ package org.mifospay.core.designsystem.component import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -38,10 +35,10 @@ fun MifosOutlinedTextField( trailingIcon: @Composable (() -> Unit)? = null, keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), ) { - OutlinedTextField( + MifosCustomTextField( value = value, onValueChange = onValueChange, - label = { Text(label) }, + label = label, modifier = modifier, leadingIcon = if (icon != null) { { @@ -56,10 +53,6 @@ fun MifosOutlinedTextField( trailingIcon = trailingIcon, maxLines = maxLines, singleLine = singleLine, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = MaterialTheme.colorScheme.onSurface, - focusedLabelColor = MaterialTheme.colorScheme.onSurface, - ), textStyle = LocalDensity.current.run { TextStyle(fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface) }, diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/TextField.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/TextField.kt index 0fe292064..28d60e83b 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/TextField.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/TextField.kt @@ -10,32 +10,56 @@ package org.mifospay.core.designsystem.component import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.jetbrains.compose.ui.tooling.preview.Preview import org.mifospay.core.designsystem.theme.MifosTheme +import org.mifospay.core.designsystem.theme.NewUi @Composable fun MfOutlinedTextField( @@ -50,11 +74,11 @@ fun MfOutlinedTextField( trailingIcon: @Composable (() -> Unit)? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, ) { - OutlinedTextField( + MifosCustomTextField( modifier = modifier, value = value, onValueChange = onValueChange, - label = { Text(label) }, + label = label, supportingText = { if (isError) { Text(text = errorMessage) @@ -66,10 +90,6 @@ fun MfOutlinedTextField( onKeyboardActions?.invoke() }, keyboardOptions = keyboardOptions, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = MaterialTheme.colorScheme.onSurface, - focusedLabelColor = MaterialTheme.colorScheme.onSurface, - ), textStyle = LocalDensity.current.run { TextStyle(fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface) }, @@ -158,6 +178,190 @@ fun MifosOutlinedTextField( ) } +@Composable +fun MifosTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardActions: KeyboardActions = KeyboardActions.Default, + singleLine: Boolean = true, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + indicatorColor: Color? = null, +) { + var isFocused by rememberSaveable { mutableStateOf(false) } + + BasicTextField( + value = value, + onValueChange = onValueChange, + textStyle = textStyle, + modifier = modifier + .fillMaxWidth() + .padding(top = 10.dp) + .onFocusChanged { focusState -> + isFocused = focusState.isFocused + } + .semantics(mergeDescendants = true) {}, + enabled = enabled, + readOnly = readOnly, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + interactionSource = interactionSource, + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + decorationBox = { innerTextField -> + Column { + Text( + text = label, + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.align(alignment = Alignment.Start), + ) + + Spacer(modifier = Modifier.height(5.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + if (leadingIcon != null) { + leadingIcon() + } + + Box(modifier = Modifier.weight(1f)) { + innerTextField() + } + + if (trailingIcon != null) { + trailingIcon() + } + } + indicatorColor?.let { color -> + HorizontalDivider( + thickness = 1.dp, + color = if (isFocused) { + color + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f) + }, + ) + } ?: run { + HorizontalDivider( + thickness = 1.dp, + color = if (isFocused) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f) + }, + ) + } + } + }, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MifosCustomTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardActions: KeyboardActions = KeyboardActions.Default, + singleLine: Boolean = true, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + isError: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + supportingText: + @Composable() + (() -> Unit)? = null, +) { + val colors = TextFieldDefaults.colors().copy( + cursorColor = MaterialTheme.colorScheme.primary, + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f), + focusedTrailingIconColor = NewUi.onSurface.copy(0.15f), + unfocusedTrailingIconColor = NewUi.onSurface.copy(0.15f), + ) + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + interactionSource = interactionSource, + enabled = enabled, + singleLine = singleLine, + readOnly = readOnly, + textStyle = textStyle, + visualTransformation = visualTransformation, + keyboardActions = keyboardActions, + maxLines = maxLines, + minLines = minLines, + keyboardOptions = keyboardOptions, + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + ) { + TextFieldDefaults.DecorationBox( + value = value, + visualTransformation = VisualTransformation.None, + innerTextField = it, + singleLine = singleLine, + enabled = enabled, + interactionSource = interactionSource, + label = { + Text( + text = label, + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.padding(bottom = 10.dp), + ) + }, + trailingIcon = trailingIcon, + leadingIcon = leadingIcon, + supportingText = supportingText, + colors = colors, + isError = isError, + contentPadding = PaddingValues(bottom = 10.dp), + container = { + TextFieldDefaults.Container( + enabled = enabled, + isError = isError, + colors = colors, + interactionSource = interactionSource, + shape = RectangleShape, + focusedIndicatorLineThickness = 1.dp, + unfocusedIndicatorLineThickness = 1.dp, + ) + }, + ) + } +} + @Preview @Composable fun MfOutlinedTextFieldPreview() { diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt index 1f01147bb..ec184755d 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt @@ -37,7 +37,12 @@ import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material.icons.outlined.Lock +import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material.icons.outlined.Wallet import androidx.compose.material.icons.rounded.AccountBalance import androidx.compose.material.icons.rounded.AccountCircle @@ -57,10 +62,15 @@ import androidx.compose.ui.graphics.vector.ImageVector * Mifos icons. Material icons are [ImageVector]s, custom icons are drawable resource IDs. */ object MifosIcons { + val OutlinedInfo = Icons.Outlined.Info + val OutlinedLock = Icons.Outlined.Lock + val OutlinedNotifications = Icons.Outlined.Notifications val ChevronRight: ImageVector = Icons.Filled.ChevronRight val QrCode: ImageVector = Icons.Filled.QrCode val Close: ImageVector = Icons.Filled.Close val AttachMoney: ImageVector = Icons.Filled.AttachMoney + val OutlinedVisibilityOff: ImageVector = Icons.Outlined.VisibilityOff + val OutlinedVisibility: ImageVector = Icons.Outlined.Visibility val VisibilityOff: ImageVector = Icons.Filled.VisibilityOff val Visibility: ImageVector = Icons.Filled.Visibility val Check: ImageVector = Icons.Default.Check diff --git a/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/SavingAccountsListResponse.kt b/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/SavingAccountsListResponse.kt index c8496b02c..989b4651c 100644 --- a/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/SavingAccountsListResponse.kt +++ b/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/SavingAccountsListResponse.kt @@ -10,8 +10,9 @@ package org.mifospay.core.network.model.entity.accounts import kotlinx.serialization.Serializable +import org.mifospay.core.network.model.entity.accounts.savings.SavingAccountEntity @Serializable data class SavingAccountsListResponse( - var savingsAccounts: List = ArrayList(), + var savingsAccounts: List = ArrayList(), ) diff --git a/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/savings/BlockUnblockResponseEntity.kt b/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/savings/BlockUnblockResponseEntity.kt new file mode 100644 index 000000000..6c3b82790 --- /dev/null +++ b/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/accounts/savings/BlockUnblockResponseEntity.kt @@ -0,0 +1,26 @@ +/* + * 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.mifospay.core.network.model.entity.accounts.savings + +import kotlinx.serialization.Serializable + +@Serializable +data class BlockUnblockResponseEntity( + val officeId: Long, + val clientId: Long, + val savingsId: Long, + val resourceId: Long, + val changes: ChangesEntity, +) + +@Serializable +data class ChangesEntity( + val subStatus: SubStatus, +) diff --git a/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/user/UpdateUserEntityPassword.kt b/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/user/UpdateUserEntityPassword.kt index bf4bdb29c..acef9cdad 100644 --- a/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/user/UpdateUserEntityPassword.kt +++ b/core/network/src/commonMain/kotlin/org/mifospay/core/network/model/entity/user/UpdateUserEntityPassword.kt @@ -12,6 +12,19 @@ package org.mifospay.core.network.model.entity.user import kotlinx.serialization.Serializable @Serializable -data class UpdateUserEntityPassword(val password: String) { - val repeatPassword: String = password -} +data class UpdateUserEntityPassword( + val password: String, + val repeatPassword: String, +) + +@Serializable +data class UpdateUserPasswordResponse( + val officeId: Long, + val resourceId: Long, + val changes: Changes, +) + +@Serializable +data class Changes( + val passwordEncoded: String, +) diff --git a/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/SavingsAccountsService.kt b/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/SavingsAccountsService.kt index cb2743f76..567665af0 100644 --- a/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/SavingsAccountsService.kt +++ b/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/SavingsAccountsService.kt @@ -17,6 +17,7 @@ import de.jensklingenberg.ktorfit.http.Query import kotlinx.coroutines.flow.Flow import org.mifospay.core.network.model.GenericResponse import org.mifospay.core.network.model.entity.Page +import org.mifospay.core.network.model.entity.accounts.savings.BlockUnblockResponseEntity import org.mifospay.core.network.model.entity.accounts.savings.SavingAccountEntity import org.mifospay.core.network.model.entity.accounts.savings.SavingsWithAssociationsEntity import org.mifospay.core.network.model.entity.accounts.savings.TransactionsEntity @@ -41,7 +42,7 @@ interface SavingsAccountsService { suspend fun blockUnblockAccount( @Path("accountId") accountId: Long, @Query("command") command: String?, - ): Flow + ): BlockUnblockResponseEntity @GET( ApiEndPoints.SAVINGS_ACCOUNTS + "/{accountId}/" + ApiEndPoints.TRANSACTIONS + diff --git a/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/UserService.kt b/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/UserService.kt index 70d99d6e8..4ecbe155d 100644 --- a/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/UserService.kt +++ b/core/network/src/commonMain/kotlin/org/mifospay/core/network/services/UserService.kt @@ -20,6 +20,8 @@ import org.mifospay.core.network.model.CommonResponse import org.mifospay.core.network.model.GenericResponse import org.mifospay.core.network.model.entity.UserWithRole import org.mifospay.core.network.model.entity.user.NewUserEntity +import org.mifospay.core.network.model.entity.user.UpdateUserEntityPassword +import org.mifospay.core.network.model.entity.user.UpdateUserPasswordResponse import org.mifospay.core.network.utils.ApiEndPoints interface UserService { @@ -35,6 +37,12 @@ interface UserService { @Body updateUserEntity: NewUserEntity, ): Flow + @PUT(ApiEndPoints.USER + "/{userId}") + suspend fun updateUserPassword( + @Path("userId") userId: Long, + @Body updateUserEntity: UpdateUserEntityPassword, + ): UpdateUserPasswordResponse + @DELETE(ApiEndPoints.USER + "/{userId}") suspend fun deleteUser( @Path("userId") userId: Int, diff --git a/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/FaqItemScreen.kt b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/FaqItemScreen.kt deleted file mode 100644 index 319682002..000000000 --- a/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/FaqItemScreen.kt +++ /dev/null @@ -1,97 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.core.ui - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.spring -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.foundation.clickable -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.material3.HorizontalDivider -import androidx.compose.material3.Icon -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.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import org.mifospay.core.designsystem.icon.MifosIcons - -@Composable -fun FaqItemScreen( - modifier: Modifier = Modifier, - question: String? = null, - answer: String? = null, -) { - var isSelected by remember { mutableStateOf(false) } - - Column( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) { - Row( - modifier = Modifier - .clickable { - isSelected = !isSelected - } - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = question.orEmpty(), - style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold), - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .fillMaxWidth() - .weight(1f), - ) - - Icon( - imageVector = MifosIcons.KeyboardArrowDown, - contentDescription = "drop down", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .scale(1f, if (isSelected) -1f else 1f), - ) - } - - AnimatedVisibility( - visible = isSelected, - enter = fadeIn() + expandVertically( - animationSpec = spring( - stiffness = Spring.StiffnessMedium, - ), - ), - ) { - Text( - text = answer.orEmpty(), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 8.dp), - ) - } - - HorizontalDivider() - } -} diff --git a/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/MifosPasswordField.kt b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/MifosPasswordField.kt index fbbcb3f00..051a58df7 100644 --- a/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/MifosPasswordField.kt +++ b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/MifosPasswordField.kt @@ -14,7 +14,6 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -33,6 +32,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import org.jetbrains.compose.ui.tooling.preview.Preview +import org.mifospay.core.designsystem.component.MifosCustomTextField import org.mifospay.core.designsystem.icon.MifosIcons import org.mifospay.core.designsystem.utils.tabNavigation @@ -54,11 +54,11 @@ fun MifosPasswordField( keyboardActions: KeyboardActions = KeyboardActions.Default, ) { val focusRequester = remember { FocusRequester() } - OutlinedTextField( + MifosCustomTextField( modifier = modifier .tabNavigation() .focusRequester(focusRequester), - label = { Text(text = label) }, + label = label, value = value, onValueChange = onValueChange, visualTransformation = when { @@ -86,16 +86,15 @@ fun MifosPasswordField( onClick = { showPasswordChange.invoke(!showPassword) }, ) { val imageVector = if (showPassword) { - MifosIcons.VisibilityOff + MifosIcons.OutlinedVisibilityOff } else { - MifosIcons.Visibility + MifosIcons.OutlinedVisibility } Icon( modifier = Modifier.semantics { showPasswordTestTag?.let { testTag = it } }, imageVector = imageVector, contentDescription = "togglePassword", - tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } }, diff --git a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/PasswordStrengthIndicator.kt b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/PasswordStrengthIndicator.kt similarity index 99% rename from feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/PasswordStrengthIndicator.kt rename to core/ui/src/commonMain/kotlin/org/mifospay/core/ui/PasswordStrengthIndicator.kt index ef44fb1f5..c764b2704 100644 --- a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/PasswordStrengthIndicator.kt +++ b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/PasswordStrengthIndicator.kt @@ -7,7 +7,7 @@ * * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md */ -package org.mifospay.feature.auth.signup +package org.mifospay.core.ui import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState diff --git a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupScreen.kt b/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupScreen.kt index 61e633f0b..29455000f 100644 --- a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupScreen.kt +++ b/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupScreen.kt @@ -64,6 +64,7 @@ import org.mifospay.core.designsystem.component.MifosScaffold import org.mifospay.core.designsystem.component.MifosTopAppBar import org.mifospay.core.designsystem.icon.MifosIcons import org.mifospay.core.ui.MifosPasswordField +import org.mifospay.core.ui.PasswordStrengthIndicator import org.mifospay.core.ui.utils.EventsEffect @OptIn(ExperimentalMaterial3Api::class) diff --git a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupViewModel.kt b/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupViewModel.kt index 5577de7bf..acf08f7e2 100644 --- a/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupViewModel.kt +++ b/feature/auth/src/commonMain/kotlin/org/mifospay/feature/auth/signup/SignupViewModel.kt @@ -27,6 +27,7 @@ import org.mifospay.core.data.util.Constants import org.mifospay.core.model.client.ClientAddress import org.mifospay.core.model.client.NewClient import org.mifospay.core.model.user.NewUser +import org.mifospay.core.ui.PasswordStrengthState import org.mifospay.core.ui.utils.BaseViewModel import org.mifospay.core.ui.utils.PasswordChecker import org.mifospay.core.ui.utils.PasswordStrength diff --git a/feature/editpassword/build.gradle.kts b/feature/editpassword/build.gradle.kts index 34b701da8..1f20fe47b 100644 --- a/feature/editpassword/build.gradle.kts +++ b/feature/editpassword/build.gradle.kts @@ -8,12 +8,22 @@ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md */ plugins { - alias(libs.plugins.mifospay.android.feature) - alias(libs.plugins.mifospay.android.library.compose) + alias(libs.plugins.mifospay.cmp.feature) + alias(libs.plugins.kotlin.parcelize) } android { namespace = "org.mifospay.feature.editpassword" } -dependencies {} \ No newline at end of file +kotlin { + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + } + } +} \ No newline at end of file diff --git a/feature/editpassword/consumer-rules.pro b/feature/editpassword/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/feature/editpassword/proguard-rules.pro b/feature/editpassword/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/feature/editpassword/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 \ No newline at end of file diff --git a/feature/editpassword/src/main/AndroidManifest.xml b/feature/editpassword/src/androidMain/AndroidManifest.xml similarity index 100% rename from feature/editpassword/src/main/AndroidManifest.xml rename to feature/editpassword/src/androidMain/AndroidManifest.xml diff --git a/feature/editpassword/src/main/res/values/strings.xml b/feature/editpassword/src/commonMain/composeResources/values/strings.xml similarity index 100% rename from feature/editpassword/src/main/res/values/strings.xml rename to feature/editpassword/src/commonMain/composeResources/values/strings.xml diff --git a/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt new file mode 100644 index 000000000..22b6988f1 --- /dev/null +++ b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.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.mifospay.feature.editpassword + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +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.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.launch +import mobile_wallet.feature.editpassword.generated.resources.Res +import mobile_wallet.feature.editpassword.generated.resources.feature_editpassword_change_password +import mobile_wallet.feature.editpassword.generated.resources.feature_editpassword_confirm_new_password +import mobile_wallet.feature.editpassword.generated.resources.feature_editpassword_new_password +import mobile_wallet.feature.editpassword.generated.resources.feature_editpassword_old_password +import mobile_wallet.feature.editpassword.generated.resources.feature_editpassword_save +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.BasicDialogState +import org.mifospay.core.designsystem.component.LoadingDialogState +import org.mifospay.core.designsystem.component.MifosBasicDialog +import org.mifospay.core.designsystem.component.MifosButton +import org.mifospay.core.designsystem.component.MifosLoadingDialog +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.ui.MifosPasswordField +import org.mifospay.core.ui.PasswordStrengthIndicator +import org.mifospay.core.ui.utils.EventsEffect + +@Composable +internal fun EditPasswordScreen( + navigateBack: () -> Unit, + onLogout: () -> Unit, + modifier: Modifier = Modifier, + viewModel: EditPasswordViewModel = koinViewModel(), +) { + val scope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + is EditPasswordEvent.NavigateBack -> navigateBack.invoke() + is EditPasswordEvent.OnLogoutUser -> onLogout.invoke() + is EditPasswordEvent.ShowToast -> { + scope.launch { + snackbarHostState.showSnackbar(event.message) + } + } + } + } + + Box(modifier) { + EditPasswordDialogs( + dialogState = state.dialogState, + onDismissRequest = remember(viewModel) { + { viewModel.trySendAction(EditPasswordAction.ErrorDialogDismiss) } + }, + ) + + EditPasswordScreenContent( + state = state, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + snackbarHostState = snackbarHostState, + ) + } +} + +@Composable +internal fun EditPasswordScreenContent( + modifier: Modifier = Modifier, + state: EditPasswordState, + onAction: (EditPasswordAction) -> Unit, + snackbarHostState: SnackbarHostState, +) { + MifosScaffold( + modifier = modifier, + topBarTitle = stringResource(Res.string.feature_editpassword_change_password), + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + backPress = { + onAction(EditPasswordAction.NavigateBackClick) + }, + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(paddingValues) + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + var showPassword by rememberSaveable { mutableStateOf(false) } + + MifosPasswordField( + value = state.currentPasswordInput, + label = stringResource(Res.string.feature_editpassword_old_password), + modifier = Modifier.fillMaxWidth(), + onValueChange = { + onAction(EditPasswordAction.CurrentPasswordChange(it)) + }, + showPassword = showPassword, + showPasswordChange = { showPassword = !showPassword }, + ) + + MifosPasswordField( + value = state.newPasswordInput, + label = stringResource(Res.string.feature_editpassword_new_password), + modifier = Modifier.fillMaxWidth(), + onValueChange = { + onAction(EditPasswordAction.NewPasswordChange(it)) + }, + showPassword = showPassword, + showPasswordChange = { showPassword = !showPassword }, + ) + PasswordStrengthIndicator( + modifier = Modifier, + state = state.passwordStrengthState, + currentCharacterCount = state.newPasswordInput.length, + ) + MifosPasswordField( + value = state.confirmPasswordInput, + label = stringResource(Res.string.feature_editpassword_confirm_new_password), + modifier = Modifier.fillMaxWidth(), + onValueChange = { + onAction(EditPasswordAction.ConfirmPasswordChange(it)) + }, + showPassword = showPassword, + showPasswordChange = { showPassword = !showPassword }, + ) + + MifosButton( + modifier = Modifier + .fillMaxWidth(), + color = MaterialTheme.colorScheme.primary, + enabled = true, + onClick = { + onAction(EditPasswordAction.SubmitClick) + }, + contentPadding = PaddingValues(12.dp), + ) { + Text( + text = stringResource(Res.string.feature_editpassword_save), + ) + } + } + } +} + +@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/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt new file mode 100644 index 000000000..c6adfbe03 --- /dev/null +++ b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt @@ -0,0 +1,267 @@ +/* + * 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.mifospay.feature.editpassword + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.mifospay.core.common.Parcelable +import org.mifospay.core.common.Parcelize +import org.mifospay.core.common.Result +import org.mifospay.core.data.repository.UserRepository +import org.mifospay.core.datastore.UserPreferencesRepository +import org.mifospay.core.ui.PasswordStrengthState +import org.mifospay.core.ui.utils.BaseViewModel +import org.mifospay.core.ui.utils.PasswordChecker +import org.mifospay.core.ui.utils.PasswordStrength +import org.mifospay.core.ui.utils.PasswordStrengthResult +import org.mifospay.feature.editpassword.EditPasswordAction.Internal.ReceivePasswordStrengthResult +import org.mifospay.feature.editpassword.EditPasswordAction.Internal.ReceiveUpdatePasswordResult +import org.mifospay.feature.editpassword.EditPasswordDialog.Error + +private const val KEY_STATE = "state" +private const val MIN_PASSWORD_LENGTH = 8 + +internal class EditPasswordViewModel( + private val userRepository: UserRepository, + userPreferencesRepository: UserPreferencesRepository, + savedStateHandle: SavedStateHandle, +) : BaseViewModel( + initialState = savedStateHandle[KEY_STATE] ?: EditPasswordState(), +) { + private val userInfo = userPreferencesRepository.userInfo.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + + private var passwordStrengthJob: Job = Job().apply { complete() } + + init { + stateFlow + .onEach { savedStateHandle[KEY_STATE] = it } + .launchIn(viewModelScope) + } + + override fun handleAction(action: EditPasswordAction) { + when (action) { + is EditPasswordAction.CurrentPasswordChange -> { + mutableStateFlow.update { + it.copy(currentPasswordInput = action.currentPassword) + } + } + + is EditPasswordAction.NewPasswordChange -> handleNewPasswordInput(action) + + is EditPasswordAction.ConfirmPasswordChange -> { + mutableStateFlow.update { + it.copy(confirmPasswordInput = action.confirmPassword) + } + } + + EditPasswordAction.ErrorDialogDismiss -> { + mutableStateFlow.update { + it.copy(dialogState = null) + } + } + + is ReceivePasswordStrengthResult -> handlePasswordStrengthResult(action) + + is ReceiveUpdatePasswordResult -> handleResult(action) + + EditPasswordAction.SubmitClick -> handleSubmitClick() + + is EditPasswordAction.NavigateBackClick -> { + sendEvent(EditPasswordEvent.NavigateBack) + } + } + } + + 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(ReceivePasswordStrengthResult(result)) + } + } + } + + private fun handlePasswordStrengthResult(action: 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 handleResult(action: ReceiveUpdatePasswordResult) { + when (val result = action.result) { + is Result.Success -> { + mutableStateFlow.update { it.copy(dialogState = null) } + sendEvent(EditPasswordEvent.ShowToast(result.data)) + sendEvent(EditPasswordEvent.OnLogoutUser) + } + + is Result.Error -> { + mutableStateFlow.update { + it.copy(dialogState = Error(result.exception.message.toString())) + } + } + + Result.Loading -> { + mutableStateFlow.update { it.copy(dialogState = EditPasswordDialog.Loading) } + } + } + } + + private fun handleSubmitClick() = when { + state.currentPasswordInput.isEmpty() -> { + mutableStateFlow.update { + it.copy(dialogState = Error("Please Enter Your Current Password")) + } + } + + state.isSamePassword -> { + mutableStateFlow.update { + it.copy(dialogState = Error("New password cannot be same as current password")) + } + } + + state.newPasswordInput.length < MIN_PASSWORD_LENGTH -> { + mutableStateFlow.update { + it.copy( + dialogState = Error( + "Password must be at least $MIN_PASSWORD_LENGTH characters long.", + ), + ) + } + } + + !state.isPasswordMatch -> { + mutableStateFlow.update { + it.copy(dialogState = Error("Passwords do not match.")) + } + } + + !state.isPasswordStrong -> { + mutableStateFlow.update { + it.copy(dialogState = Error("Password is weak.")) + } + } + + else -> initiateUpdatePassword() + } + + private fun initiateUpdatePassword() { + mutableStateFlow.update { + it.copy(dialogState = EditPasswordDialog.Loading) + } + + updatePassword(state.newPasswordInput) + } + + private fun updatePassword(newPassword: String) { + viewModelScope.launch { + val userId = requireNotNull(userInfo.value?.userId) + val result = userRepository.updateUserPassword(userId, newPassword) + + sendAction(ReceiveUpdatePasswordResult(result)) + } + } +} + +@Parcelize +internal data class EditPasswordState( + val currentPasswordInput: String = "", + 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 + + val isSamePassword: Boolean + get() = currentPasswordInput == newPasswordInput +} + +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 object OnLogoutUser : EditPasswordEvent + data class ShowToast(val message: String) : EditPasswordEvent +} + +internal sealed interface EditPasswordAction { + data class CurrentPasswordChange(val currentPassword: String) : 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: Result, + ) : Internal() + + data class ReceivePasswordStrengthResult( + val result: PasswordStrengthResult, + ) : Internal() + } +} diff --git a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt similarity index 65% rename from feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt rename to feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt index ed1432c0a..5592a2e2f 100644 --- a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt +++ b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/di/EditPasswordModule.kt @@ -9,17 +9,10 @@ */ package org.mifospay.feature.editpassword.di -import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module import org.mifospay.feature.editpassword.EditPasswordViewModel val EditPasswordModule = module { - viewModel { - EditPasswordViewModel( - mUseCaseHandler = get(), - mPreferencesHelper = get(), - authenticateUserUseCase = get(), - updateUserUseCase = get(), - ) - } + viewModelOf(::EditPasswordViewModel) } diff --git a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt similarity index 85% rename from feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt rename to feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt index e9ed63266..f7acdeda6 100644 --- a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt +++ b/feature/editpassword/src/commonMain/kotlin/org/mifospay/feature/editpassword/navigation/EditPasswordScreenNavigation.kt @@ -17,13 +17,13 @@ import org.mifospay.feature.editpassword.EditPasswordScreen const val EDIT_PASSWORD_ROUTE = "edit_password_route" fun NavGraphBuilder.editPasswordScreen( - onBackPress: () -> Unit, - onCancelChanges: () -> Unit, + navigateBack: () -> Unit, + onLogOut: () -> Unit, ) { composable(route = EDIT_PASSWORD_ROUTE) { EditPasswordScreen( - onBackPress = onBackPress, - onCancelChanges = onCancelChanges, + navigateBack = navigateBack, + onLogout = onLogOut, ) } } diff --git a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt b/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt deleted file mode 100644 index e49c4ede1..000000000 --- a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordScreen.kt +++ /dev/null @@ -1,225 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.editpassword - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -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.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import kotlinx.coroutines.launch -import org.koin.androidx.compose.koinViewModel -import org.mifospay.core.designsystem.component.MfPasswordTextField -import org.mifospay.core.designsystem.component.MifosButton -import org.mifospay.core.designsystem.component.MifosScaffold -import org.mifospay.core.designsystem.theme.MifosTheme - -@Composable -internal fun EditPasswordScreen( - onBackPress: () -> Unit, - onCancelChanges: () -> Unit, - modifier: Modifier = Modifier, - viewModel: EditPasswordViewModel = koinViewModel(), -) { - val editPasswordUiState by viewModel.editPasswordUiState.collectAsStateWithLifecycle() - EditPasswordScreen( - modifier = modifier, - editPasswordUiState = editPasswordUiState, - onCancelChanges = onCancelChanges, - onBackPress = onBackPress, - onSave = { currentPass, newPass, confirmPass -> - viewModel.updatePassword( - currentPassword = currentPass, - newPassword = newPass, - newPasswordRepeat = confirmPass, - ) - }, - ) -} - -@Composable -private fun EditPasswordScreen( - editPasswordUiState: EditPasswordUiState, - onCancelChanges: () -> Unit, - onBackPress: () -> Unit, - onSave: (currentPass: String, newPass: String, confirmPass: String) -> Unit, - modifier: Modifier = Modifier, -) { - val context = LocalContext.current - var currentPassword by rememberSaveable { mutableStateOf("") } - var newPassword by rememberSaveable { mutableStateOf("") } - var confirmNewPassword by rememberSaveable { mutableStateOf("") } - var isConfirmPasswordVisible by rememberSaveable { mutableStateOf(false) } - var isNewPasswordVisible by rememberSaveable { mutableStateOf(false) } - var isConfirmNewPasswordVisible by rememberSaveable { mutableStateOf(false) } - - val snackbarHostState = remember { SnackbarHostState() } - val coroutineScope = rememberCoroutineScope() - - val currentSnackbarHostState by rememberUpdatedState(snackbarHostState) - - LaunchedEffect(editPasswordUiState) { - when (editPasswordUiState) { - is EditPasswordUiState.Error -> { - val errorMessage = editPasswordUiState.message - coroutineScope.launch { - currentSnackbarHostState.showSnackbar(errorMessage) - } - } - - EditPasswordUiState.Loading -> {} - EditPasswordUiState.Success -> { - coroutineScope.launch { - currentSnackbarHostState.showSnackbar( - context.getString(R.string.feature_editpassword_password_changed_successfully), - ) - } - } - } - } - - MifosScaffold( - modifier = modifier, - topBarTitle = R.string.feature_editpassword_change_password, - snackbarHost = { - SnackbarHost(hostState = snackbarHostState) - }, - backPress = onBackPress, - scaffoldContent = { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - ) { - MfPasswordTextField( - password = currentPassword, - label = stringResource(R.string.feature_editpassword_current_password), - isError = false, - isPasswordVisible = isConfirmPasswordVisible, - onTogglePasswordVisibility = { - isConfirmPasswordVisible = !isConfirmPasswordVisible - }, - onPasswordChange = { currentPassword = it }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - MfPasswordTextField( - password = newPassword, - - label = stringResource(id = R.string.feature_editpassword_new_password), - isError = newPassword.isNotEmpty() && newPassword.length < 6, - isPasswordVisible = isNewPasswordVisible, - onTogglePasswordVisibility = { isNewPasswordVisible = !isNewPasswordVisible }, - onPasswordChange = { newPassword = it }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - errorMessage = if (newPassword.isNotEmpty() && newPassword.length < 6) { - stringResource( - id = R.string.feature_editpassword_password_length_error, - ) - } else { - null - }, - ) - MfPasswordTextField( - password = confirmNewPassword, - label = stringResource(id = R.string.feature_editpassword_confirm_new_password), - isError = newPassword != confirmNewPassword && confirmNewPassword.isNotEmpty(), - isPasswordVisible = isConfirmNewPasswordVisible, - onTogglePasswordVisibility = { - isConfirmNewPasswordVisible = !isConfirmNewPasswordVisible - }, - onPasswordChange = { confirmNewPassword = it }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - errorMessage = if (newPassword != - confirmNewPassword && confirmNewPassword.isNotEmpty() - ) { - stringResource( - id = R.string.feature_editpassword_password_mismatch_error, - ) - } else { - null - }, - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp, start = 16.dp, end = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - MifosButton( - onClick = { onCancelChanges.invoke() }, - modifier = Modifier - .weight(1f) - .padding(8.dp), - contentPadding = PaddingValues(16.dp), - content = { Text(text = stringResource(id = R.string.feature_editpassword_cancel)) }, - ) - MifosButton( - modifier = Modifier - .weight(1f) - .padding(8.dp), - onClick = { - onSave.invoke(currentPassword, newPassword, confirmNewPassword) - }, - contentPadding = PaddingValues(16.dp), - content = { Text(text = stringResource(id = R.string.feature_editpassword_save)) }, - ) - } - } - }, - ) -} - -class EditPasswordUiStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - EditPasswordUiState.Loading, - EditPasswordUiState.Success, - EditPasswordUiState.Error("Some Error Occurred"), - ) -} - -@Preview -@Composable -private fun EditPasswordScreenPreview( - @PreviewParameter(EditPasswordUiStateProvider::class) editPasswordUiState: EditPasswordUiState, -) { - MifosTheme { - EditPasswordScreen(editPasswordUiState = editPasswordUiState, {}, {}, { _, _, _ -> }) - } -} diff --git a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt b/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt deleted file mode 100644 index 99f780494..000000000 --- a/feature/editpassword/src/main/kotlin/org/mifospay/feature/editpassword/EditPasswordViewModel.kt +++ /dev/null @@ -1,124 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.editpassword - -import androidx.lifecycle.ViewModel -import com.mifospay.core.model.domain.user.UpdateUserEntityPassword -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import org.mifospay.core.common.Constants -import org.mifospay.core.data.base.UseCase -import org.mifospay.core.data.base.UseCaseHandler -import org.mifospay.core.data.domain.usecase.user.AuthenticateUser -import org.mifospay.core.data.domain.usecase.user.UpdateUser -import org.mifospay.core.datastore.PreferencesHelper - -@Suppress("NestedBlockDepth") -class EditPasswordViewModel( - private val mUseCaseHandler: UseCaseHandler, - private val mPreferencesHelper: PreferencesHelper, - private val authenticateUserUseCase: AuthenticateUser, - private val updateUserUseCase: UpdateUser, -) : ViewModel() { - - private val _editPasswordUiState = - MutableStateFlow(EditPasswordUiState.Loading) - val editPasswordUiState: StateFlow = _editPasswordUiState - - fun updatePassword( - currentPassword: String?, - newPassword: String?, - newPasswordRepeat: String?, - ) { - _editPasswordUiState.value = EditPasswordUiState.Loading - if (isNotEmpty(currentPassword) && isNotEmpty(newPassword) && - isNotEmpty(newPasswordRepeat) - ) { - when { - currentPassword == newPassword -> { - _editPasswordUiState.value = - EditPasswordUiState.Error(Constants.ERROR_PASSWORDS_CANT_BE_SAME) - } - - newPassword?.let { - newPasswordRepeat?.let { it1 -> - isNewPasswordValid( - it, - it1, - ) - } - } == true -> { - if (currentPassword != null) { - updatePassword(currentPassword, newPassword) - } - } - - else -> { - _editPasswordUiState.value = - EditPasswordUiState.Error(Constants.ERROR_VALIDATING_PASSWORD) - } - } - } else { - _editPasswordUiState.value = - EditPasswordUiState.Error(Constants.ERROR_FIELDS_CANNOT_BE_EMPTY) - } - } - - private fun isNotEmpty(str: String?): Boolean { - return !str.isNullOrEmpty() - } - - private fun isNewPasswordValid(newPassword: String, newPasswordRepeat: String): Boolean { - return newPassword == newPasswordRepeat - } - - private fun updatePassword(currentPassword: String, newPassword: String) { - // authenticate and then update - mUseCaseHandler.execute( - authenticateUserUseCase, - AuthenticateUser.RequestValues( - mPreferencesHelper.username, - currentPassword, - ), - object : UseCase.UseCaseCallback { - override fun onSuccess(response: AuthenticateUser.ResponseValue) { - mUseCaseHandler.execute( - updateUserUseCase, - UpdateUser.RequestValues( - UpdateUserEntityPassword( - newPassword, - ), - mPreferencesHelper.userId.toInt(), - ), - object : UseCase.UseCaseCallback { - override fun onSuccess(response: UpdateUser.ResponseValue?) { - _editPasswordUiState.value = EditPasswordUiState.Success - } - - override fun onError(message: String) { - _editPasswordUiState.value = EditPasswordUiState.Error(message) - } - }, - ) - } - - override fun onError(message: String) { - _editPasswordUiState.value = EditPasswordUiState.Error("Wrong Password") - } - }, - ) - } -} - -sealed interface EditPasswordUiState { - data object Loading : EditPasswordUiState - data object Success : EditPasswordUiState - data class Error(val message: String) : EditPasswordUiState -} diff --git a/feature/faq/build.gradle.kts b/feature/faq/build.gradle.kts index 053183d86..d152c1e63 100644 --- a/feature/faq/build.gradle.kts +++ b/feature/faq/build.gradle.kts @@ -8,12 +8,22 @@ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md */ plugins { - alias(libs.plugins.mifospay.android.feature) - alias(libs.plugins.mifospay.android.library.compose) + alias(libs.plugins.mifospay.cmp.feature) + alias(libs.plugins.kotlin.parcelize) } android { namespace = "org.mifospay.feature.faq" } -dependencies { } \ No newline at end of file +kotlin { + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + } + } +} \ No newline at end of file diff --git a/feature/faq/consumer-rules.pro b/feature/faq/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/feature/faq/proguard-rules.pro b/feature/faq/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/feature/faq/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 \ No newline at end of file diff --git a/feature/faq/src/main/AndroidManifest.xml b/feature/faq/src/androidMain/AndroidManifest.xml similarity index 100% rename from feature/faq/src/main/AndroidManifest.xml rename to feature/faq/src/androidMain/AndroidManifest.xml diff --git a/feature/faq/src/main/res/values/strings.xml b/feature/faq/src/commonMain/composeResources/values/strings.xml similarity index 93% rename from feature/faq/src/main/res/values/strings.xml rename to feature/faq/src/commonMain/composeResources/values/strings.xml index 08fc8c193..34625452c 100644 --- a/feature/faq/src/main/res/values/strings.xml +++ b/feature/faq/src/commonMain/composeResources/values/strings.xml @@ -17,5 +17,5 @@ Navigate to Payments section. You will find your payment history under the History tab. To make a transfer, navigate to Payments section.Under the Send tab, from there you can choose the type of transfer, add the amount and submit. Navigate to Finance section. Click on Add Card under the Cards tab. - Frequently Asked Question + FAQ's \ No newline at end of file diff --git a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQ.kt b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQ.kt similarity index 73% rename from feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQ.kt rename to feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQ.kt index 9ae8a88da..a64fcec30 100644 --- a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQ.kt +++ b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQ.kt @@ -9,7 +9,10 @@ */ package org.mifospay.feature.faq +import org.jetbrains.compose.resources.StringResource + internal data class FAQ( - var question: Int, - var answer: Int? = null, + val faqId: Int, + val question: StringResource, + val answer: StringResource, ) diff --git a/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQViewModel.kt b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQViewModel.kt new file mode 100644 index 000000000..b242768a0 --- /dev/null +++ b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FAQViewModel.kt @@ -0,0 +1,75 @@ +/* + * 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.mifospay.feature.faq + +import kotlinx.coroutines.flow.update +import mobile_wallet.feature.faq.generated.resources.Res +import mobile_wallet.feature.faq.generated.resources.feature_faq_answer1 +import mobile_wallet.feature.faq.generated.resources.feature_faq_answer2 +import mobile_wallet.feature.faq.generated.resources.feature_faq_answer3 +import mobile_wallet.feature.faq.generated.resources.feature_faq_answer4 +import mobile_wallet.feature.faq.generated.resources.feature_faq_question1 +import mobile_wallet.feature.faq.generated.resources.feature_faq_question2 +import mobile_wallet.feature.faq.generated.resources.feature_faq_question3 +import mobile_wallet.feature.faq.generated.resources.feature_faq_question4 +import org.mifospay.core.ui.utils.BaseViewModel + +internal class FAQViewModel : BaseViewModel( + initialState = FaqState(sampleFaqList), +) { + override fun handleAction(action: FaqAction) { + when (action) { + is FaqAction.ExpandFaq -> { + mutableStateFlow.update { + if (it.expandedFaq == action.faqId) { + it.copy(expandedFaq = null) + } else { + it.copy(expandedFaq = action.faqId) + } + } + } + + is FaqAction.NavigateBack -> { + sendEvent(FaqEvent.OnNavigateBack) + } + } + } +} + +internal data class FaqState( + val faqList: List, + val expandedFaq: Int? = null, +) + +internal sealed interface FaqEvent { + data object OnNavigateBack : FaqEvent +} + +internal sealed interface FaqAction { + data class ExpandFaq(val faqId: Int) : FaqAction + data object NavigateBack : FaqAction +} + +/** + * Retrieves a list of Frequently Asked Questions (FAQs). + * + * Currently, the FAQs are statically defined within this function. This is a temporary + * implementation for demonstration or testing purposes. In the future, this method will + * be updated to fetch the FAQ data from a backend service once the backend functionality + * is implemented. This will allow for dynamic and up-to-date FAQ content. + * + * @return A list of [FAQ] objects containing the questions and answers. + */ +internal val sampleFaqList = listOf( + FAQ(1, Res.string.feature_faq_question1, Res.string.feature_faq_answer1), + FAQ(2, Res.string.feature_faq_question2, Res.string.feature_faq_answer2), + FAQ(3, Res.string.feature_faq_question3, Res.string.feature_faq_answer3), + FAQ(4, Res.string.feature_faq_question4, Res.string.feature_faq_answer4), +) diff --git a/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FaqScreen.kt b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FaqScreen.kt new file mode 100644 index 000000000..49ee4af00 --- /dev/null +++ b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/FaqScreen.kt @@ -0,0 +1,203 @@ +/* + * 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.mifospay.feature.faq + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +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.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +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.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.feature.faq.generated.resources.Res +import mobile_wallet.feature.faq.generated.resources.feature_faq +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.MifosTopBar +import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.core.designsystem.theme.NewUi +import org.mifospay.core.ui.utils.EventsEffect + +@Composable +internal fun FaqScreenRoute( + navigateBack: () -> Unit, + modifier: Modifier = Modifier, + faqViewModel: FAQViewModel = koinViewModel(), +) { + val state by faqViewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(faqViewModel) { event -> + when (event) { + is FaqEvent.OnNavigateBack -> navigateBack.invoke() + } + } + + FaqScreen( + modifier = modifier, + state = state, + onAction = remember(faqViewModel) { + { faqViewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun FaqScreen( + state: FaqState, + onAction: (FaqAction) -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize(), + ) { + MifosTopBar( + topBarTitle = stringResource(Res.string.feature_faq), + backPress = { + onAction(FaqAction.NavigateBack) + }, + ) + LazyColumn( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + ) { + itemsIndexed( + items = state.faqList, + key = { _, item -> item.faqId }, + ) { i, faqItem -> + FaqItemScreen( + faq = faqItem, + onExpand = { faqId -> + onAction(FaqAction.ExpandFaq(faqId)) + }, + isExpanded = state.expandedFaq == faqItem.faqId, + ) + + if (i != state.faqList.lastIndex) { + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + thickness = 1.dp, + color = NewUi.onSurface.copy(alpha = 0.05f), + ) + } + } + } + } +} + +@Composable +private fun FaqItemScreen( + faq: FAQ, + isExpanded: Boolean, + modifier: Modifier = Modifier, + onExpand: (Int) -> Unit, +) { + val density = LocalDensity.current + + Card( + modifier = modifier.fillMaxWidth(), + onClick = { onExpand(faq.faqId) }, + colors = CardDefaults.cardColors( + containerColor = Color.Transparent, + ), + shape = RoundedCornerShape(0.dp), + ) { + Column( + modifier = Modifier.padding( + horizontal = 20.dp, + vertical = 25.dp, + ), + ) { + Row { + Text( + text = stringResource(faq.question), + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.W500, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + ) + + Icon( + imageVector = MifosIcons.KeyboardArrowDown, + contentDescription = "drop down", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .scale(1f, if (isExpanded) -1f else 1f), + ) + } + Row { + AnimatedVisibility( + modifier = Modifier.weight(1f), + visible = isExpanded, + enter = slideInVertically { + with(density) { -40.dp.roundToPx() } + } + expandVertically( + expandFrom = Alignment.Top, + ) + fadeIn( + initialAlpha = 0.3f, + ), + exit = slideOutVertically() + shrinkVertically() + fadeOut(), + ) { + Text( + text = stringResource(faq.answer), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp) + .weight(1f), + ) + } + + Spacer(modifier = Modifier.weight(.1f)) + } + } + } +} + +@Preview +@Composable +private fun FaqScreenPreview() { + FaqScreen( + state = FaqState(sampleFaqList), + onAction = {}, + ) +} diff --git a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/di/FaqModule.kt b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/di/FaqModule.kt similarity index 83% rename from feature/faq/src/main/kotlin/org/mifospay/feature/faq/di/FaqModule.kt rename to feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/di/FaqModule.kt index 844a6e8cc..0d216e42b 100644 --- a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/di/FaqModule.kt +++ b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/di/FaqModule.kt @@ -9,13 +9,10 @@ */ package org.mifospay.feature.faq.di -import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module import org.mifospay.feature.faq.FAQViewModel val FaqModule = module { - - viewModel { - FAQViewModel() - } + viewModelOf(::FAQViewModel) } diff --git a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/navigation/FAQNavigation.kt b/feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/navigation/FAQNavigation.kt similarity index 100% rename from feature/faq/src/main/kotlin/org/mifospay/feature/faq/navigation/FAQNavigation.kt rename to feature/faq/src/commonMain/kotlin/org/mifospay/feature/faq/navigation/FAQNavigation.kt diff --git a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQViewModel.kt b/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQViewModel.kt deleted file mode 100644 index 1a5376313..000000000 --- a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FAQViewModel.kt +++ /dev/null @@ -1,34 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.faq - -import androidx.lifecycle.ViewModel - -internal class FAQViewModel : ViewModel() { - - /** - * Retrieves a list of Frequently Asked Questions (FAQs). - * - * Currently, the FAQs are statically defined within this function. This is a temporary - * implementation for demonstration or testing purposes. In the future, this method will - * be updated to fetch the FAQ data from a backend service once the backend functionality - * is implemented. This will allow for dynamic and up-to-date FAQ content. - * - * @return A list of [FAQ] objects containing the questions and answers. - */ - fun getFAQ(): List { - return listOf( - FAQ(R.string.feature_faq_question1, R.string.feature_faq_answer1), - FAQ(R.string.feature_faq_question2, R.string.feature_faq_answer2), - FAQ(R.string.feature_faq_question3, R.string.feature_faq_answer3), - FAQ(R.string.feature_faq_question4, R.string.feature_faq_answer4), - ) - } -} diff --git a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FaqScreen.kt b/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FaqScreen.kt deleted file mode 100644 index a3a7a5426..000000000 --- a/feature/faq/src/main/kotlin/org/mifospay/feature/faq/FaqScreen.kt +++ /dev/null @@ -1,79 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.faq - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import org.koin.androidx.compose.koinViewModel -import org.mifospay.core.designsystem.component.MifosTopBar -import org.mifospay.core.ui.FaqItemScreen - -@Composable -internal fun FaqScreenRoute( - navigateBack: () -> Unit, - modifier: Modifier = Modifier, - faqViewModel: FAQViewModel = koinViewModel(), -) { - FaqScreen( - modifier = modifier, - navigateBack = navigateBack, - faqList = faqViewModel.getFAQ(), - ) -} - -@Composable -private fun FaqScreen( - navigateBack: () -> Unit, - faqList: List, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .fillMaxSize(), - ) { - MifosTopBar( - topBarTitle = R.string.feature_faq_frequently_asked_questions, - backPress = { navigateBack.invoke() }, - ) - LazyColumn( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - ) { - itemsIndexed(items = faqList) { _, faqItem -> - FaqItemScreen( - question = stringResource(id = faqItem.question), - answer = faqItem.answer?.let { stringResource(id = it) }, - ) - } - } - } -} - -@Preview(showSystemUi = true) -@Composable -private fun FaqScreenPreview() { - FaqScreen( - {}, - listOf( - FAQ(R.string.feature_faq_question1, R.string.feature_faq_answer1), - FAQ(R.string.feature_faq_question2, R.string.feature_faq_answer2), - FAQ(R.string.feature_faq_question3, R.string.feature_faq_answer3), - FAQ(R.string.feature_faq_question4, R.string.feature_faq_answer4), - ), - ) -} diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 0de327314..d37f3abd5 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -8,12 +8,25 @@ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md */ plugins { - alias(libs.plugins.mifospay.android.feature) - alias(libs.plugins.mifospay.android.library.compose) + alias(libs.plugins.mifospay.cmp.feature) + alias(libs.plugins.kotlin.parcelize) } android { namespace = "org.mifospay.feature.settings" } -dependencies {} \ No newline at end of file +kotlin { + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + + implementation(libs.koin.compose.viewmodel) + implementation(libs.koin.compose) + } + } +} \ No newline at end of file diff --git a/feature/settings/consumer-rules.pro b/feature/settings/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/feature/settings/proguard-rules.pro b/feature/settings/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/feature/settings/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 \ No newline at end of file diff --git a/feature/settings/src/main/AndroidManifest.xml b/feature/settings/src/androidMain/AndroidManifest.xml similarity index 100% rename from feature/settings/src/main/AndroidManifest.xml rename to feature/settings/src/androidMain/AndroidManifest.xml diff --git a/feature/settings/src/commonMain/composeResources/drawable/outline_logout.xml b/feature/settings/src/commonMain/composeResources/drawable/outline_logout.xml new file mode 100644 index 000000000..0e2ed8d8c --- /dev/null +++ b/feature/settings/src/commonMain/composeResources/drawable/outline_logout.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/feature/settings/src/commonMain/composeResources/drawable/outline_password.xml b/feature/settings/src/commonMain/composeResources/drawable/outline_password.xml new file mode 100644 index 000000000..1195645e8 --- /dev/null +++ b/feature/settings/src/commonMain/composeResources/drawable/outline_password.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/feature/settings/src/commonMain/composeResources/drawable/outline_pin.xml b/feature/settings/src/commonMain/composeResources/drawable/outline_pin.xml new file mode 100644 index 000000000..09f262303 --- /dev/null +++ b/feature/settings/src/commonMain/composeResources/drawable/outline_pin.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/feature/settings/src/main/res/values/strings.xml b/feature/settings/src/commonMain/composeResources/values/strings.xml similarity index 96% rename from feature/settings/src/main/res/values/strings.xml rename to feature/settings/src/commonMain/composeResources/values/strings.xml index ee6c46246..1cc87fe9e 100644 --- a/feature/settings/src/main/res/values/strings.xml +++ b/feature/settings/src/commonMain/composeResources/values/strings.xml @@ -11,6 +11,7 @@ Are you sure you want to disable account? Notification Settings + FAQ Settings Change Password Change Passcode diff --git a/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsScreen.kt b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsScreen.kt new file mode 100644 index 000000000..c7bd23ef2 --- /dev/null +++ b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsScreen.kt @@ -0,0 +1,245 @@ +/* + * 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.mifospay.feature.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.feature.settings.generated.resources.Res +import mobile_wallet.feature.settings.generated.resources.feature_settings_change_passcode +import mobile_wallet.feature.settings.generated.resources.feature_settings_change_password +import mobile_wallet.feature.settings.generated.resources.feature_settings_disable_account +import mobile_wallet.feature.settings.generated.resources.feature_settings_faq +import mobile_wallet.feature.settings.generated.resources.feature_settings_log_out +import mobile_wallet.feature.settings.generated.resources.feature_settings_notification_settings +import mobile_wallet.feature.settings.generated.resources.feature_settings_settings +import mobile_wallet.feature.settings.generated.resources.outline_logout +import mobile_wallet.feature.settings.generated.resources.outline_password +import mobile_wallet.feature.settings.generated.resources.outline_pin +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.resources.vectorResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.BasicDialogState +import org.mifospay.core.designsystem.component.LoadingDialogState +import org.mifospay.core.designsystem.component.MifosBasicDialog +import org.mifospay.core.designsystem.component.MifosLoadingDialog +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.core.designsystem.theme.NewUi +import org.mifospay.core.ui.utils.EventsEffect + +@Composable +internal fun SettingsScreenRoute( + backPress: () -> Unit, + onEditPassword: () -> Unit, + onLogout: () -> Unit, + onChangePasscode: () -> Unit, + navigateToFaqScreen: () -> Unit, + navigateToNotificationScreen: () -> Unit, + modifier: Modifier = Modifier, + viewmodel: SettingsViewModel = koinViewModel(), +) { + val state by viewmodel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewmodel) { event -> + when (event) { + SettingsEvent.OnNavigateBack -> backPress.invoke() + SettingsEvent.OnNavigateToChangePasscodeScreen -> onChangePasscode.invoke() + SettingsEvent.OnNavigateToEditPasswordScreen -> onEditPassword.invoke() + SettingsEvent.OnNavigateToFaqScreen -> navigateToFaqScreen.invoke() + SettingsEvent.OnNavigateToLogout -> onLogout.invoke() + SettingsEvent.OnNavigateToNotificationScreen -> navigateToNotificationScreen.invoke() + } + } + + Box(modifier) { + SettingsDialogs( + dialogState = state.dialogState, + onDismissRequest = remember(viewmodel) { + { viewmodel.trySendAction(SettingsAction.DismissDialog) } + }, + ) + + SettingsScreenContent( + modifier = Modifier, + onAction = viewmodel::trySendAction, + ) + } +} + +@Composable +private fun SettingsScreenContent( + modifier: Modifier = Modifier, + onAction: (SettingsAction) -> Unit, +) { + MifosScaffold( + modifier = modifier, + topBarTitle = stringResource(Res.string.feature_settings_settings), + backPress = { + onAction(SettingsAction.NavigateBack) + }, + ) { contentPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(contentPadding) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + SettingsCardItem( + title = stringResource(Res.string.feature_settings_notification_settings), + icon = MifosIcons.OutlinedNotifications, + onClick = { + onAction(SettingsAction.NavigateToNotificationSettings) + }, + ) + + SettingsCardItem( + title = stringResource(Res.string.feature_settings_faq), + icon = MifosIcons.OutlinedInfo, + onClick = { + onAction(SettingsAction.NavigateToFaqScreen) + }, + ) + + SettingsCardItem( + title = stringResource(Res.string.feature_settings_change_password), + icon = vectorResource(Res.drawable.outline_password), + onClick = { + onAction(SettingsAction.ChangePassword) + }, + ) + + SettingsCardItem( + title = stringResource(Res.string.feature_settings_change_passcode), + icon = vectorResource(Res.drawable.outline_pin), + onClick = { + onAction(SettingsAction.ChangePasscode) + }, + ) + + SettingsCardItem( + title = stringResource(Res.string.feature_settings_log_out), + icon = vectorResource(Res.drawable.outline_logout), + onClick = { + onAction(SettingsAction.Logout) + }, + ) + + SettingsCardItem( + title = stringResource(Res.string.feature_settings_disable_account), + icon = MifosIcons.OutlinedLock, + color = Color.Red, + onClick = { + onAction(SettingsAction.DisableAccount) + }, + ) + } + } +} + +@Composable +private fun SettingsCardItem( + title: String, + icon: ImageVector, + modifier: Modifier = Modifier, + color: Color = NewUi.onSurface, + onClick: () -> Unit, +) { + ListItem( + leadingContent = { + Icon( + imageVector = icon, + contentDescription = title, + ) + }, + headlineContent = { + Text(text = title) + }, + trailingContent = { + Icon( + imageVector = MifosIcons.ChevronRight, + contentDescription = null, + ) + }, + modifier = modifier.clickable { + onClick() + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent, + headlineColor = color, + ), + ) +} + +@Composable +private fun SettingsDialogs( + dialogState: DialogState?, + onDismissRequest: () -> Unit, +) { + when (dialogState) { + is DialogState.Error -> MifosBasicDialog( + visibilityState = BasicDialogState.Shown( + message = dialogState.message, + ), + onDismissRequest = onDismissRequest, + ) + + is DialogState.Loading -> MifosLoadingDialog( + visibilityState = LoadingDialogState.Shown, + ) + + is DialogState.DisableAccount -> MifosBasicDialog( + visibilityState = BasicDialogState.Shown( + title = stringResource(dialogState.title), + message = stringResource(dialogState.message), + ), + onConfirm = dialogState.onConfirm, + onDismissRequest = onDismissRequest, + ) + + is DialogState.Logout -> MifosBasicDialog( + visibilityState = BasicDialogState.Shown( + title = stringResource(dialogState.title), + message = stringResource(dialogState.message), + ), + onConfirm = dialogState.onConfirm, + onDismissRequest = onDismissRequest, + ) + + null -> Unit + } +} + +@Preview +@Composable +private fun SettingsScreenPreview() { + SettingsScreenContent( + onAction = {}, + ) +} diff --git a/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt new file mode 100644 index 000000000..08b9bc325 --- /dev/null +++ b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt @@ -0,0 +1,189 @@ +/* + * 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.mifospay.feature.settings + +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import mobile_wallet.feature.settings.generated.resources.Res +import mobile_wallet.feature.settings.generated.resources.feature_settings_alert_disable_account +import mobile_wallet.feature.settings.generated.resources.feature_settings_alert_disable_account_desc +import mobile_wallet.feature.settings.generated.resources.feature_settings_empty +import mobile_wallet.feature.settings.generated.resources.feature_settings_log_out_title +import org.jetbrains.compose.resources.StringResource +import org.mifospay.core.common.Result +import org.mifospay.core.data.repository.SavingsAccountRepository +import org.mifospay.core.datastore.UserPreferencesRepository +import org.mifospay.core.model.client.Client +import org.mifospay.core.ui.utils.BaseViewModel +import org.mifospay.feature.settings.SettingsAction.Internal.DisableAccountResult + +class SettingsViewModel( + private val userPreferencesRepository: UserPreferencesRepository, + private val repository: SavingsAccountRepository, +) : BaseViewModel( + initialState = run { + val client = requireNotNull(userPreferencesRepository.client.value) + + SettingsState( + client = client, + dialogState = null, + ) + }, +) { + + override fun handleAction(action: SettingsAction) { + when (action) { + is SettingsAction.NavigateBack -> { + sendEvent(SettingsEvent.OnNavigateBack) + } + + is SettingsAction.NavigateToFaqScreen -> { + sendEvent(SettingsEvent.OnNavigateToFaqScreen) + } + + is SettingsAction.ChangePasscode -> { + sendEvent(SettingsEvent.OnNavigateToChangePasscodeScreen) + } + + is SettingsAction.ChangePassword -> { + sendEvent(SettingsEvent.OnNavigateToEditPasswordScreen) + } + + is SettingsAction.NavigateToNotificationSettings -> { + sendEvent(SettingsEvent.OnNavigateToNotificationScreen) + } + + is SettingsAction.DismissDialog -> { + mutableStateFlow.update { + it.copy(dialogState = null) + } + } + + is SettingsAction.DisableAccount -> { + mutableStateFlow.update { + it.copy( + dialogState = DialogState.DisableAccount( + title = Res.string.feature_settings_alert_disable_account, + message = Res.string.feature_settings_alert_disable_account_desc, + onConfirm = { + trySendAction(SettingsAction.Internal.DisableAccount) + }, + ), + ) + } + } + + is SettingsAction.Logout -> { + mutableStateFlow.update { + it.copy( + dialogState = DialogState.Logout( + title = Res.string.feature_settings_log_out_title, + message = Res.string.feature_settings_empty, + onConfirm = { + trySendAction(SettingsAction.DismissDialog) + sendEvent(SettingsEvent.OnNavigateToLogout) + }, + ), + ) + } + } + + is SettingsAction.Internal.DisableAccount -> handleDisableAccount() + + is DisableAccountResult -> handleDisableAccountResult(action) + } + } + + private fun handleDisableAccount() { + mutableStateFlow.update { + it.copy(dialogState = DialogState.Loading) + } + + // TODO:: this shouldn't work, we need account id to block account + viewModelScope.launch { + val result = repository.blockAccount(state.client.clientId) + sendAction(DisableAccountResult(result)) + } + } + + private fun handleDisableAccountResult(action: DisableAccountResult) { + when (action.result) { + is Result.Error -> { + mutableStateFlow.update { + it.copy( + dialogState = DialogState.Error( + action.result.exception.message ?: "Error", + ), + ) + } + } + + is Result.Loading -> { + mutableStateFlow.update { + it.copy(dialogState = DialogState.Loading) + } + } + + is Result.Success -> { + mutableStateFlow.update { + it.copy(dialogState = null) + } + sendEvent(SettingsEvent.OnNavigateToLogout) + } + } + } +} + +data class SettingsState( + val client: Client, + val dialogState: DialogState? = null, +) + +sealed interface DialogState { + data object Loading : DialogState + data class Error(val message: String) : DialogState + data class DisableAccount( + val title: StringResource, + val message: StringResource, + val onConfirm: () -> Unit, + ) : DialogState + + data class Logout( + val title: StringResource, + val message: StringResource, + val onConfirm: () -> Unit, + ) : DialogState +} + +sealed interface SettingsEvent { + data object OnNavigateBack : SettingsEvent + data object OnNavigateToEditPasswordScreen : SettingsEvent + data object OnNavigateToChangePasscodeScreen : SettingsEvent + data object OnNavigateToLogout : SettingsEvent + data object OnNavigateToFaqScreen : SettingsEvent + data object OnNavigateToNotificationScreen : SettingsEvent +} + +sealed interface SettingsAction { + data object NavigateBack : SettingsAction + data object Logout : SettingsAction + data object DisableAccount : SettingsAction + data object ChangePasscode : SettingsAction + data object ChangePassword : SettingsAction + data object NavigateToFaqScreen : SettingsAction + data object NavigateToNotificationSettings : SettingsAction + data object DismissDialog : SettingsAction + + sealed interface Internal : SettingsAction { + data object DisableAccount : Internal + data class DisableAccountResult(val result: Result) : Internal + } +} diff --git a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt similarity index 68% rename from feature/settings/src/main/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt rename to feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt index 5b912b4bf..a368a37c3 100644 --- a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt +++ b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/di/SettingsModule.kt @@ -9,16 +9,10 @@ */ package org.mifospay.feature.settings.di -import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module import org.mifospay.feature.settings.SettingsViewModel val SettingsModule = module { - viewModel { - SettingsViewModel( - mUseCaseHandler = get(), - mLocalRepository = get(), - blockUnblockCommandUseCase = get(), - ) - } + viewModelOf(::SettingsViewModel) } diff --git a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt similarity index 80% rename from feature/settings/src/main/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt rename to feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt index 56ec4b7a6..7d900b029 100644 --- a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/commonMain/kotlin/org/mifospay/feature/settings/navigation/SettingsNavigation.kt @@ -23,16 +23,20 @@ fun NavController.navigateToSettings(navOptions: NavOptions? = null) { fun NavGraphBuilder.settingsScreen( onBackPress: () -> Unit, - navigateToEditPasswordScreen: () -> Unit, onLogout: () -> Unit, onChangePasscode: () -> Unit, + navigateToEditPasswordScreen: () -> Unit, + navigateToFaqScreen: () -> Unit, + navigateToNotificationScreen: () -> Unit, ) { composable(route = SETTINGS_ROUTE) { SettingsScreenRoute( backPress = onBackPress, - navigateToEditPasswordScreen = navigateToEditPasswordScreen, + onEditPassword = navigateToEditPasswordScreen, onLogout = onLogout, onChangePasscode = onChangePasscode, + navigateToFaqScreen = navigateToFaqScreen, + navigateToNotificationScreen = navigateToNotificationScreen, ) } } diff --git a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/DialogManager.kt b/feature/settings/src/main/kotlin/org/mifospay/feature/settings/DialogManager.kt deleted file mode 100644 index cb7d64052..000000000 --- a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/DialogManager.kt +++ /dev/null @@ -1,49 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.settings - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import org.mifospay.core.designsystem.component.MifosDialogBox -import org.mifospay.core.ui.utility.DialogState -import org.mifospay.core.ui.utility.DialogType - -@Composable -internal fun DialogManager( - dialogState: DialogState, - onDismiss: () -> Unit, - modifier: Modifier = Modifier, -) { - when (dialogState.type) { - DialogType.DISABLE_ACCOUNT -> MifosDialogBox( - showDialogState = true, - onDismiss = onDismiss, - title = R.string.feature_settings_alert_disable_account, - confirmButtonText = R.string.feature_settings_ok, - onConfirm = dialogState.onConfirm, - dismissButtonText = R.string.feature_settings_cancel, - message = R.string.feature_settings_alert_disable_account_desc, - modifier = modifier, - ) - - DialogType.LOGOUT -> MifosDialogBox( - showDialogState = true, - onDismiss = onDismiss, - title = R.string.feature_settings_log_out_title, - confirmButtonText = R.string.feature_settings_yes, - onConfirm = dialogState.onConfirm, - dismissButtonText = R.string.feature_settings_no, - message = R.string.feature_settings_empty, - modifier = modifier, - ) - - DialogType.NONE -> {} - } -} diff --git a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsScreen.kt b/feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsScreen.kt deleted file mode 100644 index 42b487b8d..000000000 --- a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsScreen.kt +++ /dev/null @@ -1,201 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.settings - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -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.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import org.koin.androidx.compose.koinViewModel -import org.mifospay.core.designsystem.component.MifosCard -import org.mifospay.core.designsystem.component.MifosTopBar -import org.mifospay.core.ui.utility.DialogState -import org.mifospay.core.ui.utility.DialogType - -@Composable -fun SettingsScreenRoute( - backPress: () -> Unit, - navigateToEditPasswordScreen: () -> Unit, - onLogout: () -> Unit, - onChangePasscode: () -> Unit, - modifier: Modifier = Modifier, - viewmodel: SettingsViewModel = koinViewModel(), -) { - var dialogState by remember { mutableStateOf(DialogState()) } - val paddingValues = PaddingValues(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 4.dp) - - DialogManager( - dialogState = dialogState, - onDismiss = { dialogState = DialogState(type = DialogType.NONE) }, - ) - - Scaffold( - topBar = { - MifosTopBar( - topBarTitle = R.string.feature_settings_settings, - backPress = { backPress.invoke() }, - ) - }, - modifier = modifier, - ) { contentPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surface) - .padding(contentPadding), - ) { - Row( - modifier = Modifier.padding(paddingValues), - ) { - MifosCard( - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface), - ) { - Text( - text = stringResource(id = R.string.feature_settings_notification_settings), - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - style = TextStyle( - fontSize = 18.sp, - color = MaterialTheme.colorScheme.onSurface, - ), - ) - } - } - - Row( - modifier = Modifier - .padding(paddingValues), - ) { - MifosCard( - onClick = { onChangePasswordClicked(navigateToEditPasswordScreen) }, - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface), - ) { - Text( - text = stringResource(id = R.string.feature_settings_change_password), - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - style = TextStyle( - fontSize = 18.sp, - color = MaterialTheme.colorScheme.onSurface, - ), - ) - } - } - - Row( - modifier = Modifier - .padding(paddingValues), - ) { - MifosCard( - onClick = onChangePasscode, - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface), - ) { - Text( - text = stringResource(id = R.string.feature_settings_change_passcode), - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - style = TextStyle( - fontSize = 18.sp, - color = MaterialTheme.colorScheme.onSurface, - ), - ) - } - } - - Column( - modifier = Modifier - .fillMaxSize() - .weight(1f), - ) {} - - Row(modifier = Modifier.padding(8.dp)) { - MifosCard( - onClick = { - dialogState = DialogState( - type = DialogType.DISABLE_ACCOUNT, - onConfirm = { viewmodel.disableAccount() }, - ) - }, - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.error), - ) { - Text( - text = stringResource(id = R.string.feature_settings_disable_account).uppercase(), - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.onError, - ) - } - } - - Row(modifier = Modifier.padding(8.dp)) { - MifosCard( - onClick = { - dialogState = DialogState( - type = DialogType.LOGOUT, - onConfirm = { - viewmodel.logout() - onLogout() - }, - ) - }, - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.secondary), - ) { - Text( - text = stringResource(id = R.string.feature_settings_log_out).uppercase(), - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.onSecondary, - ) - } - } - } - } -} - -private fun onChangePasswordClicked(navigateToEditPasswordScreen: () -> Unit) { - navigateToEditPasswordScreen.invoke() -} - -@Preview(showSystemUi = true) -@Composable -private fun SettingsScreenPreview() { - SettingsScreenRoute( - backPress = {}, - navigateToEditPasswordScreen = {}, - onLogout = {}, - onChangePasscode = {}, - viewmodel = koinViewModel(), - ) -} diff --git a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt deleted file mode 100644 index 2ab18b0bd..000000000 --- a/feature/settings/src/main/kotlin/org/mifospay/feature/settings/SettingsViewModel.kt +++ /dev/null @@ -1,45 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.settings - -import androidx.lifecycle.ViewModel -import org.mifospay.core.data.base.UseCase -import org.mifospay.core.data.base.UseCaseHandler -import org.mifospay.core.data.domain.usecase.account.BlockUnblockCommand -import org.mifospay.core.data.repository.local.LocalRepository - -class SettingsViewModel( - private val mUseCaseHandler: UseCaseHandler, - private val mLocalRepository: LocalRepository, - private val blockUnblockCommandUseCase: BlockUnblockCommand, -) : ViewModel() { - - fun logout() { - mLocalRepository.preferencesHelper.clear() - } - - fun disableAccount() { - // keep it disabled for now - if (0 * 67 == 0) { - return - } - mUseCaseHandler.execute( - blockUnblockCommandUseCase, - BlockUnblockCommand.RequestValues( - mLocalRepository.clientDetails.clientId, - "block", - ), - object : UseCase.UseCaseCallback { - override fun onSuccess(response: BlockUnblockCommand.ResponseValue) {} - override fun onError(message: String) {} - }, - ) - } -} diff --git a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt index 3e45c83bf..107fa2e9b 100644 --- a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt +++ b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt @@ -1802,6 +1802,8 @@ | | | \--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) | | +--- project :core:designsystem (*) | | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) @@ -1884,8 +1886,6 @@ | | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) -| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) -| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) | | +--- org.jetbrains.kotlin:kotlin-reflect:2.0.20 | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) @@ -1905,6 +1905,8 @@ | | +--- project :core:ui (*) | | +--- project :core:designsystem (*) | | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) @@ -1919,8 +1921,6 @@ | | +--- org.jetbrains.compose.material:material-icons-extended:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) -| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) -| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) | | +--- org.jetbrains.kotlin:kotlin-reflect:2.0.20 (*) | | +--- org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.2 (*) @@ -1944,6 +1944,8 @@ | | +--- project :core:ui (*) | | +--- project :core:designsystem (*) | | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) | | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) | | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) @@ -1957,8 +1959,99 @@ | | +--- org.jetbrains.compose.material3:material3:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) +| | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) +| +--- project :feature:settings +| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.6 (*) +| | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 (*) +| | +--- androidx.tracing:tracing-ktx:1.3.0-alpha02 (*) +| | +--- io.insert-koin:koin-bom:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-android:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-compose:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-navigation:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-core-viewmodel:4.0.0-RC2 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) +| | +--- io.insert-koin:koin-core:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*) +| | +--- project :core:ui (*) +| | +--- project :core:designsystem (*) +| | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2 (*) +| | +--- org.jetbrains.androidx.savedstate:savedstate:1.2.2 (*) +| | +--- org.jetbrains.androidx.core:core-bundle:1.0.1 (*) +| | +--- org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8 (*) +| | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.material3:material3:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) +| | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) +| +--- project :feature:faq +| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.6 (*) +| | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 (*) +| | +--- androidx.tracing:tracing-ktx:1.3.0-alpha02 (*) +| | +--- io.insert-koin:koin-bom:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-android:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-compose:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-navigation:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-core-viewmodel:4.0.0-RC2 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) +| | +--- io.insert-koin:koin-core:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*) +| | +--- project :core:ui (*) +| | +--- project :core:designsystem (*) +| | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) | | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2 (*) +| | +--- org.jetbrains.androidx.savedstate:savedstate:1.2.2 (*) +| | +--- org.jetbrains.androidx.core:core-bundle:1.0.1 (*) +| | +--- org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8 (*) +| | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.material3:material3:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) +| | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) +| +--- project :feature:editpassword +| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.6 (*) +| | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 (*) +| | +--- androidx.tracing:tracing-ktx:1.3.0-alpha02 (*) +| | +--- io.insert-koin:koin-bom:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-android:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-compose:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-navigation:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-core-viewmodel:4.0.0-RC2 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) +| | +--- io.insert-koin:koin-core:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*) +| | +--- project :core:ui (*) +| | +--- project :core:designsystem (*) +| | +--- project :core:data (*) | | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2 (*) +| | +--- org.jetbrains.androidx.savedstate:savedstate:1.2.2 (*) +| | +--- org.jetbrains.androidx.core:core-bundle:1.0.1 (*) +| | +--- org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8 (*) +| | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.material3:material3:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) | | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*) diff --git a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt index 1f966c624..c4a671ffd 100644 --- a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt +++ b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt @@ -9,7 +9,10 @@ :core:network :core:ui :feature:auth +:feature:editpassword +:feature:faq :feature:home +:feature:settings :libs:country-code-picker :libs:mifos-passcode :mifospay-shared diff --git a/mifospay-android/src/test/java/org/mifospay/KoinModulesCheck.kt b/mifospay-android/src/test/java/org/mifospay/KoinModulesCheck.kt deleted file mode 100644 index f9c811a25..000000000 --- a/mifospay-android/src/test/java/org/mifospay/KoinModulesCheck.kt +++ /dev/null @@ -1,76 +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-wallet/blob/master/LICENSE.md - */ -package org.mifospay - -import android.content.Context -import androidx.lifecycle.SavedStateHandle -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngine -import org.koin.test.AutoCloseKoinTest -import org.koin.test.verify.verify -import org.mifos.core.network.services.KtorAuthenticationService -import org.mifos.library.passcode.data.PasscodeManager -import org.mifospay.core.data.repository.auth.UserDataRepository -import org.mifospay.core.datastore.PreferencesHelper -import org.mifospay.core.network.FineractApiManager -import org.mifospay.core.network.SelfServiceApiManager -import org.mifospay.di.KoinModules -import kotlin.test.Test - -class KoinModulesCheck : AutoCloseKoinTest() { - - @Test - fun checkKoinModules() { - val koinModules = KoinModules() - - koinModules.libsModule.verify( - extraTypes = listOf(Context::class), - ) - koinModules.commonModules.verify() - koinModules.analyticsModules.verify() - koinModules.networkModules.verify( - extraTypes = listOf( - HttpClientEngine::class, - HttpClientConfig::class, - ), - ) - koinModules.featureModules.verify( - extraTypes = listOf( - PreferencesHelper::class, - FineractApiManager::class, - SelfServiceApiManager::class, - KtorAuthenticationService::class, - SavedStateHandle::class, - ), - ) - koinModules.coreDataStoreModules.verify( - extraTypes = listOf( - PreferencesHelper::class, - Context::class, - ), - ) - koinModules.mifosPayModule.verify( - extraTypes = listOf( - UserDataRepository::class, - PasscodeManager::class, - Context::class, - ), - ) - koinModules.dataModules.verify( - extraTypes = listOf( - FineractApiManager::class, - SelfServiceApiManager::class, - KtorAuthenticationService::class, - PreferencesHelper::class, - Map::class, - ), - ) - } -} diff --git a/mifospay-shared/build.gradle.kts b/mifospay-shared/build.gradle.kts index a46ed42b9..d635b8572 100644 --- a/mifospay-shared/build.gradle.kts +++ b/mifospay-shared/build.gradle.kts @@ -37,6 +37,9 @@ kotlin { api(projects.feature.auth) api(projects.libs.mifosPasscode) api(projects.feature.home) + api(projects.feature.settings) + api(projects.feature.faq) + api(projects.feature.editpassword) } desktopMain.dependencies { diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt index 139309e20..bc40481dd 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt @@ -22,7 +22,10 @@ import org.mifospay.core.domain.di.DomainModule import org.mifospay.core.network.di.LocalModule import org.mifospay.core.network.di.NetworkModule import org.mifospay.feature.auth.di.AuthModule +import org.mifospay.feature.editpassword.di.EditPasswordModule +import org.mifospay.feature.faq.di.FaqModule import org.mifospay.feature.home.di.HomeModule +import org.mifospay.feature.settings.di.SettingsModule import org.mifospay.shared.MifosPayViewModel object KoinModules { @@ -48,6 +51,9 @@ object KoinModules { includes( AuthModule, HomeModule, + SettingsModule, + FaqModule, + EditPasswordModule, ) } private val LibraryModule = module { diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt index 5bcf04167..84dae5f3d 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt @@ -12,8 +12,13 @@ package org.mifospay.shared.navigation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost +import org.mifospay.feature.editpassword.navigation.editPasswordScreen +import org.mifospay.feature.editpassword.navigation.navigateToEditPassword +import org.mifospay.feature.faq.navigation.faqScreen +import org.mifospay.feature.faq.navigation.navigateToFAQ import org.mifospay.feature.home.navigation.HOME_ROUTE import org.mifospay.feature.home.navigation.homeScreen +import org.mifospay.feature.settings.navigation.settingsScreen import org.mifospay.shared.ui.MifosAppState @Composable @@ -35,5 +40,23 @@ internal fun MifosNavHost( onRequest = {}, onPay = {}, ) + + settingsScreen( + onBackPress = navController::navigateUp, + onLogout = onClickLogout, + onChangePasscode = {}, + navigateToEditPasswordScreen = navController::navigateToEditPassword, + navigateToFaqScreen = navController::navigateToFAQ, + navigateToNotificationScreen = {}, + ) + + faqScreen( + navigateBack = navController::navigateUp, + ) + + editPasswordScreen( + navigateBack = navController::navigateUp, + onLogOut = onClickLogout, + ) } } diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt index e4c244c8d..c088cd5c0 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt @@ -9,7 +9,6 @@ */ package org.mifospay.shared.ui -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -22,8 +21,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -37,10 +34,7 @@ import androidx.compose.material3.TopAppBarDefaults 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.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent @@ -52,9 +46,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hierarchy import mobile_wallet.mifospay_shared.generated.resources.Res -import mobile_wallet.mifospay_shared.generated.resources.faq import mobile_wallet.mifospay_shared.generated.resources.not_connected -import mobile_wallet.mifospay_shared.generated.resources.settings import org.jetbrains.compose.resources.stringResource import org.mifospay.core.data.util.NetworkMonitor import org.mifospay.core.data.util.TimeZoneMonitor @@ -67,6 +59,7 @@ import org.mifospay.core.designsystem.component.MifosNavigationRail import org.mifospay.core.designsystem.component.MifosNavigationRailItem import org.mifospay.core.designsystem.icon.MifosIcons import org.mifospay.core.designsystem.theme.LocalGradientColors +import org.mifospay.feature.settings.navigation.navigateToSettings import org.mifospay.shared.navigation.MifosNavHost import org.mifospay.shared.utils.TopLevelDestination @@ -87,6 +80,7 @@ internal fun MifosApp( ) val snackbarHostState = remember { SnackbarHostState() } + val destination = appState.currentTopLevelDestination val isOffline by appState.isOffline.collectAsStateWithLifecycle() @@ -107,7 +101,7 @@ internal fun MifosApp( contentColor = MaterialTheme.colorScheme.onBackground, snackbarHost = { SnackbarHost(snackbarHostState) }, bottomBar = { - if (appState.shouldShowBottomBar) { + if (appState.shouldShowBottomBar && destination != null) { MifosBottomBar( destinations = appState.topLevelDestinations, destinationsWithUnreadResources = emptySet(), @@ -129,14 +123,13 @@ internal fun MifosApp( ), ), ) { - if (appState.shouldShowNavRail) { + if (appState.shouldShowNavRail && destination != null) { MifosNavRail( destinations = appState.topLevelDestinations, destinationsWithUnreadResources = emptySet(), onNavigateToDestination = appState::navigateToTopLevelDestination, currentDestination = appState.currentDestination, - modifier = - Modifier + modifier = Modifier .testTag("NiaNavRail") .safeDrawingPadding(), ) @@ -144,13 +137,15 @@ internal fun MifosApp( Column(Modifier.fillMaxSize()) { // Show the top app bar on top level destinations. - val destination = appState.currentTopLevelDestination if (destination != null) { MifosAppBar( title = stringResource(destination.titleText), onClickLogout = onClickLogout, onNavigateToFaq = {}, - onNavigateToSettings = {}, + onNavigateToSettings = { + appState.navController.navigateToSettings() + }, + onNavigateToEditProfile = {}, destination = destination, ) } @@ -166,63 +161,6 @@ internal fun MifosApp( } } -// TODO:: This could be removed -@Composable -private fun HomeMenu( - showHomeMenuOption: Boolean, - onDismissRequest: () -> Unit, - onClickLogout: () -> Unit, - onNavigateToFaq: () -> Unit, - onNavigateToSettings: () -> Unit, - modifier: Modifier = Modifier, -) { - DropdownMenu( - modifier = modifier.background(color = MaterialTheme.colorScheme.surface), - expanded = showHomeMenuOption, - onDismissRequest = onDismissRequest, - ) { - DropdownMenuItem( - text = { - Text( - text = stringResource(Res.string.faq), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - onClick = { - onDismissRequest() - onNavigateToFaq() - }, - ) - - DropdownMenuItem( - text = { - Text( - text = stringResource(Res.string.settings), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - onClick = { - onDismissRequest() - onNavigateToSettings() - }, - ) - - // TODO:: this could be removed, just added for testing - DropdownMenuItem( - text = { - Text( - text = "Logout", - color = MaterialTheme.colorScheme.onSurface, - ) - }, - onClick = { - onDismissRequest() - onClickLogout() - }, - ) - } -} - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun MifosAppBar( @@ -230,11 +168,10 @@ private fun MifosAppBar( onClickLogout: () -> Unit, onNavigateToFaq: () -> Unit, onNavigateToSettings: () -> Unit, + onNavigateToEditProfile: () -> Unit, destination: TopLevelDestination?, modifier: Modifier = Modifier, ) { - var showHomeMenuOption by rememberSaveable { mutableStateOf(false) } - TopAppBar( title = { Text(text = title) }, actions = { @@ -243,34 +180,19 @@ private fun MifosAppBar( TopLevelDestination.HOME -> { IconBox( icon = MifosIcons.SettingsOutlined, - onClick = { - showHomeMenuOption = true - // appState.navController.navigateToSettings() - }, + onClick = onNavigateToSettings, ) } TopLevelDestination.PROFILE -> { IconBox( icon = MifosIcons.Edit2, - onClick = { - showHomeMenuOption = true - // appState.navController.navigateToEditProfile() - }, + onClick = onNavigateToEditProfile, ) } else -> {} } - - // TODO:: this could be removed - HomeMenu( - showHomeMenuOption = showHomeMenuOption, - onDismissRequest = { showHomeMenuOption = false }, - onClickLogout = onClickLogout, - onNavigateToFaq = onNavigateToFaq, - onNavigateToSettings = onNavigateToSettings, - ) } }, colors = TopAppBarDefaults.topAppBarColors( diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt index 270854104..2e8b8b20d 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.datetime.TimeZone import org.mifospay.core.data.util.NetworkMonitor import org.mifospay.core.data.util.TimeZoneMonitor +import org.mifospay.feature.home.navigation.HOME_ROUTE import org.mifospay.shared.utils.TopLevelDestination @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @@ -73,11 +74,11 @@ internal class MifosAppState( val currentTopLevelDestination: TopLevelDestination? @Composable get() = when (currentDestination?.route) { -// HOME_ROUTE -> TopLevelDestination.HOME + HOME_ROUTE -> TopLevelDestination.HOME // PAYMENTS_ROUTE -> TopLevelDestination.PAYMENTS // FINANCE_ROUTE -> TopLevelDestination.FINANCE // PROFILE_ROUTE -> TopLevelDestination.PROFILE - else -> TopLevelDestination.HOME + else -> null } val shouldShowBottomBar: Boolean