diff --git a/feature/settings/src/commonMain/composeResources/values/strings.xml b/feature/settings/src/commonMain/composeResources/values/strings.xml index f4c2c024d..d619c56c9 100644 --- a/feature/settings/src/commonMain/composeResources/values/strings.xml +++ b/feature/settings/src/commonMain/composeResources/values/strings.xml @@ -83,8 +83,11 @@ App Info Find the app version details - Logout - Logout from all devices + Logout Now + Are You Sure You Want To Logout? + Logging out will end your current and all active sessions for security purposes. You’ll need to log in again to access your account. + I don’t want to logout? + Back to home Settings @@ -93,7 +96,6 @@ No internet connection. Invalid profile image format. Customer Account: %1$s - Are you sure to logout? About Us diff --git a/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/componenets/MifosLogoutDilaog.kt b/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/componenets/MifosLogoutDilaog.kt new file mode 100644 index 000000000..613cf3c93 --- /dev/null +++ b/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/componenets/MifosLogoutDilaog.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.feature.settings.componenets + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import mifos_mobile.feature.settings.generated.resources.Res +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_action +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_description +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_message +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_now +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_title +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.mifos.mobile.core.designsystem.component.MifosButton +import org.mifos.mobile.core.designsystem.theme.AppColors +import org.mifos.mobile.core.designsystem.theme.DesignToken +import org.mifos.mobile.core.designsystem.theme.MifosTypography + +@Composable +fun MifosLogoutDialog( + visibilityState: LogoutDialogState, +): Unit = when (visibilityState) { + LogoutDialogState.Hidden -> Unit + is LogoutDialogState.Shown -> { + AlertDialog( + onDismissRequest = visibilityState.onDismiss, + confirmButton = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy( + space = DesignToken.spacing.medium, + alignment = Alignment.CenterVertically, + ), + ) { + MifosButton( + modifier = Modifier + .fillMaxWidth() + .height(DesignToken.sizes.buttonHeight), + shape = DesignToken.shapes.medium, + onClick = visibilityState.onLogout, + ) { + Text( + text = stringResource(Res.string.feature_settings_logout_now), + style = MifosTypography.titleMedium, + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy( + space = DesignToken.spacing.extraSmall, + alignment = Alignment.CenterHorizontally, + ), + ) { + Text( + text = stringResource(visibilityState.message), + style = MifosTypography.bodySmallEmphasized, + color = MaterialTheme.colorScheme.secondary, + ) + + Text( + text = stringResource(visibilityState.messageActionText), + style = MifosTypography.bodySmallEmphasized, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.clickable { + visibilityState.onNavigateToHome.invoke() + }, + ) + } + } + }, + title = { + Text( + text = stringResource(visibilityState.title), + style = MifosTypography.headlineSmallEmphasized, + color = AppColors.customBlack, + modifier = Modifier + .fillMaxWidth() + .testTag("AlertTitleText"), + ) + }, + text = { + Text( + text = stringResource(visibilityState.description), + style = MifosTypography.labelMediumEmphasized, + color = MaterialTheme.colorScheme.secondary, + modifier = Modifier + .fillMaxWidth() + .testTag("AlertContentText"), + ) + }, + containerColor = Color.White, + shape = DesignToken.shapes.medium, + ) + } +} + +sealed interface LogoutDialogState { + + data object Hidden : LogoutDialogState + + data class Shown( + val description: StringResource, + val title: StringResource, + val message: StringResource, + val messageActionText: StringResource, + val onLogout: () -> Unit, + val onNavigateToHome: () -> Unit, + val onDismiss: () -> Unit, + ) : LogoutDialogState +} + +@Preview +@Composable +fun MifosLogoutDialogPreview() { + MifosLogoutDialog( + visibilityState = LogoutDialogState.Shown( + description = Res.string.feature_settings_logout_description, + title = Res.string.feature_settings_logout_title, + message = Res.string.feature_settings_logout_message, + messageActionText = Res.string.feature_settings_logout_action, + onLogout = { }, + onNavigateToHome = { }, + onDismiss = { }, + ), + ) +} diff --git a/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsScreen.kt b/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsScreen.kt index ad1b7cd51..5ca1037e3 100644 --- a/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsScreen.kt +++ b/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsScreen.kt @@ -37,8 +37,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.collections.immutable.ImmutableList import mifos_mobile.feature.settings.generated.resources.Res -import mifos_mobile.feature.settings.generated.resources.feature_settings_action_logout import mifos_mobile.feature.settings.generated.resources.feature_settings_customer_account_no +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_action import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_message import mifos_mobile.feature.settings.generated.resources.feature_settings_top_bar_title import org.jetbrains.compose.resources.stringResource @@ -53,6 +53,8 @@ import org.mifos.mobile.core.ui.component.MifosActionCard import org.mifos.mobile.core.ui.component.MifosProgressIndicator import org.mifos.mobile.core.ui.component.MifosUserImage import org.mifos.mobile.core.ui.utils.EventsEffect +import org.mifos.mobile.feature.settings.componenets.LogoutDialogState +import org.mifos.mobile.feature.settings.componenets.MifosLogoutDialog import org.mifos.mobile.feature.settings.componenets.SettingsItems @Composable @@ -104,14 +106,17 @@ private fun SettingsDialog( SettingsState.DialogState.Loading -> MifosProgressIndicator() - SettingsState.DialogState.Logout -> { - MifosBasicDialog( - visibilityState = BasicDialogState.Shown( - title = stringResource(Res.string.feature_settings_action_logout), - message = stringResource(Res.string.feature_settings_logout_message), + is SettingsState.DialogState.Logout -> { + MifosLogoutDialog( + visibilityState = LogoutDialogState.Shown( + description = state.dialogState.message, + title = state.dialogState.title, + message = Res.string.feature_settings_logout_message, + messageActionText = Res.string.feature_settings_logout_action, + onLogout = { onAction(SettingsAction.Logout) }, + onNavigateToHome = { onAction(SettingsAction.OnNavigateBack) }, + onDismiss = { onAction(SettingsAction.DismissDialog) }, ), - onDismissRequest = { onAction(SettingsAction.DismissDialog) }, - onConfirm = { onAction(SettingsAction.Logout) }, ) } diff --git a/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsViewModel.kt b/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsViewModel.kt index 5c7dcba22..9ae5efd62 100644 --- a/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsViewModel.kt +++ b/feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/settings/SettingsViewModel.kt @@ -17,9 +17,12 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mifos_mobile.feature.settings.generated.resources.Res import mifos_mobile.feature.settings.generated.resources.feature_settings_error_fetching_client +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_description +import mifos_mobile.feature.settings.generated.resources.feature_settings_logout_title import org.jetbrains.compose.resources.StringResource import org.mifos.mobile.core.common.DataState import org.mifos.mobile.core.data.repository.HomeRepository +import org.mifos.mobile.core.data.repository.UserDataRepository import org.mifos.mobile.core.datastore.UserPreferencesRepository import org.mifos.mobile.core.model.entity.client.Client import org.mifos.mobile.core.ui.utils.BaseViewModel @@ -44,7 +47,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi internal class SettingsViewModel( private val homeRepositoryImpl: HomeRepository, private val userPreferencesRepositoryImpl: UserPreferencesRepository, -// private val userDataRepositoryImpl: UserDataRepository, + private val userDataRepositoryImpl: UserDataRepository, ) : BaseViewModel( initialState = run { SettingsState( @@ -85,8 +88,8 @@ internal class SettingsViewModel( when (action) { SettingsAction.OnNavigateBack -> sendEvent(SettingsEvents.NavigateBack) SettingsAction.DismissDialog -> setDialogState(null) - SettingsAction.LogoutDialog -> setDialogState(SettingsState.DialogState.Logout) - SettingsAction.Logout -> handleLogout() + SettingsAction.LogoutDialog -> handleLogout() + SettingsAction.Logout -> handleInternalLogOut() is SettingsAction.Internal.ReceiveClientInfo -> handleClientResponse(action.dataState) is SettingsAction.Internal.ReceiveClientImage -> handleClientImageResponse(action.dataState) is SettingsAction.NavigateTo -> sendEvent(SettingsEvents.NavigateTo(action.item)) @@ -96,14 +99,42 @@ internal class SettingsViewModel( /** * Handles the user logout process. * - * This function would typically perform the following actions: - * 1. Clear user-specific data, such as authentication tokens and client information, from the repository. - * 2. Reset the application state to its initial, logged-out state. - * 3. Send a navigation event to redirect the user to the login or welcome screen. + * This function initiates the logout flow by updating the state to show a confirmation dialog. + * The actual logout logic (clearing data, navigating) is handled by a separate function + * after the user confirms the action. + * + * This function sets the dialog state to a [SettingsState.DialogState.Logout] with + * a title and a descriptive message to prompt user confirmation. */ private fun handleLogout() { + mutableStateFlow.update { + it.copy( + dialogState = SettingsState.DialogState.Logout( + title = Res.string.feature_settings_logout_title, + message = Res.string.feature_settings_logout_description, + ), + ) + } + } + + /** + * Executes the internal logout logic. + * + * This function first dismisses any active dialog by setting the dialog state to null. + * It then triggers the actual logout operation on the `userDataRepository`. This includes + * clearing user-specific data and tokens from the repository, which is a critical + * step in securely logging out the user. + * + * The commented-out code `userDataRepository.logout(...)` represents the intended + * functionality to be implemented. The `reason` parameter provides context for the logout event. + */ + private fun handleInternalLogOut() { + mutableStateFlow.update { + it.copy(dialogState = null) + } + viewModelScope.launch { - userPreferencesRepositoryImpl.logOut() + userDataRepositoryImpl.logOut() } } @@ -239,7 +270,10 @@ internal data class SettingsState( data object Loading : DialogState /** Represents a logout dialog. */ - data object Logout : DialogState + data class Logout( + val title: StringResource, + val message: StringResource, + ) : DialogState } }