Skip to content

Commit d54c6fb

Browse files
fix: Settings Module Enhancements (Theme Change and Clean Up) (#2403)
1 parent 9bd9944 commit d54c6fb

File tree

36 files changed

+194
-403
lines changed

36 files changed

+194
-403
lines changed

cmp-navigation/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ kotlin {
4646
implementation(projects.feature.pathTracking)
4747
// implementation(projects.feature.report)
4848
implementation(projects.feature.savings)
49-
implementation(projects.feature.splash)
5049
implementation(projects.feature.settings)
5150
implementation(projects.feature.search)
5251

cmp-navigation/src/commonMain/kotlin/cmp/navigation/App.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ fun App(
291291
FeatureNavHost(
292292
appState = appState,
293293
padding = paddingValues,
294-
onClickLogout = onClickLogout,
295294
modifier = Modifier,
296295
)
297296
if (dialogState) {
@@ -300,7 +299,10 @@ fun App(
300299
showDialogState = dialogState,
301300
confirmButtonText = "LogOut",
302301
onDismiss = { dialogState = false },
303-
onConfirm = onClickLogout,
302+
onConfirm = {
303+
dialogState = false
304+
onClickLogout()
305+
},
304306
dismissButtonText = "Cancel",
305307
)
306308
}

cmp-navigation/src/commonMain/kotlin/cmp/navigation/ComposeApp.kt

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,63 @@
99
*/
1010
package cmp.navigation
1111

12+
import androidx.compose.foundation.isSystemInDarkTheme
1213
import androidx.compose.runtime.Composable
1314
import androidx.compose.runtime.getValue
1415
import androidx.compose.ui.Modifier
16+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1517
import androidx.navigation.compose.rememberNavController
18+
import cmp.navigation.navigation.NavGraphRoute.AUTH_GRAPH
19+
import cmp.navigation.navigation.NavGraphRoute.MAIN_GRAPH
1620
import cmp.navigation.navigation.RootNavGraph
1721
import com.mifos.core.data.util.NetworkMonitor
22+
import com.mifos.core.datastore.model.AppTheme
1823
import com.mifos.core.designsystem.theme.MifosTheme
1924
import org.koin.compose.koinInject
25+
import org.koin.compose.viewmodel.koinViewModel
2026

2127
@Composable
2228
fun ComposeApp(
2329
modifier: Modifier = Modifier,
2430
networkMonitor: NetworkMonitor = koinInject(),
31+
viewModel: ComposeAppViewModel = koinViewModel(),
2532
) {
26-
MifosTheme {
33+
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
34+
val navController = rememberNavController()
35+
36+
val navDestination = when (uiState) {
37+
is MainUiState.Loading -> AUTH_GRAPH
38+
is MainUiState.Success -> if ((uiState as MainUiState.Success).isAuthenticated) {
39+
MAIN_GRAPH
40+
} else {
41+
AUTH_GRAPH
42+
}
43+
}
44+
45+
val isDarkTheme = when (uiState) {
46+
MainUiState.Loading -> isSystemInDarkTheme()
47+
is MainUiState.Success -> {
48+
when ((uiState as MainUiState.Success).appTheme) {
49+
AppTheme.SYSTEM -> isSystemInDarkTheme()
50+
AppTheme.LIGHT -> false
51+
AppTheme.DARK -> true
52+
}
53+
}
54+
}
55+
56+
MifosTheme(isDarkTheme) {
2757
RootNavGraph(
2858
networkMonitor = networkMonitor,
29-
navHostController = rememberNavController(),
59+
navHostController = navController,
60+
startDestination = navDestination,
61+
onClickLogout = {
62+
viewModel.logout()
63+
navController.navigate(AUTH_GRAPH) {
64+
popUpTo(navController.graph.id) {
65+
inclusive = true
66+
}
67+
}
68+
},
3069
modifier = modifier,
3170
)
3271
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
9+
*/
10+
package cmp.navigation
11+
12+
import androidx.lifecycle.ViewModel
13+
import androidx.lifecycle.viewModelScope
14+
import com.mifos.core.datastore.UserPreferencesRepository
15+
import com.mifos.core.datastore.model.AppTheme
16+
import kotlinx.coroutines.flow.SharingStarted
17+
import kotlinx.coroutines.flow.StateFlow
18+
import kotlinx.coroutines.flow.combine
19+
import kotlinx.coroutines.flow.stateIn
20+
import kotlinx.coroutines.launch
21+
22+
class ComposeAppViewModel(
23+
private val userPreferencesRepository: UserPreferencesRepository,
24+
) : ViewModel() {
25+
26+
private val userDataFlow = userPreferencesRepository.userData
27+
private val appThemeFlow = userPreferencesRepository.appTheme
28+
29+
val uiState: StateFlow<MainUiState> = combine(userDataFlow, appThemeFlow) { userData, appTheme ->
30+
MainUiState.Success(
31+
isAuthenticated = userData.isAuthenticated,
32+
appTheme = appTheme,
33+
)
34+
}.stateIn(
35+
scope = viewModelScope,
36+
initialValue = MainUiState.Loading,
37+
started = SharingStarted.WhileSubscribed(5_000),
38+
)
39+
40+
fun logout() {
41+
viewModelScope.launch {
42+
userPreferencesRepository.logOut()
43+
}
44+
}
45+
}
46+
47+
sealed interface MainUiState {
48+
data object Loading : MainUiState
49+
data class Success(val isAuthenticated: Boolean, val appTheme: AppTheme) : MainUiState
50+
}

cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package cmp.navigation.di
1111

12+
import cmp.navigation.ComposeAppViewModel
1213
import com.mifos.core.common.network.di.DispatchersModule
1314
import com.mifos.core.data.di.RepositoryModule
1415
import com.mifos.core.datastore.di.PreferencesModule
@@ -26,10 +27,10 @@ import com.mifos.feature.pathTracking.di.PathTrackingModule
2627
import com.mifos.feature.savings.di.SavingsModule
2728
import com.mifos.feature.search.di.SearchModule
2829
import com.mifos.feature.settings.di.SettingsModule
29-
import com.mifos.feature.splash.di.SplashModule
3030
import com.mifos.room.di.DaoModule
3131
import com.mifos.room.di.HelperModule
3232
import com.mifos.room.di.PlatformSpecificDatabaseModule
33+
import org.koin.core.module.dsl.viewModelOf
3334
import org.koin.dsl.module
3435

3536
object KoinModules {
@@ -52,6 +53,9 @@ object KoinModules {
5253
NetworkModule,
5354
)
5455
}
56+
private val sharedModule = module {
57+
viewModelOf(::ComposeAppViewModel)
58+
}
5559

5660
private val featureModules = module {
5761
includes(
@@ -72,12 +76,12 @@ object KoinModules {
7276
// ReportModule,
7377
SavingsModule,
7478
SearchModule,
75-
SplashModule,
7679
SettingsModule,
7780
)
7881
}
7982

8083
val allModules = listOf(
84+
sharedModule,
8185
commonModules,
8286
domainModule,
8387
dataModules,

cmp-navigation/src/commonMain/kotlin/cmp/navigation/navigation/FeatureNavHost.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import cmp.navigation.AppState
1717
import com.mifos.feature.about.navigation.aboutNavGraph
1818
import com.mifos.feature.activate.navigation.activateScreen
1919
import com.mifos.feature.activate.navigation.navigateToActivateScreen
20+
import com.mifos.feature.auth.navigation.navigateToLogin
2021
import com.mifos.feature.center.navigation.centerNavGraph
2122
import com.mifos.feature.center.navigation.navigateCreateCenterScreenRoute
2223
import com.mifos.feature.checker.inbox.task.navigation.checkerInboxTaskNavGraph
@@ -35,7 +36,6 @@ import com.mifos.feature.settings.navigation.settingsScreen
3536
@Composable
3637
internal fun FeatureNavHost(
3738
appState: AppState,
38-
onClickLogout: () -> Unit,
3939
padding: PaddingValues,
4040
modifier: Modifier = Modifier,
4141
) {
@@ -97,7 +97,7 @@ internal fun FeatureNavHost(
9797

9898
settingsScreen(
9999
navigateBack = appState.navController::popBackStack,
100-
navigateToLoginScreen = {},
100+
navigateToLoginScreen = appState.navController::navigateToLogin,
101101
changePasscode = {},
102102
languageChanged = {},
103103
)

cmp-navigation/src/commonMain/kotlin/cmp/navigation/navigation/RootNavGraph.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,37 @@ import androidx.navigation.NavHostController
1515
import androidx.navigation.compose.NavHost
1616
import androidx.navigation.compose.composable
1717
import cmp.navigation.App
18+
import cmp.navigation.navigation.NavGraphRoute.AUTH_GRAPH
1819
import cmp.navigation.navigation.NavGraphRoute.MAIN_GRAPH
1920
import com.mifos.core.data.util.NetworkMonitor
20-
import com.mifos.feature.auth.navigation.AuthScreens
2121
import com.mifos.feature.auth.navigation.authNavGraph
22-
import com.mifos.feature.auth.navigation.navigateToLogin
23-
import com.mifos.feature.splash.navigation.splashNavGraph
2422

2523
@Composable
2624
fun RootNavGraph(
2725
networkMonitor: NetworkMonitor,
2826
navHostController: NavHostController,
27+
startDestination: String,
28+
onClickLogout: () -> Unit,
2929
modifier: Modifier = Modifier,
3030
) {
3131
NavHost(
3232
navController = navHostController,
33-
startDestination = AuthScreens.LoginScreenRoute.route,
33+
startDestination = startDestination,
3434
route = NavGraphRoute.ROOT_GRAPH,
3535
modifier = modifier,
3636
) {
3737
authNavGraph(
38+
route = AUTH_GRAPH,
3839
navigateHome = { navHostController.navigate(MAIN_GRAPH) },
3940
navigatePasscode = { },
4041
updateServerConfig = {},
4142
)
4243

43-
splashNavGraph(
44-
navigateLogin = navHostController::navigateToLogin,
45-
navigatePasscode = {},
46-
)
47-
4844
composable(MAIN_GRAPH) {
4945
App(
5046
modifier = modifier,
5147
networkMonitor = networkMonitor,
52-
onClickLogout = {},
48+
onClickLogout = onClickLogout,
5349
)
5450
}
5551
}

core/datastore/src/commonMain/kotlin/com/mifos/core/datastore/UserPreferencesDataSource.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.russhwolf.settings.serialization.encodeValue
2222
import kotlinx.coroutines.CoroutineDispatcher
2323
import kotlinx.coroutines.flow.MutableStateFlow
2424
import kotlinx.coroutines.flow.asStateFlow
25+
import kotlinx.coroutines.flow.map
2526
import kotlinx.coroutines.withContext
2627
import kotlinx.serialization.ExperimentalSerializationApi
2728

@@ -62,6 +63,7 @@ class UserPreferencesDataSource(
6263
)
6364
val userInfo = _userInfo.asStateFlow()
6465
val settingsInfo = _settingsInfo.asStateFlow()
66+
val appTheme = _settingsInfo.map { it.appTheme }
6567

6668
private val _userData = MutableStateFlow(
6769
settings.decodeValue(
@@ -140,7 +142,7 @@ class UserPreferencesDataSource(
140142

141143
suspend fun clearInfo() {
142144
withContext(dispatcher) {
143-
settings.remove(AUTH_USER)
145+
settings.clear()
144146
}
145147
}
146148

core/datastore/src/commonMain/kotlin/com/mifos/core/datastore/UserPreferencesRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface UserPreferencesRepository {
2323
val settingsInfo: Flow<AppSettings>
2424
val serverConfig: Flow<ServerConfig>
2525
val token: String?
26+
val appTheme: StateFlow<AppTheme>
2627

2728
suspend fun updateUser(user: User): Result<Unit>
2829
suspend fun updateUserStatus(status: Boolean): Result<Unit>

core/datastore/src/commonMain/kotlin/com/mifos/core/datastore/UserPreferencesRepositoryImpl.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ class UserPreferencesRepositoryImpl(
4242
override val serverConfig: Flow<ServerConfig>
4343
get() = preferenceManager.serverConfig
4444

45+
override val appTheme: StateFlow<AppTheme>
46+
get() = preferenceManager.appTheme.stateIn(
47+
scope = unconfinedScope,
48+
initialValue = AppTheme.SYSTEM,
49+
started = SharingStarted.Eagerly,
50+
)
51+
4552
override suspend fun updateUserInfo(user: UserData): Result<Unit> {
4653
return withContext(ioDispatcher) {
4754
try {
@@ -52,6 +59,7 @@ class UserPreferencesRepositoryImpl(
5259
}
5360
}
5461
}
62+
5563
override val token: String?
5664
get() = preferenceManager.token
5765

core/ui/src/androidMain/kotlin/com/mifos/core/ui/util/ShareUtils.android.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,14 @@ actual object ShareUtils {
177177
}
178178
context.startActivity(intent)
179179
}
180+
181+
actual fun restartApplication() {
182+
val context = activityProvider.invoke().applicationContext
183+
val packageManager = context.packageManager
184+
val intent = packageManager.getLaunchIntentForPackage(context.packageName)
185+
val componentName = intent?.component ?: return
186+
val restartIntent = Intent.makeRestartActivityTask(componentName)
187+
context.startActivity(restartIntent)
188+
Runtime.getRuntime().exit(0)
189+
}
180190
}

core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/ShareUtils.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ expect object ShareUtils {
3030
fun openUrl(url: String)
3131

3232
fun ossLicensesMenuActivity()
33+
34+
fun restartApplication()
3335
}

core/ui/src/desktopMain/kotlin/com/mifos/core/ui/util/ShareUtils.desktop.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,7 @@ actual object ShareUtils {
7373

7474
actual fun ossLicensesMenuActivity() {
7575
}
76+
77+
actual fun restartApplication() {
78+
}
7679
}

core/ui/src/nativeMain/kotlin/com/mifos/core/ui/util/ShareUtils.native.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,7 @@ actual object ShareUtils {
5454

5555
actual fun ossLicensesMenuActivity() {
5656
}
57+
58+
actual fun restartApplication() {
59+
}
5760
}

feature/auth/src/commonMain/kotlin/com/mifos/feature/auth/login/LoginViewModel.kt

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ import com.mifos.core.network.model.PostAuthenticationResponse
2626
import kotlinx.coroutines.flow.MutableStateFlow
2727
import kotlinx.coroutines.flow.SharingStarted
2828
import kotlinx.coroutines.flow.StateFlow
29-
import kotlinx.coroutines.flow.first
29+
import kotlinx.coroutines.flow.asStateFlow
3030
import kotlinx.coroutines.flow.map
31-
import kotlinx.coroutines.flow.onStart
3231
import kotlinx.coroutines.flow.stateIn
3332
import kotlinx.coroutines.launch
3433

@@ -44,23 +43,7 @@ class LoginViewModel(
4443
) : ViewModel() {
4544

4645
private val _loginUiState = MutableStateFlow<LoginUiState>(LoginUiState.Empty)
47-
val loginUiState = _loginUiState
48-
.onStart { checkLoginStatus() }
49-
.stateIn(
50-
viewModelScope,
51-
SharingStarted.WhileSubscribed(5000),
52-
LoginUiState.Empty,
53-
)
54-
private fun checkLoginStatus() {
55-
viewModelScope.launch {
56-
val user = prefManager.userData.first()
57-
if (user.isAuthenticated) {
58-
_loginUiState.value = LoginUiState.HomeActivityIntent
59-
} else {
60-
_loginUiState.value = LoginUiState.Empty
61-
}
62-
}
63-
}
46+
val loginUiState = _loginUiState.asStateFlow()
6447

6548
private val passcode: StateFlow<String?> = prefManager.settingsInfo
6649
.map { it.passcode }

0 commit comments

Comments
 (0)