diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index cd18e8e82..f33d075aa 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -38,7 +38,7 @@ diff --git a/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivity.kt b/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivity.kt index 66d7682cb..0bfc0ecb7 100644 --- a/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivity.kt +++ b/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivity.kt @@ -9,8 +9,10 @@ */ package org.mifos.mobile +import android.graphics.Color import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels @@ -35,6 +37,7 @@ import org.mifos.mobile.core.data.utils.NetworkMonitor import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme import org.mifos.mobile.core.designsystem.theme.darkScrim import org.mifos.mobile.core.designsystem.theme.lightScrim +import org.mifos.mobile.core.model.enums.AppTheme import org.mifos.mobile.navigation.MifosNavGraph.AUTH_GRAPH import org.mifos.mobile.navigation.MifosNavGraph.PASSCODE_GRAPH import org.mifos.mobile.navigation.RootNavGraph @@ -54,7 +57,6 @@ class HomeActivity : ComponentActivity() { super.onCreate(savedInstanceState) var uiState: HomeActivityUiState by mutableStateOf(HomeActivityUiState.Loading) - // Update the uiState lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState @@ -76,25 +78,41 @@ class HomeActivity : ComponentActivity() { val navController = rememberNavController() val appState = rememberMifosMobileState(networkMonitor = networkMonitor) - val darkTheme = isSystemInDarkTheme() val navDestination = when (uiState) { is Success -> if ((uiState as Success).userData.isAuthenticated) { PASSCODE_GRAPH } else { AUTH_GRAPH } - else -> AUTH_GRAPH } - DisposableEffect(darkTheme) { - window?.statusBarColor = if (darkTheme) darkScrim.toArgb() else lightScrim.toArgb() - window?.navigationBarColor = if (darkTheme) darkScrim.toArgb() else lightScrim.toArgb() + val isSystemInDarkMode = isSystemInDarkTheme() + DisposableEffect(isSystemInDarkMode) { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + Color.TRANSPARENT, + Color.TRANSPARENT, + ) { isSystemInDarkMode }, + navigationBarStyle = SystemBarStyle.auto( + lightScrim.toArgb(), + darkScrim.toArgb(), + ) { isSystemInDarkMode }, + ) onDispose {} } + val isDarkMode = when (uiState) { + is Success -> when ((uiState as Success).themeState) { + AppTheme.DARK -> true + AppTheme.LIGHT -> false + AppTheme.SYSTEM -> isSystemInDarkMode + } + else -> isSystemInDarkMode + } + CompositionLocalProvider { - MifosMobileTheme { + MifosMobileTheme(isDarkMode) { RootNavGraph( appState = appState, navHostController = navController, diff --git a/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivityViewModel.kt b/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivityViewModel.kt index fdfc7e533..776d00a5b 100644 --- a/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivityViewModel.kt +++ b/androidApp/src/main/kotlin/org/mifos/mobile/HomeActivityViewModel.kt @@ -14,22 +14,28 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.mifos.library.passcode.data.PasscodeManager import org.mifos.mobile.core.data.repository.UserDataRepository +import org.mifos.mobile.core.datastore.PreferencesHelper import org.mifos.mobile.core.model.UserData +import org.mifos.mobile.core.model.enums.AppTheme import javax.inject.Inject @HiltViewModel class HomeActivityViewModel @Inject constructor( private val userDataRepository: UserDataRepository, private val passcodeManager: PasscodeManager, + private val preferencesHelper: PreferencesHelper, ) : ViewModel() { - val uiState: StateFlow = userDataRepository.userData.map { - HomeActivityUiState.Success(it) + val uiState: StateFlow = combine( + userDataRepository.userData, + preferencesHelper.themeFlow, + ) { userData, themeState -> + HomeActivityUiState.Success(userData, themeState) }.stateIn( scope = viewModelScope, initialValue = HomeActivityUiState.Loading, @@ -46,5 +52,5 @@ class HomeActivityViewModel @Inject constructor( sealed interface HomeActivityUiState { data object Loading : HomeActivityUiState - data class Success(val userData: UserData) : HomeActivityUiState + data class Success(val userData: UserData, val themeState: AppTheme) : HomeActivityUiState } diff --git a/androidApp/src/main/kotlin/org/mifos/mobile/MifosSelfServiceApp.kt b/androidApp/src/main/kotlin/org/mifos/mobile/MifosSelfServiceApp.kt index a728b6070..2ccac90b8 100644 --- a/androidApp/src/main/kotlin/org/mifos/mobile/MifosSelfServiceApp.kt +++ b/androidApp/src/main/kotlin/org/mifos/mobile/MifosSelfServiceApp.kt @@ -14,8 +14,6 @@ import androidx.multidex.MultiDexApplication import com.google.firebase.crashlytics.FirebaseCrashlytics import com.raizlabs.android.dbflow.config.FlowManager import dagger.hilt.android.HiltAndroidApp -import org.mifos.mobile.core.datastore.PreferencesHelper -import org.mifos.mobile.feature.settings.applySavedTheme @HiltAndroidApp class MifosSelfServiceApp : MultiDexApplication() { @@ -24,7 +22,6 @@ class MifosSelfServiceApp : MultiDexApplication() { MultiDex.install(this) FlowManager.init(this) FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) - PreferencesHelper(this).applySavedTheme() } override fun onTerminate() { diff --git a/androidApp/src/main/res/values-night/color.xml b/androidApp/src/main/res/values-night/color.xml new file mode 100644 index 000000000..b71f9f07c --- /dev/null +++ b/androidApp/src/main/res/values-night/color.xml @@ -0,0 +1,13 @@ + + + + #FF1B1B1F // dark/color.xml + \ No newline at end of file diff --git a/androidApp/src/main/res/values-night/theme.xml b/androidApp/src/main/res/values-night/theme.xml new file mode 100644 index 000000000..1bcacceb8 --- /dev/null +++ b/androidApp/src/main/res/values-night/theme.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/androidApp/src/main/res/values/colors.xml b/androidApp/src/main/res/values/colors.xml index 94394e4c5..f6d79d8f5 100644 --- a/androidApp/src/main/res/values/colors.xml +++ b/androidApp/src/main/res/values/colors.xml @@ -54,7 +54,7 @@ #33CCCCCC - + #FFFEFBFF diff --git a/androidApp/src/main/res/values/splash.xml b/androidApp/src/main/res/values/splash.xml index 9ee17867e..64f0c3289 100644 --- a/androidApp/src/main/res/values/splash.xml +++ b/androidApp/src/main/res/values/splash.xml @@ -8,18 +8,31 @@ See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md --> - - + + + - + + + diff --git a/core/datastore/src/main/java/org/mifos/mobile/core/datastore/PreferencesHelper.kt b/core/datastore/src/main/java/org/mifos/mobile/core/datastore/PreferencesHelper.kt index b469a6bea..8c1fea1d8 100644 --- a/core/datastore/src/main/java/org/mifos/mobile/core/datastore/PreferencesHelper.kt +++ b/core/datastore/src/main/java/org/mifos/mobile/core/datastore/PreferencesHelper.kt @@ -16,6 +16,9 @@ import android.text.TextUtils import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow import org.mifos.mobile.core.model.enums.AppTheme @@ -29,9 +32,23 @@ import javax.inject.Singleton */ @Singleton class PreferencesHelper @Inject constructor(@ApplicationContext context: Context?) { + private val themeFlowState: MutableStateFlow + val themeFlow: StateFlow get() = themeFlowState.asStateFlow() + private val sharedPreferences: SharedPreferences? = PreferenceManager.getDefaultSharedPreferences(context) + init { + if (!sharedPreferences?.contains(APPLICATION_THEME)!!) { + putInt(APPLICATION_THEME, AppTheme.SYSTEM.ordinal) + } + themeFlowState = MutableStateFlow( + AppTheme.entries.getOrNull( + sharedPreferences.getInt(APPLICATION_THEME, AppTheme.SYSTEM.ordinal), + ) ?: AppTheme.SYSTEM, + ) + } + fun clear() { val editor = sharedPreferences?.edit() // prevent deletion of url and tenant @@ -178,6 +195,7 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context get() = getInt(APPLICATION_THEME, AppTheme.SYSTEM.ordinal) ?: AppTheme.SYSTEM.ordinal set(value) { putInt(APPLICATION_THEME, value) + themeFlowState.value = AppTheme.entries[value] } var language diff --git a/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/theme/Theme.kt b/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/theme/Theme.kt index b3e2da1f8..d5c34ea67 100644 --- a/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/theme/Theme.kt +++ b/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/theme/Theme.kt @@ -33,7 +33,7 @@ private val DarkThemeColors = darkColorScheme( secondary = Black1, error = RedErrorDark, background = BackgroundDark, - surface = Black1, + surface = BackgroundDark, onSurface = Color.White, onSecondary = Color.White, outlineVariant = Color.White, diff --git a/feature/settings/src/main/java/org/mifos/mobile/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/org/mifos/mobile/feature/settings/SettingsViewModel.kt index 2e0627206..6d974423e 100644 --- a/feature/settings/src/main/java/org/mifos/mobile/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/java/org/mifos/mobile/feature/settings/SettingsViewModel.kt @@ -9,8 +9,6 @@ */ package org.mifos.mobile.feature.settings -import android.os.Build -import androidx.appcompat.app.AppCompatDelegate import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -70,15 +68,7 @@ internal class SettingsViewModel @Inject constructor( } fun updateTheme(theme: AppTheme) { - AppCompatDelegate.setDefaultNightMode( - when (theme) { - AppTheme.DARK -> AppCompatDelegate.MODE_NIGHT_YES - AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO - else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - }, - ) preferencesHelper.appTheme = theme.ordinal - preferencesHelper.applyTheme(theme) } } @@ -124,27 +114,3 @@ internal enum class SettingsCardItem( subclassOf = R.string.other, ), } - -fun PreferencesHelper.applySavedTheme() { - val applicationTheme = AppTheme.entries.find { it.ordinal == this.appTheme } - AppCompatDelegate.setDefaultNightMode( - when { - applicationTheme == AppTheme.DARK -> AppCompatDelegate.MODE_NIGHT_YES - applicationTheme == AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO - Build.VERSION.SDK_INT > Build.VERSION_CODES.P -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - else -> AppCompatDelegate.MODE_NIGHT_NO - }, - ) -} - -internal fun PreferencesHelper.applyTheme(applicationTheme: AppTheme) { - this.appTheme = applicationTheme.ordinal - AppCompatDelegate.setDefaultNightMode( - when { - applicationTheme == AppTheme.DARK -> AppCompatDelegate.MODE_NIGHT_YES - applicationTheme == AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO - Build.VERSION.SDK_INT > Build.VERSION_CODES.P -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - else -> AppCompatDelegate.MODE_NIGHT_NO - }, - ) -}