pull/2012/merge
Everts Chavez 7 days ago committed by GitHub
commit f63b97f658
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -99,6 +99,7 @@ dependencies {
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.lifecycle.viewModel.navigation3)
implementation(libs.androidx.profileinstaller)

@ -20,11 +20,17 @@ import android.app.Application
import android.content.pm.ApplicationInfo
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy.Builder
import androidx.datastore.core.DataStore
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope
import com.google.samples.apps.nowinandroid.sync.initializers.Sync
import com.google.samples.apps.nowinandroid.util.ProfileVerifierLogger
import com.google.samples.apps.nowinandroid.util.initializeNightModeFromPreferences
import com.google.samples.apps.nowinandroid.util.observeNightModePreferences
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope
import javax.inject.Inject
/**
@ -35,12 +41,24 @@ class NiaApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var imageLoader: dagger.Lazy<ImageLoader>
@Inject
lateinit var userPrefsDataStore: DataStore<UserPreferences>
@Inject
lateinit var profileVerifierLogger: ProfileVerifierLogger
@Inject
@ApplicationScope
lateinit var applicationScope: CoroutineScope
override fun onCreate() {
super.onCreate()
// Initialize dark mode from user prefs
initializeNightModeFromPreferences(userPrefsDataStore)
observeNightModePreferences(userPrefsDataStore, applicationScope)
setStrictModePolicy()
// Initialize Sync; the system responsible for keeping data in the app up to date.

@ -16,13 +16,28 @@
package com.google.samples.apps.nowinandroid.util
import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import androidx.activity.ComponentActivity
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.util.Consumer
import androidx.datastore.core.DataStore
import com.google.samples.apps.nowinandroid.core.datastore.DarkThemeConfigProto
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
/**
* Convenience wrapper for dark mode checking
@ -47,3 +62,81 @@ fun ComponentActivity.isSystemInDarkTheme() = callbackFlow {
}
.distinctUntilChanged()
.conflate()
/**
* Converts [DarkThemeConfig] to AppCompat night mode constant.
*/
fun DarkThemeConfig.toNightMode(): Int = when (this) {
DarkThemeConfig.FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
DarkThemeConfig.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
DarkThemeConfig.DARK -> AppCompatDelegate.MODE_NIGHT_YES
}
/**
* Maps a [DarkThemeConfig] value to the corresponding night mode setting
* used by UiModeManager on Android 12 (API level 31) and above.
*/
@RequiresApi(Build.VERSION_CODES.S)
fun DarkThemeConfig.toUiNightMode(): Int = when(this) {
DarkThemeConfig.FOLLOW_SYSTEM -> UiModeManager.MODE_NIGHT_AUTO
DarkThemeConfig.LIGHT -> UiModeManager.MODE_NIGHT_NO
DarkThemeConfig.DARK -> UiModeManager.MODE_NIGHT_YES
}
/**
* Applies this [DarkThemeConfig] as default night mode.
*/
fun DarkThemeConfig.applyAsDefaultNightMode(context: Context) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
uiModeManager.setApplicationNightMode(toUiNightMode())
} else {
AppCompatDelegate.setDefaultNightMode(toNightMode())
}
}
/**
* Converts stored proto data into a DarkThemeConfig object.
*/
fun DarkThemeConfigProto.toDarkThemeConfig(): DarkThemeConfig = when (this) {
DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT -> DarkThemeConfig.LIGHT
DarkThemeConfigProto.DARK_THEME_CONFIG_DARK -> DarkThemeConfig.DARK
else -> DarkThemeConfig.FOLLOW_SYSTEM
}
/**
* Sets night mode from user prefs. Call in Application.onCreate() to show correct splash theme.
*/
fun Context.initializeNightModeFromPreferences(userPrefsDataStore: DataStore<UserPreferences>) {
runBlocking {
runCatching {
val darkThemeConfig = userPrefsDataStore.data
.first()
.darkThemeConfig
.toDarkThemeConfig()
darkThemeConfig.applyAsDefaultNightMode(this@initializeNightModeFromPreferences)
}.onFailure {
DarkThemeConfig.FOLLOW_SYSTEM.applyAsDefaultNightMode(this@initializeNightModeFromPreferences)
}
}
}
/**
* Observe theme changes and updates UiModeManager to prevent a bug where the first cold start
* uses the previous theme, while later starts use the correct one.
*/
fun Context.observeNightModePreferences(
userPrefsDataStore: DataStore<UserPreferences>,
scope: CoroutineScope,
) {
scope.launch {
userPrefsDataStore.data
.map { it.darkThemeConfig.toDarkThemeConfig() }
.distinctUntilChanged()
.drop(1) // Skip first emission (already handled by initializeNightModeFromPreferences)
.collect { darkThemeConfig ->
darkThemeConfig.applyAsDefaultNightMode(this@observeNightModePreferences)
}
}
}
Loading…
Cancel
Save