https://kotlinlang.org logo
#compose
Title
# compose
s

Stephen Prochnow

01/19/2022, 3:26 PM
I think there is an issue with the calculation of the LocalContentAlpha in themes, the LocalContentAlpha is determined before the LocalContentColor is set. This results in wrong contrast values and nested themes use the LocalContentColor from the outer themes. More details in the thread.
c

Colton Idle

01/19/2022, 3:33 PM
@Stephen Prochnow Could you edit your question to put more of it in the thread? See: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1616265877303000
👍 1
s

Stephen Prochnow

01/19/2022, 3:36 PM
Given a theme which for example inverts the color usage and uses White as the onSurface color:
Copy code
private val LightColorPalette = lightColors(
    surface = Color.Red,
    onSurface = Color.White,
)

@Composable
fun CustomTheme(
    content: @Composable () -> Unit
) {

  MaterialTheme(
      colors = LightColorPalette,
      typography = typography,
      shapes = shapes,
      content = content
  )
}
When a MaterialTheme is initialised the CompositionLocalProvider calls
LocalContentAlpha provides ContentAlpha.high
, this determines based on the current contentColor the required alpha value to guarantee sufficient contrast:
Copy code
private fun contentAlpha(
        /*@FloatRange(from = 0.0, to = 1.0)*/
        highContrastAlpha: Float,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        lowContrastAlpha: Float
    ): Float {
        val contentColor = LocalContentColor.current
        val lightTheme = MaterialTheme.colors.isLight
        return if (lightTheme) {
            if (contentColor.luminance() > 0.5) highContrastAlpha else lowContrastAlpha
        } else {
            if (contentColor.luminance() < 0.5) highContrastAlpha else lowContrastAlpha
        }
    }
But at this point the LocalContentColor is not set and returns Black as a default, which then results always in a lowContrastAlpha. This has two consequences: 1. If nothing sets the LocalContentAlpha again after the LocalContentColor was set to the specific colors defined in the theme the alpha value would be wrong and would not provide enough contrast 2. When themes are nested, the inner theme takes the contentColor from the outer theme to determine its initial LocalContentAlpha Can somebody confirm this behaviour or do I miss something in the usage of themes?
🙏 1
c

Chris Sinco [G]

01/19/2022, 4:44 PM
I believe this is how it works, though I’m not sure if the use case of setting light on dark colors was considered in a light theme. @Louis Pullen-Freilich [G] can confirm
1. When themes are nested, the inner theme takes the contentColor from the outer theme to determine its initial LocalContentAlpha
This is true, though contentColor can be changed by providing a custom LocalContentColor through CompositionLocals
s

Stephen Prochnow

01/19/2022, 4:54 PM
My example is probably a bit on the extreme side but in theory this could also happen with a light background and a custom onColor for which the luminance is below 0.5. Wrapping the themes with a CompositionLocal is currently my workaround for both cases.
l

Louis Pullen-Freilich [G]

01/19/2022, 5:57 PM
I believe this is how it works, though I’m not sure if the use case of setting light on dark colors was considered in a light theme
Most of this is just a best effort heuristic to try and achieve better contrast ratios by default - if you are using an ‘inverse’ theme or similar then it’s recommended that you explicitly set these values in any case - the default is just there as a reasonable default for most cases
👍 1