From a96c146d30e5ae757fb4cf87cd95787ee152670e Mon Sep 17 00:00:00 2001 From: Timur Gilfanov Date: Mon, 23 Mar 2026 15:26:16 +0400 Subject: [PATCH 1/7] Introduce "Black" theme and refactor theme handling * Add `Theme.Black` to the `Theme` enum and update `isDark()` to include it. * Refactor `ElementTheme` to accept a `Theme` object instead of a `darkTheme` boolean, allowing for more specific color mapping (e.g., setting `bgCanvasDefault` to `Color.Black` for the Black theme). * Update `AdvancedSettingsPresenter` and `AdvancedSettingsState` to include "Black" as a user-selectable theme option. * Adjust `ElementThemeApp` and `MaterialTextPreview` to handle the expanded theme selection. * Add `ElementPreviewBlack` and update existing preview components to support the new theme. * Update Konsist tests to ensure `@PreviewsDayNight` annotated functions don't have "BlackPreview" suffix. --- .../impl/timeline/protection/ProtectedView.kt | 3 ++- .../advanced/AdvancedSettingsPresenter.kt | 2 ++ .../impl/advanced/AdvancedSettingsState.kt | 7 ++++++ .../impl/advanced/AdvancedSettingsView.kt | 6 +++++ .../compound/previews/CompoundIconsPreview.kt | 3 ++- .../previews/SemanticColorsPreview.kt | 5 ++-- .../android/compound/theme/AvatarColors.kt | 2 +- .../android/compound/theme/ElementTheme.kt | 23 ++++++++++++------ .../compound/theme/ForcedDarkElementTheme.kt | 2 +- .../compound/theme/MaterialTextPreview.kt | 14 +++++++---- .../compound/theme/MaterialThemeColors.kt | 16 +++++++++++-- .../element/android/compound/theme/Theme.kt | 3 ++- .../compound/screenshot/CompoundIconTest.kt | 3 ++- .../screenshot/MaterialYouThemeTest.kt | 3 ++- .../designsystem/atomic/pages/SunsetPage.kt | 3 ++- .../designsystem/preview/ElementPreview.kt | 5 ++-- .../preview/ElementPreviewBlack.kt | 24 +++++++++++++++++++ .../preview/ElementPreviewDark.kt | 3 ++- .../preview/ElementPreviewLight.kt | 3 ++- .../preview/ElementThemedPreview.kt | 9 +++---- .../designsystem/theme/ElementThemeApp.kt | 5 ++-- .../src/main/res/values/temporary.xml | 10 ++++++++ .../tests/konsist/KonsistPreviewTest.kt | 3 ++- 23 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewBlack.kt create mode 100644 libraries/ui-strings/src/main/res/values/temporary.xml diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt index de55735b76..2dbe47e9e4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import io.element.android.features.messages.impl.timeline.components.event.TimelineItemAspectRatioBox import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground import io.element.android.libraries.designsystem.preview.ElementPreview @@ -49,7 +50,7 @@ fun ProtectedView( .background(Color(0x99000000)), contentAlignment = Alignment.Center, ) { - ElementTheme(darkTheme = false, applySystemBarsUpdate = false) { + ElementTheme(theme = Theme.Light, applySystemBarsUpdate = false) { // Not using a button to be able to have correct size Text( modifier = Modifier diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index c2871e0be8..5f95806469 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -56,6 +56,7 @@ class AdvancedSettingsPresenter( when (theme.value) { Theme.System -> ThemeOption.System Theme.Dark -> ThemeOption.Dark + Theme.Black -> ThemeOption.Black Theme.Light -> ThemeOption.Light } } @@ -98,6 +99,7 @@ class AdvancedSettingsPresenter( when (event.theme) { ThemeOption.System -> appPreferencesStore.setTheme(Theme.System.name) ThemeOption.Dark -> appPreferencesStore.setTheme(Theme.Dark.name) + ThemeOption.Black -> appPreferencesStore.setTheme(Theme.Black.name) ThemeOption.Light -> appPreferencesStore.setTheme(Theme.Light.name) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index 6eb7414a29..be443e905b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -48,6 +48,13 @@ enum class ThemeOption : DropdownOption { @ReadOnlyComposable override fun getText(): String = stringResource(CommonStrings.common_dark) }, + + Black { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(CommonStrings.common_black) + }, + Light { @Composable @ReadOnlyComposable diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index c2b51973d0..f8f1faebbd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewBlack import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -324,6 +325,11 @@ internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettings internal fun AdvancedSettingsViewDarkPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = ElementPreviewDark { ContentToPreview(state) } +@PreviewWithLargeHeight +@Composable +internal fun AdvancedSettingsViewBlackPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = + ElementPreviewBlack { ContentToPreview(state) } + @ExcludeFromCoverage @Composable private fun ContentToPreview(state: AdvancedSettingsState) { diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt index 236e3627f4..7b1219a9f9 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import io.element.android.compound.tokens.generated.CompoundIcons import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -62,7 +63,7 @@ internal fun IconsCompoundPreviewRtl() = ElementTheme { @Preview(widthDp = 730, heightDp = 1920) @Composable -internal fun IconsCompoundPreviewDark() = ElementTheme(darkTheme = true) { +internal fun IconsCompoundPreviewDark() = ElementTheme(theme = Theme.Dark) { IconsCompoundPreview() } diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt index 3f5fb42149..735f8f495d 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import io.element.android.compound.tokens.generated.compoundColorsHcDark import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf @@ -65,7 +66,7 @@ internal fun CompoundSemanticColorsLightHc() = ElementTheme( @Preview(heightDp = 2000) @Composable -internal fun CompoundSemanticColorsDark() = ElementTheme(darkTheme = true) { +internal fun CompoundSemanticColorsDark() = ElementTheme(theme = Theme.Dark) { Surface { Column( modifier = Modifier.padding(16.dp), @@ -85,7 +86,7 @@ internal fun CompoundSemanticColorsDark() = ElementTheme(darkTheme = true) { @Preview(heightDp = 2000) @Composable internal fun CompoundSemanticColorsDarkHc() = ElementTheme( - darkTheme = true, + theme = Theme.Dark, compoundDark = compoundColorsHcDark, ) { Surface { diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt index 763f422a00..8d3e495332 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt @@ -65,7 +65,7 @@ internal fun AvatarColorsPreviewLight() { @Preview @Composable internal fun AvatarColorsPreviewDark() { - ElementTheme(darkTheme = true) { + ElementTheme(theme = Theme.Dark) { val chunks = avatarColors().chunked(4) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { for (chunk in chunks) { diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt index bb2ae2b62e..ac83365658 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt @@ -77,10 +77,10 @@ internal val LocalCompoundColors = staticCompositionLocalOf { compoundColorsLigh /** * Sets up the theme for the application, or a part of it. * - * @param darkTheme whether to use the dark theme or not. If `true`, the dark theme will be used. + * @param theme the [Theme] to use. Defaults to [Theme.Dark] or [Theme.Light] based on the system setting. * @param applySystemBarsUpdate whether to update the system bars color scheme or not when the theme changes. It's `true` by default. * This is specially useful when you want to apply an alternate theme to a part of the app but don't want it to affect the system bars. - * @param lightStatusBar whether to use a light status bar color scheme or not. By default, it's the opposite of [darkTheme]. + * @param lightStatusBar whether to use a light status bar color scheme or not. By default, it's `true` for light themes and `false` for dark ones. * @param dynamicColor whether to enable MaterialYou or not. It's `false` by default. * @param compoundLight the [SemanticColors] to use in light theme. * @param compoundDark the [SemanticColors] to use in dark theme. @@ -91,9 +91,9 @@ internal val LocalCompoundColors = staticCompositionLocalOf { compoundColorsLigh */ @Composable fun ElementTheme( - darkTheme: Boolean = isSystemInDarkTheme(), + theme: Theme = if (isSystemInDarkTheme()) Theme.Dark else Theme.Light, applySystemBarsUpdate: Boolean = true, - lightStatusBar: Boolean = !darkTheme, + lightStatusBar: Boolean = !theme.isDark(), // true to enable MaterialYou dynamicColor: Boolean = false, compoundLight: SemanticColors = compoundColorsLight, @@ -103,8 +103,13 @@ fun ElementTheme( typography: Typography = compoundTypography, content: @Composable () -> Unit, ) { + val darkTheme = theme.isDark() val currentCompoundColor = when { - darkTheme -> compoundDark + darkTheme -> if (theme == Theme.Black) { + compoundDark.copy(bgCanvasDefault = Color.Black) + } else { + compoundDark + } else -> compoundLight } @@ -113,7 +118,11 @@ fun ElementTheme( val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - darkTheme -> materialColorsDark + darkTheme -> if (theme == Theme.Black) { + currentCompoundColor.toMaterialColorScheme() + } else { + materialColorsDark + } else -> materialColorsLight } @@ -130,7 +139,7 @@ fun ElementTheme( if (applySystemBarsUpdate) { val activity = LocalActivity.current as? ComponentActivity - LaunchedEffect(statusBarColorScheme, darkTheme, lightStatusBar) { + LaunchedEffect(statusBarColorScheme, theme, lightStatusBar) { activity?.enableEdgeToEdge( // For Status bar use the background color of the app statusBarStyle = SystemBarStyle.auto( diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt index 272245f199..8f9d2bdf0c 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt @@ -51,7 +51,7 @@ fun ForcedDarkElementTheme( } } ElementTheme( - darkTheme = true, + theme = Theme.Dark, compoundLight = colors.light, compoundDark = colors.dark, lightStatusBar = lightStatusBar, diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt index 792c7fbabf..27e874c141 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt @@ -36,11 +36,15 @@ internal fun MaterialTextPreview() = Row( ) { MaterialPreview( modifier = Modifier.weight(1f), - darkTheme = false, + theme = Theme.Light, ) MaterialPreview( modifier = Modifier.weight(1f), - darkTheme = true, + theme = Theme.Dark, + ) + MaterialPreview( + modifier = Modifier.weight(1f), + theme = Theme.Black, ) } @@ -52,7 +56,7 @@ private data class Model( @Composable private fun MaterialPreview( - darkTheme: Boolean, + theme: Theme, modifier: Modifier = Modifier, ) = Column(modifier = modifier) { Text( @@ -60,13 +64,13 @@ private fun MaterialPreview( .fillMaxWidth() .padding(8.dp), textAlign = TextAlign.Center, - text = if (darkTheme) "Dark" else "Light", + text = theme.name, color = Color.Black, fontSize = 18.sp, fontWeight = FontWeight.Bold, ) ElementTheme( - darkTheme = darkTheme, + theme = theme, ) { Column( modifier = Modifier.fillMaxSize() diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt index c2a923e926..c283780295 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt @@ -50,7 +50,7 @@ internal fun ColorsSchemeLightHcPreview() = ElementTheme( @Preview(heightDp = 1200) @Composable internal fun ColorsSchemeDarkPreview() = ElementTheme( - darkTheme = true, + theme = Theme.Dark, ) { ColorsSchemePreview( Color.White, @@ -62,7 +62,7 @@ internal fun ColorsSchemeDarkPreview() = ElementTheme( @Preview(heightDp = 1200) @Composable internal fun ColorsSchemeDarkHcPreview() = ElementTheme( - darkTheme = true, + theme = Theme.Dark, compoundDark = compoundColorsHcDark, ) { ColorsSchemePreview( @@ -71,3 +71,15 @@ internal fun ColorsSchemeDarkHcPreview() = ElementTheme( ElementTheme.materialColors, ) } + +@Preview(heightDp = 1200) +@Composable +internal fun ColorsSchemeBlackPreview() = ElementTheme( + theme = Theme.Black +) { + ColorsSchemePreview( + Color.White, + Color.Black, + ElementTheme.materialColors, + ) +} diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt index 131b14430c..fd00abb4f4 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.map enum class Theme { System, Dark, + Black, Light, } @@ -23,7 +24,7 @@ enum class Theme { fun Theme.isDark(): Boolean { return when (this) { Theme.System -> isSystemInDarkTheme() - Theme.Dark -> true + Theme.Dark, Theme.Black -> true Theme.Light -> false } } diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt index 6e15233013..3e8ce06cdb 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt @@ -19,6 +19,7 @@ import io.element.android.compound.previews.IconsCompoundPreviewRtl import io.element.android.compound.previews.IconsPreview import io.element.android.compound.screenshot.utils.screenshotFile import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import io.element.android.compound.tokens.generated.CompoundIcons import kotlinx.collections.immutable.toImmutableList import org.junit.Test @@ -56,7 +57,7 @@ class CompoundIconTest { val content: List<@Composable ColumnScope.() -> Unit> = CompoundIcons.all.map { @Composable { Icon(imageVector = it, contentDescription = null) } } - ElementTheme(darkTheme = true) { + ElementTheme(theme = Theme.Dark) { IconsPreview( title = "Compound Vector Icons", content = content.toImmutableList() diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt index 2fe3167199..dcbbc9d4ac 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt @@ -24,6 +24,7 @@ import com.github.takahirom.roborazzi.captureRoboImage import io.element.android.compound.previews.ColorsSchemePreview import io.element.android.compound.screenshot.utils.screenshotFile import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -51,7 +52,7 @@ class MaterialYouThemeTest { } } captureRoboImage(file = screenshotFile("MaterialYou Theme - Dark.png")) { - ElementTheme(dynamicColor = true, darkTheme = true) { + ElementTheme(dynamicColor = true, theme = Theme.Dark) { Surface { Column( modifier = Modifier.padding(16.dp), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt index c2ee9800db..0ff1533ea0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.element.android.compound.annotations.CoreColorToken import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import io.element.android.compound.tokens.generated.internal.DarkColorTokens import io.element.android.compound.tokens.generated.internal.LightColorTokens import io.element.android.libraries.designsystem.R @@ -50,7 +51,7 @@ fun SunsetPage( ) { ElementTheme( // Always use the opposite value of the current theme - darkTheme = ElementTheme.isLightTheme, + theme = if (ElementTheme.isLightTheme) Theme.Dark else Theme.Light, applySystemBarsUpdate = false, ) { Box( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt index b0ef41e154..e4030a77ea 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt @@ -19,6 +19,7 @@ import coil3.asImage import coil3.compose.AsyncImagePreviewHandler import coil3.compose.LocalAsyncImagePreviewHandler import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.theme.Theme import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.utils.CommonDrawables @@ -26,7 +27,7 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable @Suppress("ModifierMissing") fun ElementPreview( - darkTheme: Boolean = isSystemInDarkTheme(), + theme: Theme = if (isSystemInDarkTheme()) Theme.Dark else Theme.Light, showBackground: Boolean = true, @DrawableRes drawableFallbackForImages: Int = CommonDrawables.sample_background, @@ -38,7 +39,7 @@ fun ElementPreview( ResourcesCompat.getDrawable(context.resources, drawableFallbackForImages, null)!!.asImage() } ) { - ElementTheme(darkTheme = darkTheme) { + ElementTheme(theme = theme) { if (showBackground) { // If we have a proper contentColor applied we need a Surface instead of a Box Surface(content = content) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewBlack.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewBlack.kt new file mode 100644 index 0000000000..c10b4272e2 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewBlack.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.preview + +import androidx.compose.runtime.Composable +import io.element.android.compound.theme.Theme + +@Composable +fun ElementPreviewBlack( + showBackground: Boolean = true, + content: @Composable () -> Unit +) { + ElementPreview( + theme = Theme.Black, + showBackground = showBackground, + content = content + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt index c054b318f3..ec7b485d47 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.designsystem.preview import androidx.compose.runtime.Composable +import io.element.android.compound.theme.Theme @Composable fun ElementPreviewDark( @@ -16,7 +17,7 @@ fun ElementPreviewDark( content: @Composable () -> Unit ) { ElementPreview( - darkTheme = true, + theme = Theme.Dark, showBackground = showBackground, content = content ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt index 1c2bdf3cef..1d0df3dd10 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.designsystem.preview import androidx.compose.runtime.Composable +import io.element.android.compound.theme.Theme @Composable fun ElementPreviewLight( @@ -16,7 +17,7 @@ fun ElementPreviewLight( content: @Composable () -> Unit ) { ElementPreview( - darkTheme = false, + theme = Theme.Light, showBackground = showBackground, content = content ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt index 7b29757843..a0bd489f8c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.Theme import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable @@ -40,14 +41,14 @@ fun ElementThemedPreview( if (vertical) { Column { ElementPreview( - darkTheme = false, + theme = Theme.Light, showBackground = showBackground, drawableFallbackForImages = drawableFallbackForImages, content = content, ) Spacer(modifier = Modifier.height(4.dp)) ElementPreview( - darkTheme = true, + theme = Theme.Dark, showBackground = showBackground, drawableFallbackForImages = drawableFallbackForImages, content = content @@ -56,14 +57,14 @@ fun ElementThemedPreview( } else { Row { ElementPreview( - darkTheme = false, + theme = Theme.Light, showBackground = showBackground, drawableFallbackForImages = drawableFallbackForImages, content = content, ) Spacer(modifier = Modifier.width(4.dp)) ElementPreview( - darkTheme = true, + theme = Theme.Dark, showBackground = showBackground, drawableFallbackForImages = drawableFallbackForImages, content = content diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt index 7aa0ab79b9..a7bd12bc13 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.Theme -import io.element.android.compound.theme.isDark import io.element.android.compound.theme.mapToTheme import io.element.android.compound.tokens.generated.SemanticColors import io.element.android.libraries.core.meta.BuildMeta @@ -68,7 +67,7 @@ fun ElementThemeApp( when (theme) { Theme.System -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM Theme.Light -> AppCompatDelegate.MODE_NIGHT_NO - Theme.Dark -> AppCompatDelegate.MODE_NIGHT_YES + Theme.Dark, Theme.Black -> AppCompatDelegate.MODE_NIGHT_YES } ) } @@ -76,7 +75,7 @@ fun ElementThemeApp( LocalBuildMeta provides buildMeta, ) { ElementTheme( - darkTheme = theme.isDark(), + theme = theme, content = content, compoundLight = compoundLight, compoundDark = compoundDark, diff --git a/libraries/ui-strings/src/main/res/values/temporary.xml b/libraries/ui-strings/src/main/res/values/temporary.xml new file mode 100644 index 0000000000..ba6c431d8b --- /dev/null +++ b/libraries/ui-strings/src/main/res/values/temporary.xml @@ -0,0 +1,10 @@ + + + + "Black" + diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index f4d02095fb..47877f3a95 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -30,7 +30,8 @@ class KonsistPreviewTest { .assertTrue { it.hasNameEndingWith("Preview") && it.hasNameEndingWith("LightPreview").not() && - it.hasNameEndingWith("DarkPreview").not() + it.hasNameEndingWith("DarkPreview").not() && + it.hasNameEndingWith("BlackPreview").not() } } From cea003a929bafc933335700272bd6c26dc3a4c60 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 23 Mar 2026 12:23:50 +0000 Subject: [PATCH 2/7] Update screenshots --- libraries/compound/screenshots/MaterialText Colors.png | 4 ++-- ...eferences.impl.advanced_AdvancedSettingsViewBlack_0_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_1_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_2_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_3_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_4_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_5_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_6_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_7_en.png | 3 +++ ...eferences.impl.advanced_AdvancedSettingsViewBlack_8_en.png | 3 +++ 10 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_7_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_8_en.png diff --git a/libraries/compound/screenshots/MaterialText Colors.png b/libraries/compound/screenshots/MaterialText Colors.png index f8f77ccca2..1273aee01e 100644 --- a/libraries/compound/screenshots/MaterialText Colors.png +++ b/libraries/compound/screenshots/MaterialText Colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4be10c3bb9900d27a3b406eca0cb902b0ff9cdf90e8e3cf1ae7760aa7c5d47d9 -size 377446 +oid sha256:1f1a277e76d351f48ae0041e082525422604fbf41d77fe078112349855dd3d2e +size 453512 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_0_en.png new file mode 100644 index 0000000000..da80c7af53 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:645a7fb89953ff6e72119a105ac966cabaf8ca6a68f3023534301e3471b942f2 +size 49262 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_1_en.png new file mode 100644 index 0000000000..6aa030a83a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea52fd146c53b690c1273b090c6b66701a8f40d80eb1dc693898c3dbf8b24def +size 49115 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_2_en.png new file mode 100644 index 0000000000..a31686aaad --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf3e4ffa06a77fbaa9dc9985274d6b1324fb023f5eeb2b9d229af0854b41e236 +size 49095 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_3_en.png new file mode 100644 index 0000000000..421367fee7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f68a09667dc19c935decb5b640bed2410dd16ae88c0d0ab6ca410225e48a87da +size 49111 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_4_en.png new file mode 100644 index 0000000000..fbcd0538aa --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5defae968cccb0e78ceff23912a364cf72f9ed61ea6ae3cc9770af9c39b05f9d +size 49035 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_5_en.png new file mode 100644 index 0000000000..7a3e1e9682 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c590f67875f9bdc460ab82b59cd3c9e11147bfadc59028552071b64fd440f211 +size 49259 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_6_en.png new file mode 100644 index 0000000000..9c788eb3ab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d48af05c854a8f313f10aaa774e4268756dcf7e0c2bea0e138dc6cd84137673 +size 48995 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_7_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_7_en.png new file mode 100644 index 0000000000..9d375b0b74 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1507eeffcfacfcf6c84494f09d89ef3478748ae31f8fee4a9922e4188ccb454 +size 48563 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_8_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_8_en.png new file mode 100644 index 0000000000..fac0ba3da5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewBlack_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4146fd85887aca1a0a65ef559976c1436d1fc2b0de522295f27bfaef1d904ea4 +size 55410 From 55d043788b0ae15e8fad84094d7e9f10c6b1e9c1 Mon Sep 17 00:00:00 2001 From: Timur Gilfanov Date: Thu, 26 Mar 2026 09:08:45 +0400 Subject: [PATCH 3/7] Reorder theme options in AdvancedSettingsState --- .../impl/advanced/AdvancedSettingsState.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index be443e905b..7cb9e9ce86 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -43,6 +43,13 @@ enum class ThemeOption : DropdownOption { @ReadOnlyComposable override fun getText(): String = stringResource(CommonStrings.common_system) }, + + Light { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(CommonStrings.common_light) + }, + Dark { @Composable @ReadOnlyComposable @@ -53,11 +60,5 @@ enum class ThemeOption : DropdownOption { @Composable @ReadOnlyComposable override fun getText(): String = stringResource(CommonStrings.common_black) - }, - - Light { - @Composable - @ReadOnlyComposable - override fun getText(): String = stringResource(CommonStrings.common_light) } } From 104ae4752a831ac4be23ff8d767d4087e32857c3 Mon Sep 17 00:00:00 2001 From: Timur Gilfanov Date: Thu, 2 Apr 2026 17:28:55 +0400 Subject: [PATCH 4/7] Reorder imports to align with static analysis --- .../features/preferences/impl/advanced/AdvancedSettingsView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index f8f1faebbd..2e4d6f9ce5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -28,8 +28,8 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewBlack +import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight import io.element.android.libraries.designsystem.preview.PreviewsDayNight From 5e6a6af409f7108d7e847fcae409b3a98bf5b456 Mon Sep 17 00:00:00 2001 From: Timur Gilfanov Date: Sun, 5 Apr 2026 12:03:50 +0400 Subject: [PATCH 5/7] Add "Allow black theme" feature flag --- .../io/element/android/x/MainActivity.kt | 1 + .../android/appnav/LoggedInFlowNode.kt | 3 ++ .../call/impl/ui/ElementCallActivity.kt | 3 ++ .../call/impl/ui/IncomingCallActivity.kt | 5 +++ .../impl/unlock/activity/PinUnlockActivity.kt | 3 ++ .../advanced/AdvancedSettingsPresenter.kt | 17 +++++++- .../impl/advanced/AdvancedSettingsState.kt | 2 + .../advanced/AdvancedSettingsStateProvider.kt | 4 ++ .../impl/advanced/AdvancedSettingsView.kt | 3 +- .../advanced/AdvancedSettingsPresenterTest.kt | 41 +++++++++++++++++++ .../impl/advanced/AdvancedSettingsViewTest.kt | 28 +++++++++++++ .../element/android/compound/theme/Theme.kt | 8 +++- .../android/compound/theme/ThemeTest.kt | 11 +++++ libraries/designsystem/build.gradle.kts | 1 + .../designsystem/theme/ElementThemeApp.kt | 11 +++-- .../libraries/featureflag/api/FeatureFlags.kt | 7 ++++ 16 files changed, 139 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index b522edd137..e97a040e91 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -71,6 +71,7 @@ class MainActivity : NodeActivity() { }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appBindings.preferencesStore(), + featureFlagService = appBindings.featureFlagService(), compoundLight = colors.light, compoundDark = colors.dark, buildMeta = appBindings.buildMeta() diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index a676df1d32..6c7e554c37 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -77,6 +77,7 @@ import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -144,6 +145,7 @@ class LoggedInFlowNode( private val syncService: SyncService, private val enterpriseService: EnterpriseService, private val appPreferencesStore: AppPreferencesStore, + private val featureFlagService: FeatureFlagService, private val buildMeta: BuildMeta, snackbarDispatcher: SnackbarDispatcher, private val analyticsService: AnalyticsService, @@ -667,6 +669,7 @@ class LoggedInFlowNode( }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, + featureFlagService = featureFlagService, compoundLight = colors.light, compoundDark = colors.dark, buildMeta = buildMeta, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index bf4f836294..5fa3beb36a 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -54,6 +54,7 @@ import io.element.android.libraries.audio.api.AudioFocusRequester import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.theme.ElementThemeApp +import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.preferences.api.store.AppPreferencesStore import timber.log.Timber @@ -66,6 +67,7 @@ class ElementCallActivity : @Inject lateinit var callIntentDataParser: CallIntentDataParser @Inject lateinit var presenterFactory: CallScreenPresenter.Factory @Inject lateinit var appPreferencesStore: AppPreferencesStore + @Inject lateinit var featureFlagService: FeatureFlagService @Inject lateinit var enterpriseService: EnterpriseService @Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter @Inject lateinit var buildMeta: BuildMeta @@ -114,6 +116,7 @@ class ElementCallActivity : }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, + featureFlagService = featureFlagService, compoundLight = colors.light, compoundDark = colors.dark, buildMeta = buildMeta, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index 73233fe453..2c4deab65e 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.di.annotations.AppCoroutineScope +import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.filter @@ -57,6 +58,9 @@ class IncomingCallActivity : AppCompatActivity() { @Inject lateinit var appPreferencesStore: AppPreferencesStore + @Inject + lateinit var featureFlagService: FeatureFlagService + @Inject lateinit var enterpriseService: EnterpriseService @@ -88,6 +92,7 @@ class IncomingCallActivity : AppCompatActivity() { }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, + featureFlagService = featureFlagService, compoundLight = colors.light, compoundDark = colors.dark, buildMeta = buildMeta, diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt index 6209c19be2..34adfeca99 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt @@ -30,6 +30,7 @@ import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.theme.ElementThemeApp +import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.launch @@ -43,6 +44,7 @@ class PinUnlockActivity : AppCompatActivity() { @Inject lateinit var presenter: PinUnlockPresenter @Inject lateinit var lockScreenService: LockScreenService @Inject lateinit var appPreferencesStore: AppPreferencesStore + @Inject lateinit var featureFlagService: FeatureFlagService @Inject lateinit var enterpriseService: EnterpriseService @Inject lateinit var buildMeta: BuildMeta @@ -56,6 +58,7 @@ class PinUnlockActivity : AppCompatActivity() { }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, + featureFlagService = featureFlagService, compoundLight = colors.light, compoundDark = colors.dark, buildMeta = buildMeta, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 5f95806469..ae32bb01a5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine @@ -45,8 +46,11 @@ class AdvancedSettingsPresenter( val isSharePresenceEnabled by remember { sessionPreferencesStore.isSharePresenceEnabled() }.collectAsState(initial = true) - val theme = remember { - appPreferencesStore.getThemeFlow().mapToTheme() + val isBlackThemeAllowed by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.AllowBlackTheme) + }.collectAsState(initial = false) + val theme = remember(isBlackThemeAllowed) { + appPreferencesStore.getThemeFlow().mapToTheme(isBlackThemeAllowed) }.collectAsState(initial = Theme.System) val mediaPreviewConfigState = mediaPreviewConfigStateStore.state() @@ -66,6 +70,14 @@ class AdvancedSettingsPresenter( value = featureFlagService.isFeatureEnabled(FeatureFlags.SelectableMediaQuality) } + val availableThemeOptions = remember(isBlackThemeAllowed) { + if (isBlackThemeAllowed) { + ThemeOption.entries + } else { + ThemeOption.entries.filterNot { it == ThemeOption.Black } + }.toImmutableList() + } + val mediaOptimizationState by produceState(null) { val hasSplitMediaQualityOptionsFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.SelectableMediaQuality) combine( @@ -119,6 +131,7 @@ class AdvancedSettingsPresenter( isSharePresenceEnabled = isSharePresenceEnabled, mediaOptimizationState = mediaOptimizationState, theme = themeOption, + availableThemeOptions = availableThemeOptions, mediaPreviewConfigState = mediaPreviewConfigState, eventSink = ::handleEvent, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index 7cb9e9ce86..0525130048 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -14,12 +14,14 @@ import androidx.compose.ui.res.stringResource import io.element.android.libraries.designsystem.components.preferences.DropdownOption import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList data class AdvancedSettingsState( val isDeveloperModeEnabled: Boolean, val isSharePresenceEnabled: Boolean, val mediaOptimizationState: MediaOptimizationState?, val theme: ThemeOption, + val availableThemeOptions: ImmutableList, val mediaPreviewConfigState: MediaPreviewConfigState, val eventSink: (AdvancedSettingsEvents) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt index 6cbe6e5c51..87df614074 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -12,6 +12,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.preferences.api.store.VideoCompressionPreset +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList open class AdvancedSettingsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -36,6 +38,7 @@ fun aAdvancedSettingsState( isSharePresenceEnabled: Boolean = false, mediaOptimizationState: MediaOptimizationState = MediaOptimizationState.AllMedia(isEnabled = false), theme: ThemeOption = ThemeOption.System, + availableThemeOptions: ImmutableList = ThemeOption.entries.toImmutableList(), hideInviteAvatars: Boolean = false, timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On, setTimelineMediaPreviewAction: AsyncAction = AsyncAction.Uninitialized, @@ -46,6 +49,7 @@ fun aAdvancedSettingsState( isSharePresenceEnabled = isSharePresenceEnabled, mediaOptimizationState = mediaOptimizationState, theme = theme, + availableThemeOptions = availableThemeOptions, mediaPreviewConfigState = MediaPreviewConfigState( hideInviteAvatars = hideInviteAvatars, timelineMediaPreviewValue = timelineMediaPreviewValue, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index 2e4d6f9ce5..230f5fa739 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -47,7 +47,6 @@ import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction -import kotlinx.collections.immutable.toImmutableList @Composable fun AdvancedSettingsView( @@ -75,7 +74,7 @@ fun AdvancedSettingsView( PreferenceDropdown( title = stringResource(id = CommonStrings.common_appearance), selectedOption = state.theme, - options = ThemeOption.entries.toImmutableList(), + options = state.availableThemeOptions, onSelectOption = { themeOption -> state.eventSink(AdvancedSettingsEvents.SetTheme(themeOption)) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index 942d549dab..fe121e4fe9 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -12,6 +12,7 @@ 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.compound.theme.Theme import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService @@ -20,6 +21,7 @@ import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -40,6 +42,9 @@ class AdvancedSettingsPresenterTest { assertThat(isSharePresenceEnabled).isTrue() assertThat(mediaOptimizationState).isNull() assertThat(theme).isEqualTo(ThemeOption.System) + assertThat(availableThemeOptions).isEqualTo( + listOf(ThemeOption.System, ThemeOption.Light, ThemeOption.Dark).toImmutableList() + ) assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse() assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On) assertThat(mediaPreviewConfigState.setHideInviteAvatarsAction).isEqualTo(AsyncAction.Uninitialized) @@ -204,6 +209,42 @@ class AdvancedSettingsPresenterTest { } } + @Test + fun `present - black theme option shown when feature flag enabled`() = runTest { + val presenter = createAdvancedSettingsPresenter( + featureFlagService = FakeFeatureFlagService().apply { + setFeatureEnabled(FeatureFlags.AllowBlackTheme, true) + } + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + + with(awaitItem()) { + assertThat(availableThemeOptions).contains(ThemeOption.Black) + assertThat(availableThemeOptions).isEqualTo(ThemeOption.entries.toImmutableList()) + } + } + } + + @Test + fun `present - stored black theme falls back to dark when feature flag disabled`() = runTest { + val appPreferencesStore = InMemoryAppPreferencesStore().apply { + setTheme(Theme.Black.name) + } + val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + + with(awaitItem()) { + assertThat(theme).isEqualTo(ThemeOption.Dark) + } + } + } + @Test fun `present - hide invite avatars`() = runTest { val mediaPreviewStore = FakeMediaPreviewConfigStateStore() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt index 36fd30983e..e46e350415 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt @@ -12,6 +12,7 @@ import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Interaction @@ -24,9 +25,11 @@ import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.assertNoNodeWithText import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack +import kotlinx.collections.immutable.toImmutableList import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule @@ -65,6 +68,31 @@ class AdvancedSettingsViewTest { eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark)) } + @Test + fun `black theme is shown when available`() { + rule.setAdvancedSettingsView( + state = aAdvancedSettingsState( + availableThemeOptions = ThemeOption.entries.toImmutableList(), + ), + ) + rule.clickOn(CommonStrings.common_appearance) + rule.run { + val text = activity.getString(CommonStrings.common_black) + onNodeWithText(text).assertExists() + } + } + + @Test + fun `black theme is hidden when unavailable`() { + rule.setAdvancedSettingsView( + state = aAdvancedSettingsState( + availableThemeOptions = ThemeOption.entries.filterNot { it == ThemeOption.Black }.toImmutableList(), + ), + ) + rule.clickOn(CommonStrings.common_appearance) + rule.assertNoNodeWithText(CommonStrings.common_black) + } + @Test fun `clicking on View source emits the expected event`() { val eventsRecorder = EventsRecorder() diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt index fd00abb4f4..04cb90b746 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt @@ -20,6 +20,10 @@ enum class Theme { Light, } +private fun Theme.coerceBlackTheme(allowBlackTheme: Boolean): Theme { + return if (this == Theme.Black && !allowBlackTheme) Theme.Dark else this +} + @Composable fun Theme.isDark(): Boolean { return when (this) { @@ -29,9 +33,9 @@ fun Theme.isDark(): Boolean { } } -fun Flow.mapToTheme(): Flow = map { +fun Flow.mapToTheme(allowBlackTheme: Boolean = true): Flow = map { when (it) { null -> Theme.System else -> Theme.valueOf(it) - } + }.coerceBlackTheme(allowBlackTheme) } diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt index 8fd7c5f041..3f43cfea67 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt @@ -15,6 +15,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Test @@ -72,4 +73,14 @@ class ThemeTest { assertThat(awaitItem()).isTrue() } } + + @Test + fun `mapToTheme falls back to dark when black theme is disabled`() = runTest { + flowOf(Theme.Black.name) + .mapToTheme(allowBlackTheme = false) + .test { + assertThat(awaitItem()).isEqualTo(Theme.Dark) + awaitComplete() + } + } } diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts index bdb9a32e89..c159659fb0 100644 --- a/libraries/designsystem/build.gradle.kts +++ b/libraries/designsystem/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.core) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.preferences.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt index a7bd12bc13..c0700620bc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt @@ -22,6 +22,8 @@ import io.element.android.compound.theme.mapToTheme import io.element.android.compound.tokens.generated.SemanticColors import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.preferences.api.store.AppPreferencesStore val LocalBuildMeta = staticCompositionLocalOf { @@ -53,15 +55,18 @@ val LocalBuildMeta = staticCompositionLocalOf { @Composable fun ElementThemeApp( appPreferencesStore: AppPreferencesStore, + featureFlagService: FeatureFlagService, compoundLight: SemanticColors, compoundDark: SemanticColors, buildMeta: BuildMeta, content: @Composable () -> Unit, ) { + val isBlackThemeAllowed by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.AllowBlackTheme) + }.collectAsState(initial = false) val theme by remember { - appPreferencesStore.getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) + appPreferencesStore.getThemeFlow().mapToTheme(allowBlackTheme = isBlackThemeAllowed) + }.collectAsState(initial = Theme.System) LaunchedEffect(theme) { AppCompatDelegate.setDefaultNightMode( when (theme) { diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 6cd9dec60c..40e99fa07f 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -147,6 +147,13 @@ enum class FeatureFlags( defaultValue = { false }, isFinished = false, ), + AllowBlackTheme( + key = "feature.allow_black_theme", + title = "Allow black theme", + description = "Allow selecting the black appearance theme for battery saving on OLED.", + defaultValue = { false }, + isFinished = false, + ), LiveLocationSharing( key = "feature.liveLocationSharing", title = "Live location sharing", From 43bef7f1df6ac3e5aee86b1f4ddddfcbf70f9272 Mon Sep 17 00:00:00 2001 From: Timur Gilfanov Date: Sun, 5 Apr 2026 12:34:07 +0400 Subject: [PATCH 6/7] Add isBlackThemeAllowed as a key to theme remember block --- .../android/libraries/designsystem/theme/ElementThemeApp.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt index c0700620bc..0c2fdff0ab 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt @@ -64,7 +64,7 @@ fun ElementThemeApp( val isBlackThemeAllowed by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.AllowBlackTheme) }.collectAsState(initial = false) - val theme by remember { + val theme by remember(isBlackThemeAllowed) { appPreferencesStore.getThemeFlow().mapToTheme(allowBlackTheme = isBlackThemeAllowed) }.collectAsState(initial = Theme.System) LaunchedEffect(theme) { From fd92a2eef25d4b90ce749b054fa2f6230bc8f581 Mon Sep 17 00:00:00 2001 From: Timur Gilfanov Date: Sun, 5 Apr 2026 12:34:25 +0400 Subject: [PATCH 7/7] Remove default value for `allowBlackTheme` in `mapToTheme` --- .../src/main/kotlin/io/element/android/compound/theme/Theme.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt index 04cb90b746..bf5932da59 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt @@ -33,7 +33,7 @@ fun Theme.isDark(): Boolean { } } -fun Flow.mapToTheme(allowBlackTheme: Boolean = true): Flow = map { +fun Flow.mapToTheme(allowBlackTheme: Boolean): Flow = map { when (it) { null -> Theme.System else -> Theme.valueOf(it)