diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/cardscanner/CardScanScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/cardscanner/CardScanScreen.kt index 63cc27706c..b48b670c3c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/cardscanner/CardScanScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/cardscanner/CardScanScreen.kt @@ -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(), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt index e6a152d49b..2ee5c49eaa 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt @@ -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(), diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt index 32b69e3c5f..eba689d530 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt @@ -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, diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ColorSchemeOverride.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ColorSchemeOverride.kt new file mode 100644 index 0000000000..98ea4d4c14 --- /dev/null +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ColorSchemeOverride.kt @@ -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, + ) +} diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/BitwardenTheme.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/BitwardenTheme.kt index c296003794..070ca7f35c 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/BitwardenTheme.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/BitwardenTheme.kt @@ -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 = 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 = + 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 = + compositionLocalOf { darkBitwardenColorScheme } + /** * Provides access to the Bitwarden shapes throughout the app. */ diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/color/ColorScheme.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/color/ColorScheme.kt index c6782939c4..ba60ecd0f7 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/color/ColorScheme.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/color/ColorScheme.kt @@ -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,