From 5afa9defd734c50578ff0690c1a871648ac723a7 Mon Sep 17 00:00:00 2001 From: Andrea Severi Date: Mon, 9 Dec 2024 13:43:49 +0100 Subject: [PATCH 1/5] Apply user dark mode preference to splashscreen --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 1 + .../samples/apps/nowinandroid/MainActivity.kt | 116 ++++++++++++------ 3 files changed, 79 insertions(+), 39 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5434df8c22..3102b4510c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation(projects.sync.work) implementation(libs.androidx.activity.compose) + implementation(libs.androidx.appcompat) implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.material3.adaptive.layout) implementation(libs.androidx.compose.material3.adaptive.navigation) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b22f98658..af35343423 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 599bd0b356..dc2eef3f1a 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -16,12 +16,16 @@ package com.google.samples.apps.nowinandroid +import android.app.UiModeManager +import android.content.Context +import android.os.Build 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 +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -108,23 +112,24 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { - val darkTheme = shouldUseDarkTheme(uiState) + val themeInfo = themeInfo(uiState) // Update the edge to edge configuration to match the theme // This is the same parameters as the default enableEdgeToEdge call, but we manually // resolve whether or not to show dark theme using uiState, since it can be different // than the configuration's dark theme value based on the user preference. - DisposableEffect(darkTheme) { + DisposableEffect(themeInfo.isThemeDark, themeInfo.shouldFollowSystem) { enableEdgeToEdge( statusBarStyle = SystemBarStyle.auto( android.graphics.Color.TRANSPARENT, android.graphics.Color.TRANSPARENT, - ) { darkTheme }, + ) { themeInfo.isThemeDark }, navigationBarStyle = SystemBarStyle.auto( lightScrim, darkScrim, - ) { darkTheme }, + ) { themeInfo.isThemeDark }, ) + setAppTheme(getSystemService(Context.UI_MODE_SERVICE) as UiModeManager, themeInfo) onDispose {} } @@ -141,9 +146,9 @@ class MainActivity : ComponentActivity() { LocalTimeZone provides currentTimeZone, ) { NiaTheme( - darkTheme = darkTheme, - androidTheme = shouldUseAndroidTheme(uiState), - disableDynamicTheming = shouldDisableDynamicTheming(uiState), + darkTheme = themeInfo.isThemeDark, + androidTheme = themeInfo.shouldUseAndroidTheme, + disableDynamicTheming = themeInfo.shouldDisableDynamicTheming, ) { NiaApp(appState) } @@ -162,44 +167,77 @@ class MainActivity : ComponentActivity() { } } -/** - * Returns `true` if the Android theme should be used, as a function of the [uiState]. - */ -@Composable -private fun shouldUseAndroidTheme( - uiState: MainActivityUiState, -): Boolean = when (uiState) { - Loading -> false - is Success -> when (uiState.userData.themeBrand) { - ThemeBrand.DEFAULT -> false - ThemeBrand.ANDROID -> true - } -} +private data class ThemeInfo( + /** + * Returns `true` if dark theme should be used. + */ + val isThemeDark: Boolean, -/** - * Returns `true` if the dynamic color is disabled, as a function of the [uiState]. - */ + /** + * Returns `true` if theme should follow the system settings. + */ + val shouldFollowSystem: Boolean, + + /** + * Returns `true` if the dynamic color is disabled. + */ + val shouldDisableDynamicTheming: Boolean, + + /** + * Returns `true` if the Android theme should be used. + */ + val shouldUseAndroidTheme: Boolean, +) @Composable -private fun shouldDisableDynamicTheming( - uiState: MainActivityUiState, -): Boolean = when (uiState) { - Loading -> false - is Success -> !uiState.userData.useDynamicColor +private fun themeInfo(uiState: MainActivityUiState): ThemeInfo { + return when (uiState) { + Loading -> ThemeInfo( + isThemeDark = isSystemInDarkTheme(), + shouldFollowSystem = false, + shouldDisableDynamicTheming = false, + shouldUseAndroidTheme = false + ) + + is Success -> { + val isThemeDark = uiState.userData.darkThemeConfig == DarkThemeConfig.DARK + val shouldFollowSystem = uiState.userData.darkThemeConfig == DarkThemeConfig.FOLLOW_SYSTEM + ThemeInfo( + isThemeDark = isThemeDark || (shouldFollowSystem && isSystemInDarkTheme()), + shouldFollowSystem = shouldFollowSystem, + shouldDisableDynamicTheming = !uiState.userData.useDynamicColor, + shouldUseAndroidTheme = uiState.userData.themeBrand == ThemeBrand.ANDROID + ) + } + } } /** - * Returns `true` if dark theme should be used, as a function of the [uiState] and the - * current system context. + * Sets app theme to reflect user choice. */ -@Composable -private fun shouldUseDarkTheme( - uiState: MainActivityUiState, -): Boolean = when (uiState) { - Loading -> isSystemInDarkTheme() - is Success -> when (uiState.userData.darkThemeConfig) { - DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() - DarkThemeConfig.LIGHT -> false - DarkThemeConfig.DARK -> true +private fun setAppTheme( + uiModeManager: UiModeManager, + themeInfo: ThemeInfo +) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + uiModeManager.setApplicationNightMode( + if (themeInfo.shouldFollowSystem) { + UiModeManager.MODE_NIGHT_AUTO + } else if (themeInfo.isThemeDark) { + UiModeManager.MODE_NIGHT_YES + } else { + UiModeManager.MODE_NIGHT_NO + } + ) + } else { + AppCompatDelegate.setDefaultNightMode( + if (themeInfo.shouldFollowSystem) { + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } else if (themeInfo.isThemeDark) { + AppCompatDelegate.MODE_NIGHT_YES + } else { + AppCompatDelegate.MODE_NIGHT_NO + } + ) } } From 0f89417af27c4a3b150ecb41b43aa8133a185667 Mon Sep 17 00:00:00 2001 From: Andrea Severi Date: Fri, 20 Dec 2024 00:05:05 +0100 Subject: [PATCH 2/5] Add shouldFollowSystemTheme to theme settings --- .../samples/apps/nowinandroid/MainActivityViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt index 2d22b7d9cd..ffc7e7696e 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt @@ -61,6 +61,9 @@ sealed interface MainActivityUiState { DarkThemeConfig.LIGHT -> false DarkThemeConfig.DARK -> true } + + override val shouldFollowSystemTheme = + userData.darkThemeConfig == DarkThemeConfig.FOLLOW_SYSTEM } /** @@ -82,4 +85,6 @@ sealed interface MainActivityUiState { * Returns `true` if dark theme should be used. */ fun shouldUseDarkTheme(isSystemDarkTheme: Boolean) = isSystemDarkTheme + + val shouldFollowSystemTheme: Boolean get() = true } From 6ffbe18bc5a56a7d1f1ea3df7f02922700512fc4 Mon Sep 17 00:00:00 2001 From: Andrea Severi Date: Fri, 20 Dec 2024 00:05:11 +0100 Subject: [PATCH 3/5] Apply spotless --- .../samples/apps/nowinandroid/MainActivity.kt | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index f26cc5a193..34c5383742 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -105,7 +105,7 @@ class MainActivity : ComponentActivity() { darkTheme = uiState.shouldUseDarkTheme(systemDark), androidTheme = uiState.shouldUseAndroidTheme, disableDynamicTheming = uiState.shouldDisableDynamicTheming, - shouldFollowSystemTheme = uiState.shouldFollowSystemTheme + shouldFollowSystemTheme = uiState.shouldFollowSystemTheme, ) } .onEach { themeSettings = it } @@ -130,7 +130,7 @@ class MainActivity : ComponentActivity() { ) setAppTheme( uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager, - themeSettings = themeSettings + themeSettings = themeSettings, ) } } @@ -182,28 +182,26 @@ class MainActivity : ComponentActivity() { */ private fun setAppTheme( uiModeManager: UiModeManager, - themeSettings: ThemeSettings + themeSettings: ThemeSettings, ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - uiModeManager.setApplicationNightMode( - if (themeSettings.shouldFollowSystemTheme) { - UiModeManager.MODE_NIGHT_AUTO - } else if (themeSettings.darkTheme) { - UiModeManager.MODE_NIGHT_YES - } else { - UiModeManager.MODE_NIGHT_NO - } - ) + val mode = if (themeSettings.shouldFollowSystemTheme) { + UiModeManager.MODE_NIGHT_AUTO + } else if (themeSettings.darkTheme) { + UiModeManager.MODE_NIGHT_YES + } else { + UiModeManager.MODE_NIGHT_NO + } + uiModeManager.setApplicationNightMode(mode) } else { - AppCompatDelegate.setDefaultNightMode( - if (themeSettings.shouldFollowSystemTheme) { - AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } else if (themeSettings.darkTheme) { - AppCompatDelegate.MODE_NIGHT_YES - } else { - AppCompatDelegate.MODE_NIGHT_NO - } - ) + val mode = if (themeSettings.shouldFollowSystemTheme) { + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } else if (themeSettings.darkTheme) { + AppCompatDelegate.MODE_NIGHT_YES + } else { + AppCompatDelegate.MODE_NIGHT_NO + } + AppCompatDelegate.setDefaultNightMode(mode) } } From 23187117f971e5507fafabe4be77a20ee05230a6 Mon Sep 17 00:00:00 2001 From: Andrea Severi Date: Fri, 20 Dec 2024 00:07:55 +0100 Subject: [PATCH 4/5] Add doc for shouldFollowSystemTheme --- .../google/samples/apps/nowinandroid/MainActivityViewModel.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt index ffc7e7696e..35e4981fe4 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt @@ -86,5 +86,8 @@ sealed interface MainActivityUiState { */ fun shouldUseDarkTheme(isSystemDarkTheme: Boolean) = isSystemDarkTheme + /** + * Returns `true` if app theme should follow system theme. + */ val shouldFollowSystemTheme: Boolean get() = true } From 13d8eee34c366de531aa48f0c1ad0c1de914057c Mon Sep 17 00:00:00 2001 From: Andrea Severi Date: Fri, 20 Dec 2024 00:14:48 +0100 Subject: [PATCH 5/5] Replace if with when for better readability --- .../samples/apps/nowinandroid/MainActivity.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 34c5383742..be45862ab7 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -185,21 +185,17 @@ private fun setAppTheme( themeSettings: ThemeSettings, ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val mode = if (themeSettings.shouldFollowSystemTheme) { - UiModeManager.MODE_NIGHT_AUTO - } else if (themeSettings.darkTheme) { - UiModeManager.MODE_NIGHT_YES - } else { - UiModeManager.MODE_NIGHT_NO + val mode = when { + themeSettings.shouldFollowSystemTheme -> UiModeManager.MODE_NIGHT_AUTO + themeSettings.darkTheme -> UiModeManager.MODE_NIGHT_YES + else -> UiModeManager.MODE_NIGHT_NO } uiModeManager.setApplicationNightMode(mode) } else { - val mode = if (themeSettings.shouldFollowSystemTheme) { - AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } else if (themeSettings.darkTheme) { - AppCompatDelegate.MODE_NIGHT_YES - } else { - AppCompatDelegate.MODE_NIGHT_NO + val mode = when { + themeSettings.shouldFollowSystemTheme -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + themeSettings.darkTheme -> AppCompatDelegate.MODE_NIGHT_YES + else -> AppCompatDelegate.MODE_NIGHT_NO } AppCompatDelegate.setDefaultNightMode(mode) }