PM-40885: bug: Support overriding the colorscheme (#7206)

This commit is contained in:
David Perez
2026-07-24 14:27:46 +00:00
committed by GitHub
parent b2553be779
commit 79d515f49e
6 changed files with 99 additions and 39 deletions
@@ -15,7 +15,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -28,6 +27,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.ui.platform.base.util.ColorSchemeOverride
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.StatusBarsAppearanceAffect
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
@@ -37,12 +37,11 @@ import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.composition.LocalCardTextAnalyzer
import com.bitwarden.ui.platform.feature.cardscanner.util.CardTextAnalyzer
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.platform.model.WindowSize
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.theme.LocalBitwardenColorScheme
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
import com.bitwarden.ui.platform.util.rememberWindowSize
/**
@@ -71,9 +70,7 @@ fun CardScanScreen(
}
// This screen should always look like it's in dark mode
CompositionLocalProvider(
LocalBitwardenColorScheme provides darkBitwardenColorScheme,
) {
ColorSchemeOverride(appTheme = AppTheme.DARK) {
StatusBarsAppearanceAffect()
BitwardenScaffold(
modifier = Modifier.fillMaxSize(),
@@ -15,13 +15,13 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.bitwarden.ui.platform.base.util.ColorSchemeOverride
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.StatusBarsAppearanceAffect
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
@@ -32,12 +32,11 @@ import com.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.composition.LocalQrCodeAnalyzer
import com.bitwarden.ui.platform.feature.qrcodescan.util.QrCodeAnalyzer
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.platform.model.WindowSize
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.theme.LocalBitwardenColorScheme
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
import com.bitwarden.ui.platform.util.rememberWindowSize
/**
@@ -70,7 +69,7 @@ fun QrCodeScanScreen(
}
}
// This screen should always look like it's in dark mode
CompositionLocalProvider(LocalBitwardenColorScheme provides darkBitwardenColorScheme) {
ColorSchemeOverride(appTheme = AppTheme.DARK) {
StatusBarsAppearanceAffect()
BitwardenScaffold(
modifier = Modifier.fillMaxSize(),
@@ -14,7 +14,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -25,6 +24,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.ui.platform.base.util.ColorSchemeOverride
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.StatusBarsAppearanceAffect
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
@@ -36,12 +36,11 @@ import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
import com.bitwarden.ui.platform.composition.LocalQrCodeAnalyzer
import com.bitwarden.ui.platform.feature.qrcodescan.util.QrCodeAnalyzer
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.platform.model.WindowSize
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.theme.LocalBitwardenColorScheme
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
import com.bitwarden.ui.platform.util.rememberWindowSize
/**
@@ -76,7 +75,7 @@ fun QrCodeScanScreen(
}
// This screen should always look like it's in dark mode
CompositionLocalProvider(LocalBitwardenColorScheme provides darkBitwardenColorScheme) {
ColorSchemeOverride(appTheme = AppTheme.DARK) {
StatusBarsAppearanceAffect()
QrCodeScanDialogs(
dialogState = state.dialog,
@@ -0,0 +1,44 @@
package com.bitwarden.ui.platform.base.util
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.theme.LocalBitwardenColorScheme
import com.bitwarden.ui.platform.theme.LocalBitwardenDynamicDarkColorScheme
import com.bitwarden.ui.platform.theme.LocalBitwardenDynamicLightColorScheme
import com.bitwarden.ui.platform.theme.color.BitwardenColorScheme
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
import com.bitwarden.ui.platform.theme.color.lightBitwardenColorScheme
/**
* Overrides the [BitwardenColorScheme] for the [content] based on the provided [AppTheme].
*/
@Composable
fun ColorSchemeOverride(
appTheme: AppTheme,
content: @Composable () -> Unit,
) {
val colorSchemeOverride = when (appTheme) {
AppTheme.DEFAULT -> BitwardenTheme.colorScheme
AppTheme.DARK -> {
if (BitwardenTheme.colorScheme.isDynamicTheme) {
LocalBitwardenDynamicDarkColorScheme.current
} else {
darkBitwardenColorScheme
}
}
AppTheme.LIGHT -> {
if (BitwardenTheme.colorScheme.isDynamicTheme) {
LocalBitwardenDynamicLightColorScheme.current
} else {
lightBitwardenColorScheme
}
}
}
CompositionLocalProvider(
value = LocalBitwardenColorScheme provides colorSchemeOverride,
content = content,
)
}
@@ -1,5 +1,6 @@
package com.bitwarden.ui.platform.theme
import android.content.Context
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
@@ -13,6 +14,7 @@ import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.platform.LocalContext
import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.ui.platform.components.field.interceptor.IncognitoInput
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.platform.theme.color.BitwardenColorScheme
@@ -61,39 +63,44 @@ object BitwardenTheme {
*/
@Composable
fun BitwardenTheme(
context: Context = LocalContext.current,
theme: AppTheme = AppTheme.DEFAULT,
dynamicColor: Boolean = false,
content: @Composable () -> Unit,
) {
val darkTheme = theme.isDarkMode(isSystemDarkMode = isSystemInDarkTheme())
// Get the current scheme
val materialColorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) {
dynamicDarkColorScheme(context = context)
} else {
dynamicLightColorScheme(context = context)
}
}
darkTheme -> darkColorScheme()
else -> lightColorScheme()
val isDarkTheme = theme.isDarkMode(isSystemDarkMode = isSystemInDarkTheme())
val isDynamicEnabled = dynamicColor && isBuildVersionAtLeast(version = Build.VERSION_CODES.S)
// Get the material color schemes from the OS.
val darkMaterialColorScheme = if (isDynamicEnabled) {
dynamicDarkColorScheme(context = context)
} else {
darkColorScheme()
}
val lightMaterialColorScheme = if (isDynamicEnabled) {
dynamicLightColorScheme(context = context)
} else {
lightColorScheme()
}
val materialColorScheme = if (isDarkTheme) darkMaterialColorScheme else lightMaterialColorScheme
// Convert the material color schemes to Bitwarden color schemes.
val lightDynamicColorScheme = dynamicBitwardenColorScheme(
materialColorScheme = lightMaterialColorScheme,
isDarkTheme = false,
)
val darkDynamicColorScheme = dynamicBitwardenColorScheme(
materialColorScheme = darkMaterialColorScheme,
isDarkTheme = true,
)
val bitwardenColorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
dynamicBitwardenColorScheme(
materialColorScheme = materialColorScheme,
isDarkTheme = darkTheme,
)
}
darkTheme -> darkBitwardenColorScheme
isDynamicEnabled -> if (isDarkTheme) darkDynamicColorScheme else lightDynamicColorScheme
isDarkTheme -> darkBitwardenColorScheme
else -> lightBitwardenColorScheme
}
// Provide the selected color scheme as well as the dynamic ones for override support.
CompositionLocalProvider(
LocalBitwardenColorScheme provides bitwardenColorScheme,
LocalBitwardenDynamicLightColorScheme provides lightDynamicColorScheme,
LocalBitwardenDynamicDarkColorScheme provides darkDynamicColorScheme,
LocalBitwardenShapes provides bitwardenShapes,
LocalBitwardenTypography provides bitwardenTypography,
) {
@@ -112,6 +119,20 @@ fun BitwardenTheme(
val LocalBitwardenColorScheme: ProvidableCompositionLocal<BitwardenColorScheme> =
compositionLocalOf { lightBitwardenColorScheme }
/**
* Provides access to the Bitwarden dynamic light-mode colors throughout the app.
* This is only to be used for scenarios that require the UI to have its colors overridden.
*/
val LocalBitwardenDynamicLightColorScheme: ProvidableCompositionLocal<BitwardenColorScheme> =
compositionLocalOf { lightBitwardenColorScheme }
/**
* Provides access to the Bitwarden default dark-mode colors throughout the app.
* This is only to be used for dynamic that require the UI to have its colors overridden.
*/
val LocalBitwardenDynamicDarkColorScheme: ProvidableCompositionLocal<BitwardenColorScheme> =
compositionLocalOf { darkBitwardenColorScheme }
/**
* Provides access to the Bitwarden shapes throughout the app.
*/
@@ -210,7 +210,7 @@ val lightBitwardenColorScheme: BitwardenColorScheme = BitwardenColorScheme(
* Creates a [BitwardenColorScheme] based on dynamic Material You colors.
*/
@Suppress("LongMethod")
fun dynamicBitwardenColorScheme(
internal fun dynamicBitwardenColorScheme(
materialColorScheme: ColorScheme,
isDarkTheme: Boolean,
): BitwardenColorScheme {
@@ -310,7 +310,7 @@ fun dynamicBitwardenColorScheme(
* Derives a Material [ColorScheme] from the [BitwardenColorScheme] using the [defaultColorScheme]
* as a baseline.
*/
fun BitwardenColorScheme.toMaterialColorScheme(
internal fun BitwardenColorScheme.toMaterialColorScheme(
defaultColorScheme: ColorScheme,
): ColorScheme = defaultColorScheme.copy(
primary = this.stroke.border,