Mervin Hemaraju
Mervin Hemaraju

Reputation: 2175

Jetpack Compose: Apply color contrast dynamically based on system

With the new color contrast feature in android 15, I have now defined a new color palette for low, medium and high color contrast for both light and dark scheme. How can I apply this dynamically based on what the user has set on his phone ?

Currently, my Theme.kt is like this:

@Composable
fun MesTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> darkScheme
        else -> lightScheme
    }

    // Update the system bar colors
    rememberSystemUiController().setSystemBarsColor(
        color = colorScheme.background
    )

    MaterialTheme(
        colorScheme = colorScheme,
        typography = MesTypography,
        shapes = MesShapes,
        content = content
    )
}

It is only using the light and dark scheme. I need to be able to apply both the medium and high contrast schemes as well.

Upvotes: 0

Views: 201

Answers (1)

Edric
Edric

Reputation: 26801

As mentioned, you can use the UiModeManager#getContrast method introduced in Android 14 (Upside Down Cake) to query the contrast value. Its return value is as described:

[Returns] The color contrast, float in [-1, 1] where

  • 0 corresponds to the default contrast
  • -1 corresponds to the minimum contrast
  • 1 corresponds to the maximum contrast

Value is between -1.0f and 1.0f inclusive

An example can be found in the Reply Compose sample. Shown below are the relevant portions:

fun isContrastAvailable(): Boolean {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
}

@Composable
fun selectSchemeForContrast(isDark: Boolean): ColorScheme {
    val context = LocalContext.current
    var colorScheme = if (isDark) darkScheme else lightScheme
    val isPreview = LocalInspectionMode.current
    // TODO(b/336693596): UIModeManager is not yet supported in preview
    if (!isPreview && isContrastAvailable()) {
        val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
        val contrastLevel = uiModeManager.contrast

        colorScheme = when (contrastLevel) {
            in 0.0f..0.33f -> if (isDark)
                darkScheme else lightScheme

            in 0.34f..0.66f -> if (isDark)
                mediumContrastDarkColorScheme else mediumContrastLightColorScheme

            in 0.67f..1.0f -> if (isDark)
                highContrastDarkColorScheme else highContrastLightColorScheme

            else -> if (isDark) darkScheme else lightScheme
        }
        return colorScheme
    } else return colorScheme
}

@Composable
fun ContrastAwareReplyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = false,
    content: @Composable() () -> Unit
) {
    val replyColorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        else -> selectSchemeForContrast(darkTheme)
    }
    // ...

    MaterialTheme(
        colorScheme = replyColorScheme,
        typography = replyTypography,
        shapes = shapes,
        content = content
    )
}

Upvotes: 1

Related Questions