Introduce LocalAndroidColorScheme to expose Android colors

This CL introduces the AndroidColorScheme and its CompositionLocal to
access any Android color from a Composable. The list of colors was taken
from [1].

Ideally we would only use M3 colors but given that M3 and Android
colors have not been aligned yet, our current UI specs are still using
the Android colors.

In the long term, this class will be deleted or slimmed down to contain
only the few colors missing from M3.

[1] http://cs/android-internal/frameworks/base/core/res/res/values/themes_device_defaults.xml;l=214;rcl=fa86dc4ca511d27ae2d32749d180466404c18218

Bug: 230605885
Test: atest SystemUIThemeTest
Change-Id: I45927f23eb8256987b4e5c6ae48e33ce581aabb3
This commit is contained in:
Jordan Demeulenaere
2022-06-13 12:09:47 +02:00
parent a3b7bb18b7
commit 78b3e889ff
5 changed files with 185 additions and 31 deletions

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.compose.theme
import android.annotation.ColorInt
import android.content.Context
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import com.android.internal.R
/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
val LocalAndroidColorScheme =
staticCompositionLocalOf<AndroidColorScheme> {
throw IllegalStateException(
"No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
"Composable surrounded by a SystemUITheme {}."
)
}
/**
* The Android color scheme.
*
* Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
* most of the colors in this class will be removed in favor of their M3 counterpart.
*/
class AndroidColorScheme internal constructor(private val context: Context) {
val colorPrimary = getColor(context, R.attr.colorPrimary)
val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
val colorAccent = getColor(context, R.attr.colorAccent)
val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
val colorSurface = getColor(context, R.attr.colorSurface)
val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
val colorError = getColor(context, R.attr.colorError)
val colorBackground = getColor(context, R.attr.colorBackground)
val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
val panelColorBackground = getColor(context, R.attr.panelColorBackground)
val textColorPrimary = getColor(context, R.attr.textColorPrimary)
val textColorSecondary = getColor(context, R.attr.textColorSecondary)
val textColorTertiary = getColor(context, R.attr.textColorTertiary)
val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
val colorForeground = getColor(context, R.attr.colorForeground)
val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
private fun getColor(context: Context, attr: Int): Color {
val ta = context.obtainStyledAttributes(intArrayOf(attr))
@ColorInt val color = ta.getColor(0, 0)
ta.recycle()
return Color(color)
}
}

View File

@@ -22,6 +22,7 @@ import androidx.compose.material3.Typography
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
/** The Material 3 theme that should wrap all SystemUI Composables. */
@@ -39,7 +40,14 @@ fun SystemUITheme(
} else {
dynamicLightColorScheme(context)
}
val androidColorScheme = AndroidColorScheme(context)
val typography = Typography()
MaterialTheme(colorScheme, typography = typography) { content() }
MaterialTheme(colorScheme, typography = typography) {
CompositionLocalProvider(
LocalAndroidColorScheme provides androidColorScheme,
) {
content()
}
}
}

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,4 +36,22 @@ class SystemUIThemeTest {
composeRule.onNodeWithText("foo").assertIsDisplayed()
}
@Test
fun testAndroidColorsAreAvailableInsideTheme() {
composeRule.setContent {
SystemUITheme { Text("foo", color = LocalAndroidColorScheme.current.colorAccent) }
}
composeRule.onNodeWithText("foo").assertIsDisplayed()
}
@Test
fun testAccessingAndroidColorsWithoutThemeThrows() {
assertThrows(IllegalStateException::class.java) {
composeRule.setContent {
Text("foo", color = LocalAndroidColorScheme.current.colorAccent)
}
}
}
}

View File

