Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@
<string name="feature_settings_action_app_info">App Info</string>
<string name="feature_settings_action_app_info_tip">Find the app version details</string>

<string name="feature_settings_action_logout">Logout</string>
<string name="feature_settings_action_logout_tip">Logout from all devices</string>
<string name="feature_settings_logout_now">Logout Now</string>
<string name="feature_settings_logout_title">Are You Sure You Want To Logout? </string>
<string name="feature_settings_logout_description">Logging out will end your current and all active sessions for security purposes. You’ll need to log in again to access your account.</string>
<string name="feature_settings_logout_message">I don’t want to logout?</string>
<string name="feature_settings_logout_action">Back to home</string>

<string name="feature_settings_top_bar_title">Settings</string>

Expand All @@ -93,7 +96,6 @@
<string name="feature_settings_internet_not_connected">No internet connection.</string>
<string name="feature_settings_error_invalid_profile_image">Invalid profile image format.</string>
<string name="feature_settings_customer_account_no">Customer Account: %1$s</string>
<string name="feature_settings_logout_message">Are you sure to logout?</string>

<!-- About Screen -->
<string name="feature_settings_about_topbar_title">About Us</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = { },
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<SettingsState, SettingsEvents, SettingsAction>(
initialState = run {
SettingsState(
Expand Down Expand Up @@ -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))
Expand All @@ -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()
}
}

Expand Down Expand Up @@ -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
}
}

Expand Down
Loading