mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Resolve the System theme from the application configuration
Selecting a Light or Dark theme makes AppCompatDelegate rewrite the activity configuration to the forced night mode. isSystemInDarkTheme reads that same configuration, so switching back to System kept showing the previously forced theme. Read the night mode from the application configuration instead, which AppCompat leaves untouched and itself uses to expand MODE_NIGHT_FOLLOW_SYSTEM. The activity configuration is still used as the recomposition key so a system-wide theme change is picked up.
This commit is contained in:
@@ -8,8 +8,11 @@
|
||||
|
||||
package io.element.android.compound.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -27,12 +30,28 @@ private fun Theme.coerceBlackTheme(allowBlackTheme: Boolean): Theme {
|
||||
@Composable
|
||||
fun Theme.isDark(): Boolean {
|
||||
return when (this) {
|
||||
Theme.System -> isSystemInDarkTheme()
|
||||
Theme.System -> isSystemThemeDark()
|
||||
Theme.Dark, Theme.Black -> true
|
||||
Theme.Light -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the system-wide theme is dark.
|
||||
*
|
||||
* This deliberately does not use [androidx.compose.foundation.isSystemInDarkTheme], which reads the activity configuration: `AppCompatDelegate` rewrites
|
||||
* that configuration whenever a non-system theme is selected, so it keeps reporting the previously forced value. The application configuration is left
|
||||
* untouched by AppCompat and is what AppCompat itself reads to expand `MODE_NIGHT_FOLLOW_SYSTEM`.
|
||||
*/
|
||||
@Composable
|
||||
private fun isSystemThemeDark(): Boolean {
|
||||
val applicationContext = LocalContext.current.applicationContext
|
||||
val activityConfiguration = LocalConfiguration.current
|
||||
return remember(activityConfiguration) {
|
||||
applicationContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
|
||||
fun Flow<String?>.mapToTheme(allowBlackTheme: Boolean): Flow<Theme> = map {
|
||||
when (it) {
|
||||
null -> Theme.System
|
||||
|
||||
@@ -11,19 +11,22 @@ package io.element.android.compound.theme
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
class ThemeTest {
|
||||
class ThemeTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `isDark for System dark returns true`() {
|
||||
`isDark for System`(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
systemUiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
expected = true,
|
||||
)
|
||||
}
|
||||
@@ -31,21 +34,38 @@ class ThemeTest {
|
||||
@Test
|
||||
fun `isDark for System light return false`() {
|
||||
`isDark for System`(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_NO,
|
||||
systemUiMode = Configuration.UI_MODE_NIGHT_NO,
|
||||
expected = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun `isDark for System`(
|
||||
uiMode: Int,
|
||||
@Test
|
||||
fun `isDark for System ignores the night mode AppCompat forced on the activity`() {
|
||||
`isDark for System`(
|
||||
systemUiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
activityUiMode = Configuration.UI_MODE_NIGHT_NO,
|
||||
expected = true,
|
||||
)
|
||||
`isDark for System`(
|
||||
systemUiMode = Configuration.UI_MODE_NIGHT_NO,
|
||||
activityUiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
expected = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun `isDark for System`(
|
||||
systemUiMode: Int,
|
||||
activityUiMode: Int = systemUiMode,
|
||||
expected: Boolean,
|
||||
) = runTest {
|
||||
val application = RuntimeEnvironment.getApplication()
|
||||
application.resources.configuration.uiMode = systemUiMode
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
var result: Boolean? = null
|
||||
CompositionLocalProvider(
|
||||
// Let set the system to dark
|
||||
LocalContext provides application,
|
||||
LocalConfiguration provides Configuration().apply {
|
||||
this.uiMode = uiMode
|
||||
this.uiMode = activityUiMode
|
||||
},
|
||||
) {
|
||||
result = Theme.System.isDark()
|
||||
|
||||
Reference in New Issue
Block a user