@@ -20,6 +20,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -32,38 +33,88 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.android.systemui.compose.theme.LocalAndroidColorScheme
/** The screen that shows all the Material 3 colors. */
@Composable
fun ColorsScreen() {
fun MaterialColorsScreen() {
val colors = MaterialTheme.colorScheme
LazyColumn {
item { ColorTile(colors.primary, "primary") }
item { ColorTile(colors.onPrimary, "onPrimary") }
item { ColorTile(colors.primaryContainer, "primaryContainer") }
item { ColorTile(colors.onPrimaryContainer, "onPrimaryContainer") }
item { ColorTile(colors.inversePrimary, "inversePrimary") }
item { ColorTile(colors.secondary, "secondary") }
item { ColorTile(colors.onSecondary, "onSecondary") }
item { ColorTile(colors.secondaryContainer, "secondaryContainer") }
item { ColorTile(colors.onSecondaryContainer, "onSecondaryContainer") }
item { ColorTile(colors.tertiary, "tertiary") }
item { ColorTile(colors.onTertiary, "onTertiary") }
item { ColorTile(colors.tertiaryContainer, "tertiaryContainer") }
item { ColorTile(colors.onTertiaryContainer, "onTertiaryContainer") }
item { ColorTile(colors.background, "background") }
item { ColorTile(colors.onBackground, "onBackground") }
item { ColorTile(colors.surface, "surface") }
item { ColorTile(colors.onSurface, "onSurface") }
item { ColorTile(colors.surfaceVariant, "surfaceVariant") }
item { ColorTile(colors.onSurfaceVariant, "onSurfaceVariant") }
item { ColorTile(colors.inverseSurface, "inverseSurface") }
item { ColorTile(colors.inverseOnSurface, "inverseOnSurface") }
item { ColorTile(colors.error, "error") }
item { ColorTile(colors.onError, "onError") }
item { ColorTile(colors.errorContainer, "errorContainer") }
item { ColorTile(colors.onErrorContainer, "onErrorContainer") }
item { ColorTile(colors.outline, "outline") }
ColorsScreen(
listOf(
"primary" to colors.primary,
"onPrimary" to colors.onPrimary,
"primaryContainer" to colors.primaryContainer,
"onPrimaryContainer" to colors.onPrimaryContainer,
"inversePrimary" to colors.inversePrimary,
"secondary" to colors.secondary,
"onSecondary" to colors.onSecondary,
"secondaryContainer" to colors.secondaryContainer,
"onSecondaryContainer" to colors.onSecondaryContainer,
"tertiary" to colors.tertiary,
"onTertiary" to colors.onTertiary,
"tertiaryContainer" to colors.tertiaryContainer,
"onTertiaryContainer" to colors.onTertiaryContainer,
"background" to colors.background,
"onBackground" to colors.onBackground,
"surface" to colors.surface,
"onSurface" to colors.onSurface,
"surfaceVariant" to colors.surfaceVariant,
"onSurfaceVariant" to colors.onSurfaceVariant,
"inverseSurface" to colors.inverseSurface,
"inverseOnSurface" to colors.inverseOnSurface,
"error" to colors.error,
"onError" to colors.onError,
"errorContainer" to colors.errorContainer,
"onErrorContainer" to colors.onErrorContainer,
"outline" to colors.outline,
)
)
}
/** The screen that shows all the Android colors. */
@Composable
fun AndroidColorsScreen() {
val colors = LocalAndroidColorScheme.current
ColorsScreen(
listOf(
"colorPrimary" to colors.colorPrimary,
"colorPrimaryDark" to colors.colorPrimaryDark,
"colorAccent" to colors.colorAccent,
"colorAccentPrimary" to colors.colorAccentPrimary,
"colorAccentSecondary" to colors.colorAccentSecondary,
"colorAccentTertiary" to colors.colorAccentTertiary,
"colorAccentPrimaryVariant" to colors.colorAccentPrimaryVariant,
"colorAccentSecondaryVariant" to colors.colorAccentSecondaryVariant,
"colorAccentTertiaryVariant" to colors.colorAccentTertiaryVariant,
"colorSurface" to colors.colorSurface,
"colorSurfaceHighlight" to colors.colorSurfaceHighlight,
"colorSurfaceVariant" to colors.colorSurfaceVariant,
"colorSurfaceHeader" to colors.colorSurfaceHeader,
"colorError" to colors.colorError,
"colorBackground" to colors.colorBackground,
"colorBackgroundFloating" to colors.colorBackgroundFloating,
"panelColorBackground" to colors.panelColorBackground,
"textColorPrimary" to colors.textColorPrimary,
"textColorSecondary" to colors.textColorSecondary,
"textColorTertiary" to colors.textColorTertiary,
"textColorPrimaryInverse" to colors.textColorPrimaryInverse,
"textColorSecondaryInverse" to colors.textColorSecondaryInverse,
"textColorTertiaryInverse" to colors.textColorTertiaryInverse,
"textColorOnAccent" to colors.textColorOnAccent,
"colorForeground" to colors.colorForeground,
"colorForegroundInverse" to colors.colorForegroundInverse,
)
)
}
@Composable
private fun ColorsScreen(
colors: List<Pair<String, Color>>,
) {
LazyColumn(
Modifier.fillMaxWidth(),
) {
colors.forEach { (name, color) -> item { ColorTile(color, name) } }
}
}

View File

@@ -30,7 +30,8 @@ import com.android.systemui.compose.theme.SystemUITheme
enum class Screen {
Home,
Typography,
Colors,
MaterialColors,
AndroidColors,
ExampleFeature,
}
@@ -49,7 +50,8 @@ private fun MainContent() {
)
}
composable(Screen.Typography.name) { TypographyScreen() }
composable(Screen.Colors.name) { ColorsScreen() }
composable(Screen.MaterialColors.name) { MaterialColorsScreen() }
composable(Screen.AndroidColors.name) { AndroidColorsScreen() }
composable(Screen.ExampleFeature.name) { ExampleFeatureScreen() }
}
}