Merge pull request #6441 from timurgilfanov/feature-oled-black
Add Black theme option for battery saving on OLED displays
This commit is contained in:
commit
a82e3425be
44 changed files with 293 additions and 50 deletions
|
|
@ -71,6 +71,7 @@ class MainActivity : NodeActivity() {
|
||||||
}.collectAsState(SemanticColorsLightDark.default)
|
}.collectAsState(SemanticColorsLightDark.default)
|
||||||
ElementThemeApp(
|
ElementThemeApp(
|
||||||
appPreferencesStore = appBindings.preferencesStore(),
|
appPreferencesStore = appBindings.preferencesStore(),
|
||||||
|
featureFlagService = appBindings.featureFlagService(),
|
||||||
compoundLight = colors.light,
|
compoundLight = colors.light,
|
||||||
compoundDark = colors.dark,
|
compoundDark = colors.dark,
|
||||||
buildMeta = appBindings.buildMeta()
|
buildMeta = appBindings.buildMeta()
|
||||||
|
|
|
||||||
|
|
@ -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.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
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.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -144,6 +145,7 @@ class LoggedInFlowNode(
|
||||||
private val syncService: SyncService,
|
private val syncService: SyncService,
|
||||||
private val enterpriseService: EnterpriseService,
|
private val enterpriseService: EnterpriseService,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
snackbarDispatcher: SnackbarDispatcher,
|
snackbarDispatcher: SnackbarDispatcher,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
|
|
@ -671,6 +673,7 @@ class LoggedInFlowNode(
|
||||||
}.collectAsState(SemanticColorsLightDark.default)
|
}.collectAsState(SemanticColorsLightDark.default)
|
||||||
ElementThemeApp(
|
ElementThemeApp(
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
compoundLight = colors.light,
|
compoundLight = colors.light,
|
||||||
compoundDark = colors.dark,
|
compoundDark = colors.dark,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
|
|
||||||
|
|
@ -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.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
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 io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -66,6 +67,7 @@ class ElementCallActivity :
|
||||||
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
||||||
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
||||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
|
@Inject lateinit var featureFlagService: FeatureFlagService
|
||||||
@Inject lateinit var enterpriseService: EnterpriseService
|
@Inject lateinit var enterpriseService: EnterpriseService
|
||||||
@Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter
|
@Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter
|
||||||
@Inject lateinit var buildMeta: BuildMeta
|
@Inject lateinit var buildMeta: BuildMeta
|
||||||
|
|
@ -114,6 +116,7 @@ class ElementCallActivity :
|
||||||
}.collectAsState(SemanticColorsLightDark.default)
|
}.collectAsState(SemanticColorsLightDark.default)
|
||||||
ElementThemeApp(
|
ElementThemeApp(
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
compoundLight = colors.light,
|
compoundLight = colors.light,
|
||||||
compoundDark = colors.dark,
|
compoundDark = colors.dark,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.architecture.bindings
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
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 io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
|
@ -57,6 +58,9 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferencesStore: AppPreferencesStore
|
lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var featureFlagService: FeatureFlagService
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var enterpriseService: EnterpriseService
|
lateinit var enterpriseService: EnterpriseService
|
||||||
|
|
||||||
|
|
@ -88,6 +92,7 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||||
}.collectAsState(SemanticColorsLightDark.default)
|
}.collectAsState(SemanticColorsLightDark.default)
|
||||||
ElementThemeApp(
|
ElementThemeApp(
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
compoundLight = colors.light,
|
compoundLight = colors.light,
|
||||||
compoundDark = colors.dark,
|
compoundDark = colors.dark,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
|
|
||||||
|
|
@ -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.architecture.bindings
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
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 io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ class PinUnlockActivity : AppCompatActivity() {
|
||||||
@Inject lateinit var presenter: PinUnlockPresenter
|
@Inject lateinit var presenter: PinUnlockPresenter
|
||||||
@Inject lateinit var lockScreenService: LockScreenService
|
@Inject lateinit var lockScreenService: LockScreenService
|
||||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
|
@Inject lateinit var featureFlagService: FeatureFlagService
|
||||||
@Inject lateinit var enterpriseService: EnterpriseService
|
@Inject lateinit var enterpriseService: EnterpriseService
|
||||||
@Inject lateinit var buildMeta: BuildMeta
|
@Inject lateinit var buildMeta: BuildMeta
|
||||||
|
|
||||||
|
|
@ -56,6 +58,7 @@ class PinUnlockActivity : AppCompatActivity() {
|
||||||
}.collectAsState(SemanticColorsLightDark.default)
|
}.collectAsState(SemanticColorsLightDark.default)
|
||||||
ElementThemeApp(
|
ElementThemeApp(
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
compoundLight = colors.light,
|
compoundLight = colors.light,
|
||||||
compoundDark = colors.dark,
|
compoundDark = colors.dark,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.features.messages.impl.timeline.components.event.TimelineItemAspectRatioBox
|
||||||
import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
|
import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -49,7 +50,7 @@ fun ProtectedView(
|
||||||
.background(Color(0x99000000)),
|
.background(Color(0x99000000)),
|
||||||
contentAlignment = Alignment.Center,
|
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
|
// Not using a button to be able to have correct size
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -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.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
@ -45,8 +46,11 @@ class AdvancedSettingsPresenter(
|
||||||
val isSharePresenceEnabled by remember {
|
val isSharePresenceEnabled by remember {
|
||||||
sessionPreferencesStore.isSharePresenceEnabled()
|
sessionPreferencesStore.isSharePresenceEnabled()
|
||||||
}.collectAsState(initial = true)
|
}.collectAsState(initial = true)
|
||||||
val theme = remember {
|
val isBlackThemeAllowed by remember {
|
||||||
appPreferencesStore.getThemeFlow().mapToTheme()
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.AllowBlackTheme)
|
||||||
|
}.collectAsState(initial = false)
|
||||||
|
val theme = remember(isBlackThemeAllowed) {
|
||||||
|
appPreferencesStore.getThemeFlow().mapToTheme(isBlackThemeAllowed)
|
||||||
}.collectAsState(initial = Theme.System)
|
}.collectAsState(initial = Theme.System)
|
||||||
|
|
||||||
val mediaPreviewConfigState = mediaPreviewConfigStateStore.state()
|
val mediaPreviewConfigState = mediaPreviewConfigStateStore.state()
|
||||||
|
|
@ -56,6 +60,7 @@ class AdvancedSettingsPresenter(
|
||||||
when (theme.value) {
|
when (theme.value) {
|
||||||
Theme.System -> ThemeOption.System
|
Theme.System -> ThemeOption.System
|
||||||
Theme.Dark -> ThemeOption.Dark
|
Theme.Dark -> ThemeOption.Dark
|
||||||
|
Theme.Black -> ThemeOption.Black
|
||||||
Theme.Light -> ThemeOption.Light
|
Theme.Light -> ThemeOption.Light
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +70,14 @@ class AdvancedSettingsPresenter(
|
||||||
value = featureFlagService.isFeatureEnabled(FeatureFlags.SelectableMediaQuality)
|
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<MediaOptimizationState?>(null) {
|
val mediaOptimizationState by produceState<MediaOptimizationState?>(null) {
|
||||||
val hasSplitMediaQualityOptionsFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.SelectableMediaQuality)
|
val hasSplitMediaQualityOptionsFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.SelectableMediaQuality)
|
||||||
combine(
|
combine(
|
||||||
|
|
@ -98,6 +111,7 @@ class AdvancedSettingsPresenter(
|
||||||
when (event.theme) {
|
when (event.theme) {
|
||||||
ThemeOption.System -> appPreferencesStore.setTheme(Theme.System.name)
|
ThemeOption.System -> appPreferencesStore.setTheme(Theme.System.name)
|
||||||
ThemeOption.Dark -> appPreferencesStore.setTheme(Theme.Dark.name)
|
ThemeOption.Dark -> appPreferencesStore.setTheme(Theme.Dark.name)
|
||||||
|
ThemeOption.Black -> appPreferencesStore.setTheme(Theme.Black.name)
|
||||||
ThemeOption.Light -> appPreferencesStore.setTheme(Theme.Light.name)
|
ThemeOption.Light -> appPreferencesStore.setTheme(Theme.Light.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +131,7 @@ class AdvancedSettingsPresenter(
|
||||||
isSharePresenceEnabled = isSharePresenceEnabled,
|
isSharePresenceEnabled = isSharePresenceEnabled,
|
||||||
mediaOptimizationState = mediaOptimizationState,
|
mediaOptimizationState = mediaOptimizationState,
|
||||||
theme = themeOption,
|
theme = themeOption,
|
||||||
|
availableThemeOptions = availableThemeOptions,
|
||||||
mediaPreviewConfigState = mediaPreviewConfigState,
|
mediaPreviewConfigState = mediaPreviewConfigState,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.libraries.designsystem.components.preferences.DropdownOption
|
import io.element.android.libraries.designsystem.components.preferences.DropdownOption
|
||||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class AdvancedSettingsState(
|
data class AdvancedSettingsState(
|
||||||
val isDeveloperModeEnabled: Boolean,
|
val isDeveloperModeEnabled: Boolean,
|
||||||
val isSharePresenceEnabled: Boolean,
|
val isSharePresenceEnabled: Boolean,
|
||||||
val mediaOptimizationState: MediaOptimizationState?,
|
val mediaOptimizationState: MediaOptimizationState?,
|
||||||
val theme: ThemeOption,
|
val theme: ThemeOption,
|
||||||
|
val availableThemeOptions: ImmutableList<ThemeOption>,
|
||||||
val mediaPreviewConfigState: MediaPreviewConfigState,
|
val mediaPreviewConfigState: MediaPreviewConfigState,
|
||||||
val eventSink: (AdvancedSettingsEvents) -> Unit
|
val eventSink: (AdvancedSettingsEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
@ -43,14 +45,22 @@ enum class ThemeOption : DropdownOption {
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
override fun getText(): String = stringResource(CommonStrings.common_system)
|
override fun getText(): String = stringResource(CommonStrings.common_system)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Light {
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
override fun getText(): String = stringResource(CommonStrings.common_light)
|
||||||
|
},
|
||||||
|
|
||||||
Dark {
|
Dark {
|
||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
override fun getText(): String = stringResource(CommonStrings.common_dark)
|
override fun getText(): String = stringResource(CommonStrings.common_dark)
|
||||||
},
|
},
|
||||||
Light {
|
|
||||||
|
Black {
|
||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
override fun getText(): String = stringResource(CommonStrings.common_light)
|
override fun getText(): String = stringResource(CommonStrings.common_black)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> {
|
open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> {
|
||||||
override val values: Sequence<AdvancedSettingsState>
|
override val values: Sequence<AdvancedSettingsState>
|
||||||
|
|
@ -36,6 +38,7 @@ fun aAdvancedSettingsState(
|
||||||
isSharePresenceEnabled: Boolean = false,
|
isSharePresenceEnabled: Boolean = false,
|
||||||
mediaOptimizationState: MediaOptimizationState = MediaOptimizationState.AllMedia(isEnabled = false),
|
mediaOptimizationState: MediaOptimizationState = MediaOptimizationState.AllMedia(isEnabled = false),
|
||||||
theme: ThemeOption = ThemeOption.System,
|
theme: ThemeOption = ThemeOption.System,
|
||||||
|
availableThemeOptions: ImmutableList<ThemeOption> = ThemeOption.entries.toImmutableList(),
|
||||||
hideInviteAvatars: Boolean = false,
|
hideInviteAvatars: Boolean = false,
|
||||||
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
|
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
|
||||||
setTimelineMediaPreviewAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
setTimelineMediaPreviewAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
|
|
@ -46,6 +49,7 @@ fun aAdvancedSettingsState(
|
||||||
isSharePresenceEnabled = isSharePresenceEnabled,
|
isSharePresenceEnabled = isSharePresenceEnabled,
|
||||||
mediaOptimizationState = mediaOptimizationState,
|
mediaOptimizationState = mediaOptimizationState,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
|
availableThemeOptions = availableThemeOptions,
|
||||||
mediaPreviewConfigState = MediaPreviewConfigState(
|
mediaPreviewConfigState = MediaPreviewConfigState(
|
||||||
hideInviteAvatars = hideInviteAvatars,
|
hideInviteAvatars = hideInviteAvatars,
|
||||||
timelineMediaPreviewValue = timelineMediaPreviewValue,
|
timelineMediaPreviewValue = timelineMediaPreviewValue,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ 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.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewBlack
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||||
|
|
@ -46,7 +47,6 @@ import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.compose.LocalAnalyticsService
|
import io.element.android.services.analytics.compose.LocalAnalyticsService
|
||||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AdvancedSettingsView(
|
fun AdvancedSettingsView(
|
||||||
|
|
@ -74,7 +74,7 @@ fun AdvancedSettingsView(
|
||||||
PreferenceDropdown(
|
PreferenceDropdown(
|
||||||
title = stringResource(id = CommonStrings.common_appearance),
|
title = stringResource(id = CommonStrings.common_appearance),
|
||||||
selectedOption = state.theme,
|
selectedOption = state.theme,
|
||||||
options = ThemeOption.entries.toImmutableList(),
|
options = state.availableThemeOptions,
|
||||||
onSelectOption = { themeOption ->
|
onSelectOption = { themeOption ->
|
||||||
state.eventSink(AdvancedSettingsEvents.SetTheme(themeOption))
|
state.eventSink(AdvancedSettingsEvents.SetTheme(themeOption))
|
||||||
}
|
}
|
||||||
|
|
@ -324,6 +324,11 @@ internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettings
|
||||||
internal fun AdvancedSettingsViewDarkPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) =
|
internal fun AdvancedSettingsViewDarkPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) =
|
||||||
ElementPreviewDark { ContentToPreview(state) }
|
ElementPreviewDark { ContentToPreview(state) }
|
||||||
|
|
||||||
|
@PreviewWithLargeHeight
|
||||||
|
@Composable
|
||||||
|
internal fun AdvancedSettingsViewBlackPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) =
|
||||||
|
ElementPreviewBlack { ContentToPreview(state) }
|
||||||
|
|
||||||
@ExcludeFromCoverage
|
@ExcludeFromCoverage
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(state: AdvancedSettingsState) {
|
private fun ContentToPreview(state: AdvancedSettingsState) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.architecture.AsyncAction
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
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.InMemoryAppPreferencesStore
|
||||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -40,6 +42,9 @@ class AdvancedSettingsPresenterTest {
|
||||||
assertThat(isSharePresenceEnabled).isTrue()
|
assertThat(isSharePresenceEnabled).isTrue()
|
||||||
assertThat(mediaOptimizationState).isNull()
|
assertThat(mediaOptimizationState).isNull()
|
||||||
assertThat(theme).isEqualTo(ThemeOption.System)
|
assertThat(theme).isEqualTo(ThemeOption.System)
|
||||||
|
assertThat(availableThemeOptions).isEqualTo(
|
||||||
|
listOf(ThemeOption.System, ThemeOption.Light, ThemeOption.Dark).toImmutableList()
|
||||||
|
)
|
||||||
assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse()
|
assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse()
|
||||||
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||||
assertThat(mediaPreviewConfigState.setHideInviteAvatarsAction).isEqualTo(AsyncAction.Uninitialized)
|
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
|
@Test
|
||||||
fun `present - hide invite avatars`() = runTest {
|
fun `present - hide invite avatars`() = runTest {
|
||||||
val mediaPreviewStore = FakeMediaPreviewConfigStateStore()
|
val mediaPreviewStore = FakeMediaPreviewConfigStateStore()
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
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.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
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.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
|
|
@ -65,6 +68,31 @@ class AdvancedSettingsViewTest {
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark))
|
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
|
@Test
|
||||||
fun `clicking on View source emits the expected event`() {
|
fun `clicking on View source emits the expected event`() {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4be10c3bb9900d27a3b406eca0cb902b0ff9cdf90e8e3cf1ae7760aa7c5d47d9
|
oid sha256:1f1a277e76d351f48ae0041e082525422604fbf41d77fe078112349855dd3d2e
|
||||||
size 377446
|
size 453512
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -62,7 +63,7 @@ internal fun IconsCompoundPreviewRtl() = ElementTheme {
|
||||||
|
|
||||||
@Preview(widthDp = 730, heightDp = 1920)
|
@Preview(widthDp = 730, heightDp = 1920)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun IconsCompoundPreviewDark() = ElementTheme(darkTheme = true) {
|
internal fun IconsCompoundPreviewDark() = ElementTheme(theme = Theme.Dark) {
|
||||||
IconsCompoundPreview()
|
IconsCompoundPreview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.compound.tokens.generated.compoundColorsHcDark
|
import io.element.android.compound.tokens.generated.compoundColorsHcDark
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
|
@ -65,7 +66,7 @@ internal fun CompoundSemanticColorsLightHc() = ElementTheme(
|
||||||
|
|
||||||
@Preview(heightDp = 2000)
|
@Preview(heightDp = 2000)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CompoundSemanticColorsDark() = ElementTheme(darkTheme = true) {
|
internal fun CompoundSemanticColorsDark() = ElementTheme(theme = Theme.Dark) {
|
||||||
Surface {
|
Surface {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
|
|
@ -85,7 +86,7 @@ internal fun CompoundSemanticColorsDark() = ElementTheme(darkTheme = true) {
|
||||||
@Preview(heightDp = 2000)
|
@Preview(heightDp = 2000)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CompoundSemanticColorsDarkHc() = ElementTheme(
|
internal fun CompoundSemanticColorsDarkHc() = ElementTheme(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
compoundDark = compoundColorsHcDark,
|
compoundDark = compoundColorsHcDark,
|
||||||
) {
|
) {
|
||||||
Surface {
|
Surface {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ internal fun AvatarColorsPreviewLight() {
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AvatarColorsPreviewDark() {
|
internal fun AvatarColorsPreviewDark() {
|
||||||
ElementTheme(darkTheme = true) {
|
ElementTheme(theme = Theme.Dark) {
|
||||||
val chunks = avatarColors().chunked(4)
|
val chunks = avatarColors().chunked(4)
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
for (chunk in chunks) {
|
for (chunk in chunks) {
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,10 @@ internal val LocalCompoundColors = staticCompositionLocalOf { compoundColorsLigh
|
||||||
/**
|
/**
|
||||||
* Sets up the theme for the application, or a part of it.
|
* 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.
|
* @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.
|
* 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 dynamicColor whether to enable MaterialYou or not. It's `false` by default.
|
||||||
* @param compoundLight the [SemanticColors] to use in light theme.
|
* @param compoundLight the [SemanticColors] to use in light theme.
|
||||||
* @param compoundDark the [SemanticColors] to use in dark theme.
|
* @param compoundDark the [SemanticColors] to use in dark theme.
|
||||||
|
|
@ -91,9 +91,9 @@ internal val LocalCompoundColors = staticCompositionLocalOf { compoundColorsLigh
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ElementTheme(
|
fun ElementTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
theme: Theme = if (isSystemInDarkTheme()) Theme.Dark else Theme.Light,
|
||||||
applySystemBarsUpdate: Boolean = true,
|
applySystemBarsUpdate: Boolean = true,
|
||||||
lightStatusBar: Boolean = !darkTheme,
|
lightStatusBar: Boolean = !theme.isDark(),
|
||||||
// true to enable MaterialYou
|
// true to enable MaterialYou
|
||||||
dynamicColor: Boolean = false,
|
dynamicColor: Boolean = false,
|
||||||
compoundLight: SemanticColors = compoundColorsLight,
|
compoundLight: SemanticColors = compoundColorsLight,
|
||||||
|
|
@ -103,8 +103,13 @@ fun ElementTheme(
|
||||||
typography: Typography = compoundTypography,
|
typography: Typography = compoundTypography,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val darkTheme = theme.isDark()
|
||||||
val currentCompoundColor = when {
|
val currentCompoundColor = when {
|
||||||
darkTheme -> compoundDark
|
darkTheme -> if (theme == Theme.Black) {
|
||||||
|
compoundDark.copy(bgCanvasDefault = Color.Black)
|
||||||
|
} else {
|
||||||
|
compoundDark
|
||||||
|
}
|
||||||
else -> compoundLight
|
else -> compoundLight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +118,11 @@ fun ElementTheme(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
}
|
}
|
||||||
darkTheme -> materialColorsDark
|
darkTheme -> if (theme == Theme.Black) {
|
||||||
|
currentCompoundColor.toMaterialColorScheme()
|
||||||
|
} else {
|
||||||
|
materialColorsDark
|
||||||
|
}
|
||||||
else -> materialColorsLight
|
else -> materialColorsLight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +139,7 @@ fun ElementTheme(
|
||||||
|
|
||||||
if (applySystemBarsUpdate) {
|
if (applySystemBarsUpdate) {
|
||||||
val activity = LocalActivity.current as? ComponentActivity
|
val activity = LocalActivity.current as? ComponentActivity
|
||||||
LaunchedEffect(statusBarColorScheme, darkTheme, lightStatusBar) {
|
LaunchedEffect(statusBarColorScheme, theme, lightStatusBar) {
|
||||||
activity?.enableEdgeToEdge(
|
activity?.enableEdgeToEdge(
|
||||||
// For Status bar use the background color of the app
|
// For Status bar use the background color of the app
|
||||||
statusBarStyle = SystemBarStyle.auto(
|
statusBarStyle = SystemBarStyle.auto(
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ fun ForcedDarkElementTheme(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ElementTheme(
|
ElementTheme(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
compoundLight = colors.light,
|
compoundLight = colors.light,
|
||||||
compoundDark = colors.dark,
|
compoundDark = colors.dark,
|
||||||
lightStatusBar = lightStatusBar,
|
lightStatusBar = lightStatusBar,
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,15 @@ internal fun MaterialTextPreview() = Row(
|
||||||
) {
|
) {
|
||||||
MaterialPreview(
|
MaterialPreview(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
darkTheme = false,
|
theme = Theme.Light,
|
||||||
)
|
)
|
||||||
MaterialPreview(
|
MaterialPreview(
|
||||||
modifier = Modifier.weight(1f),
|
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
|
@Composable
|
||||||
private fun MaterialPreview(
|
private fun MaterialPreview(
|
||||||
darkTheme: Boolean,
|
theme: Theme,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) = Column(modifier = modifier) {
|
) = Column(modifier = modifier) {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -60,13 +64,13 @@ private fun MaterialPreview(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
text = if (darkTheme) "Dark" else "Light",
|
text = theme.name,
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
ElementTheme(
|
ElementTheme(
|
||||||
darkTheme = darkTheme,
|
theme = theme,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ internal fun ColorsSchemeLightHcPreview() = ElementTheme(
|
||||||
@Preview(heightDp = 1200)
|
@Preview(heightDp = 1200)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColorsSchemeDarkPreview() = ElementTheme(
|
internal fun ColorsSchemeDarkPreview() = ElementTheme(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
) {
|
) {
|
||||||
ColorsSchemePreview(
|
ColorsSchemePreview(
|
||||||
Color.White,
|
Color.White,
|
||||||
|
|
@ -62,7 +62,7 @@ internal fun ColorsSchemeDarkPreview() = ElementTheme(
|
||||||
@Preview(heightDp = 1200)
|
@Preview(heightDp = 1200)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColorsSchemeDarkHcPreview() = ElementTheme(
|
internal fun ColorsSchemeDarkHcPreview() = ElementTheme(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
compoundDark = compoundColorsHcDark,
|
compoundDark = compoundColorsHcDark,
|
||||||
) {
|
) {
|
||||||
ColorsSchemePreview(
|
ColorsSchemePreview(
|
||||||
|
|
@ -71,3 +71,15 @@ internal fun ColorsSchemeDarkHcPreview() = ElementTheme(
|
||||||
ElementTheme.materialColors,
|
ElementTheme.materialColors,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(heightDp = 1200)
|
||||||
|
@Composable
|
||||||
|
internal fun ColorsSchemeBlackPreview() = ElementTheme(
|
||||||
|
theme = Theme.Black
|
||||||
|
) {
|
||||||
|
ColorsSchemePreview(
|
||||||
|
Color.White,
|
||||||
|
Color.Black,
|
||||||
|
ElementTheme.materialColors,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,21 +16,26 @@ import kotlinx.coroutines.flow.map
|
||||||
enum class Theme {
|
enum class Theme {
|
||||||
System,
|
System,
|
||||||
Dark,
|
Dark,
|
||||||
|
Black,
|
||||||
Light,
|
Light,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Theme.coerceBlackTheme(allowBlackTheme: Boolean): Theme {
|
||||||
|
return if (this == Theme.Black && !allowBlackTheme) Theme.Dark else this
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Theme.isDark(): Boolean {
|
fun Theme.isDark(): Boolean {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
Theme.System -> isSystemInDarkTheme()
|
Theme.System -> isSystemInDarkTheme()
|
||||||
Theme.Dark -> true
|
Theme.Dark, Theme.Black -> true
|
||||||
Theme.Light -> false
|
Theme.Light -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Flow<String?>.mapToTheme(): Flow<Theme> = map {
|
fun Flow<String?>.mapToTheme(allowBlackTheme: Boolean): Flow<Theme> = map {
|
||||||
when (it) {
|
when (it) {
|
||||||
null -> Theme.System
|
null -> Theme.System
|
||||||
else -> Theme.valueOf(it)
|
else -> Theme.valueOf(it)
|
||||||
}
|
}.coerceBlackTheme(allowBlackTheme)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import io.element.android.compound.previews.IconsCompoundPreviewRtl
|
||||||
import io.element.android.compound.previews.IconsPreview
|
import io.element.android.compound.previews.IconsPreview
|
||||||
import io.element.android.compound.screenshot.utils.screenshotFile
|
import io.element.android.compound.screenshot.utils.screenshotFile
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -56,7 +57,7 @@ class CompoundIconTest {
|
||||||
val content: List<@Composable ColumnScope.() -> Unit> = CompoundIcons.all.map {
|
val content: List<@Composable ColumnScope.() -> Unit> = CompoundIcons.all.map {
|
||||||
@Composable { Icon(imageVector = it, contentDescription = null) }
|
@Composable { Icon(imageVector = it, contentDescription = null) }
|
||||||
}
|
}
|
||||||
ElementTheme(darkTheme = true) {
|
ElementTheme(theme = Theme.Dark) {
|
||||||
IconsPreview(
|
IconsPreview(
|
||||||
title = "Compound Vector Icons",
|
title = "Compound Vector Icons",
|
||||||
content = content.toImmutableList()
|
content = content.toImmutableList()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.github.takahirom.roborazzi.captureRoboImage
|
||||||
import io.element.android.compound.previews.ColorsSchemePreview
|
import io.element.android.compound.previews.ColorsSchemePreview
|
||||||
import io.element.android.compound.screenshot.utils.screenshotFile
|
import io.element.android.compound.screenshot.utils.screenshotFile
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
@ -51,7 +52,7 @@ class MaterialYouThemeTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
captureRoboImage(file = screenshotFile("MaterialYou Theme - Dark.png")) {
|
captureRoboImage(file = screenshotFile("MaterialYou Theme - Dark.png")) {
|
||||||
ElementTheme(dynamicColor = true, darkTheme = true) {
|
ElementTheme(dynamicColor = true, theme = Theme.Dark) {
|
||||||
Surface {
|
Surface {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -72,4 +73,14 @@ class ThemeTest {
|
||||||
assertThat(awaitItem()).isTrue()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ dependencies {
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.annotations.CoreColorToken
|
import io.element.android.compound.annotations.CoreColorToken
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.DarkColorTokens
|
||||||
import io.element.android.compound.tokens.generated.internal.LightColorTokens
|
import io.element.android.compound.tokens.generated.internal.LightColorTokens
|
||||||
import io.element.android.libraries.designsystem.R
|
import io.element.android.libraries.designsystem.R
|
||||||
|
|
@ -50,7 +51,7 @@ fun SunsetPage(
|
||||||
) {
|
) {
|
||||||
ElementTheme(
|
ElementTheme(
|
||||||
// Always use the opposite value of the current theme
|
// Always use the opposite value of the current theme
|
||||||
darkTheme = ElementTheme.isLightTheme,
|
theme = if (ElementTheme.isLightTheme) Theme.Dark else Theme.Light,
|
||||||
applySystemBarsUpdate = false,
|
applySystemBarsUpdate = false,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import coil3.asImage
|
||||||
import coil3.compose.AsyncImagePreviewHandler
|
import coil3.compose.AsyncImagePreviewHandler
|
||||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.theme.components.Surface
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("ModifierMissing")
|
@Suppress("ModifierMissing")
|
||||||
fun ElementPreview(
|
fun ElementPreview(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
theme: Theme = if (isSystemInDarkTheme()) Theme.Dark else Theme.Light,
|
||||||
showBackground: Boolean = true,
|
showBackground: Boolean = true,
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
drawableFallbackForImages: Int = CommonDrawables.sample_background,
|
drawableFallbackForImages: Int = CommonDrawables.sample_background,
|
||||||
|
|
@ -38,7 +39,7 @@ fun ElementPreview(
|
||||||
ResourcesCompat.getDrawable(context.resources, drawableFallbackForImages, null)!!.asImage()
|
ResourcesCompat.getDrawable(context.resources, drawableFallbackForImages, null)!!.asImage()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
ElementTheme(darkTheme = darkTheme) {
|
ElementTheme(theme = theme) {
|
||||||
if (showBackground) {
|
if (showBackground) {
|
||||||
// If we have a proper contentColor applied we need a Surface instead of a Box
|
// If we have a proper contentColor applied we need a Surface instead of a Box
|
||||||
Surface(content = content)
|
Surface(content = content)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.libraries.designsystem.preview
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -20,7 +21,7 @@ fun ElementPreviewDark(
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
ElementPreview(
|
ElementPreview(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
showBackground = showBackground,
|
showBackground = showBackground,
|
||||||
drawableFallbackForImages = drawableFallbackForImages,
|
drawableFallbackForImages = drawableFallbackForImages,
|
||||||
content = content,
|
content = content,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.libraries.designsystem.preview
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -20,7 +21,7 @@ fun ElementPreviewLight(
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
ElementPreview(
|
ElementPreview(
|
||||||
darkTheme = false,
|
theme = Theme.Light,
|
||||||
showBackground = showBackground,
|
showBackground = showBackground,
|
||||||
drawableFallbackForImages = drawableFallbackForImages,
|
drawableFallbackForImages = drawableFallbackForImages,
|
||||||
content = content,
|
content = content,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.Theme
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -40,14 +41,14 @@ fun ElementThemedPreview(
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
Column {
|
Column {
|
||||||
ElementPreview(
|
ElementPreview(
|
||||||
darkTheme = false,
|
theme = Theme.Light,
|
||||||
showBackground = showBackground,
|
showBackground = showBackground,
|
||||||
drawableFallbackForImages = drawableFallbackForImages,
|
drawableFallbackForImages = drawableFallbackForImages,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
ElementPreview(
|
ElementPreview(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
showBackground = showBackground,
|
showBackground = showBackground,
|
||||||
drawableFallbackForImages = drawableFallbackForImages,
|
drawableFallbackForImages = drawableFallbackForImages,
|
||||||
content = content
|
content = content
|
||||||
|
|
@ -56,14 +57,14 @@ fun ElementThemedPreview(
|
||||||
} else {
|
} else {
|
||||||
Row {
|
Row {
|
||||||
ElementPreview(
|
ElementPreview(
|
||||||
darkTheme = false,
|
theme = Theme.Light,
|
||||||
showBackground = showBackground,
|
showBackground = showBackground,
|
||||||
drawableFallbackForImages = drawableFallbackForImages,
|
drawableFallbackForImages = drawableFallbackForImages,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
ElementPreview(
|
ElementPreview(
|
||||||
darkTheme = true,
|
theme = Theme.Dark,
|
||||||
showBackground = showBackground,
|
showBackground = showBackground,
|
||||||
drawableFallbackForImages = drawableFallbackForImages,
|
drawableFallbackForImages = drawableFallbackForImages,
|
||||||
content = content
|
content = content
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,12 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.theme.Theme
|
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.theme.mapToTheme
|
||||||
import io.element.android.compound.tokens.generated.SemanticColors
|
import io.element.android.compound.tokens.generated.SemanticColors
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.core.meta.BuildType
|
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
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
|
||||||
val LocalBuildMeta = staticCompositionLocalOf {
|
val LocalBuildMeta = staticCompositionLocalOf {
|
||||||
|
|
@ -54,21 +55,24 @@ val LocalBuildMeta = staticCompositionLocalOf {
|
||||||
@Composable
|
@Composable
|
||||||
fun ElementThemeApp(
|
fun ElementThemeApp(
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
|
featureFlagService: FeatureFlagService,
|
||||||
compoundLight: SemanticColors,
|
compoundLight: SemanticColors,
|
||||||
compoundDark: SemanticColors,
|
compoundDark: SemanticColors,
|
||||||
buildMeta: BuildMeta,
|
buildMeta: BuildMeta,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val theme by remember {
|
val isBlackThemeAllowed by remember {
|
||||||
appPreferencesStore.getThemeFlow().mapToTheme()
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.AllowBlackTheme)
|
||||||
}
|
}.collectAsState(initial = false)
|
||||||
.collectAsState(initial = Theme.System)
|
val theme by remember(isBlackThemeAllowed) {
|
||||||
|
appPreferencesStore.getThemeFlow().mapToTheme(allowBlackTheme = isBlackThemeAllowed)
|
||||||
|
}.collectAsState(initial = Theme.System)
|
||||||
LaunchedEffect(theme) {
|
LaunchedEffect(theme) {
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
AppCompatDelegate.setDefaultNightMode(
|
||||||
when (theme) {
|
when (theme) {
|
||||||
Theme.System -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
Theme.System -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
Theme.Light -> AppCompatDelegate.MODE_NIGHT_NO
|
Theme.Light -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
Theme.Dark -> AppCompatDelegate.MODE_NIGHT_YES
|
Theme.Dark, Theme.Black -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +80,7 @@ fun ElementThemeApp(
|
||||||
LocalBuildMeta provides buildMeta,
|
LocalBuildMeta provides buildMeta,
|
||||||
) {
|
) {
|
||||||
ElementTheme(
|
ElementTheme(
|
||||||
darkTheme = theme.isDark(),
|
theme = theme,
|
||||||
content = content,
|
content = content,
|
||||||
compoundLight = compoundLight,
|
compoundLight = compoundLight,
|
||||||
compoundDark = compoundDark,
|
compoundDark = compoundDark,
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,13 @@ enum class FeatureFlags(
|
||||||
defaultValue = { false },
|
defaultValue = { false },
|
||||||
isFinished = 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(
|
LiveLocationSharing(
|
||||||
key = "feature.liveLocationSharing",
|
key = "feature.liveLocationSharing",
|
||||||
title = "Live location sharing",
|
title = "Live location sharing",
|
||||||
|
|
|
||||||
10
libraries/ui-strings/src/main/res/values/temporary.xml
Normal file
10
libraries/ui-strings/src/main/res/values/temporary.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
~
|
||||||
|
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
~ Please see LICENSE files in the repository root for full details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="common_black">"Black"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -30,7 +30,8 @@ class KonsistPreviewTest {
|
||||||
.assertTrue {
|
.assertTrue {
|
||||||
it.hasNameEndingWith("Preview") &&
|
it.hasNameEndingWith("Preview") &&
|
||||||
it.hasNameEndingWith("LightPreview").not() &&
|
it.hasNameEndingWith("LightPreview").not() &&
|
||||||
it.hasNameEndingWith("DarkPreview").not()
|
it.hasNameEndingWith("DarkPreview").not() &&
|
||||||
|
it.hasNameEndingWith("BlackPreview").not()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:645a7fb89953ff6e72119a105ac966cabaf8ca6a68f3023534301e3471b942f2
|
||||||
|
size 49262
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ea52fd146c53b690c1273b090c6b66701a8f40d80eb1dc693898c3dbf8b24def
|
||||||
|
size 49115
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cf3e4ffa06a77fbaa9dc9985274d6b1324fb023f5eeb2b9d229af0854b41e236
|
||||||
|
size 49095
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f68a09667dc19c935decb5b640bed2410dd16ae88c0d0ab6ca410225e48a87da
|
||||||
|
size 49111
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5defae968cccb0e78ceff23912a364cf72f9ed61ea6ae3cc9770af9c39b05f9d
|
||||||
|
size 49035
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c590f67875f9bdc460ab82b59cd3c9e11147bfadc59028552071b64fd440f211
|
||||||
|
size 49259
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8d48af05c854a8f313f10aaa774e4268756dcf7e0c2bea0e138dc6cd84137673
|
||||||
|
size 48995
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c1507eeffcfacfcf6c84494f09d89ef3478748ae31f8fee4a9922e4188ccb454
|
||||||
|
size 48563
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4146fd85887aca1a0a65ef559976c1436d1fc2b0de522295f27bfaef1d904ea4
|
||||||
|
size 55410
|
||||||
Loading…
Add table
Add a link
Reference in a new issue