[Meta] Upstream Fenix's theming to Android Components
Categories
(Firefox for Android :: Design System and Theming, enhancement, P3)
Tracking
()
People
(Reporter: 007, Assigned: 007)
References
Details
(Keywords: meta, Whiteboard: [techfundamentals])
Design System Upstream to Android Components
Introduction
The theming systems on Fenix have been baking since 2021, and it'd benefit the UI in Android Components, Focus, and potentially other apps at Mozilla if we could share the color tokens, typography tokens, and top-level components we've been implementing as part of the Acorn design system.
Motivation
In a world where Composable components are reusable, themes are extensible, and colors and typography are , it becomes all the more important to broaden their accessibility through our code levels and . To that end, the FirefoxTheme wrapper and all of our components in Fenix should be migrated to Android Components. All existing Android apps under the Firefox Android team and future Mozilla Android apps would then have access to a robust theming and component system. Several goals are highlighted below, but it would be essential for the theming to be as plug-and-play as possible, similar to the MaterialTheme wrapper Google provides where apps can opt-in to as much or as little of the system when writing UI code.
Ultimately, we want to bolster our relationship with Design and make all future UI implementations at the Fenix, Focus, and AC level as seamless as possible. By having a centralized theming system, we can provide ourselves (and our community) with a one-stop-shop of the Android implementation of the Acorn design system with robust, customizable themes and components.
Goals
- The theming system is entirely self-contained in its own module.
- The theming system is extensible (i.e. the existing
FirefoxThemecan live on top of it). - The color tokens are now usable in AC and Focus.
- The color palettes are still extensible (i.e. any number of themes/color palettes could be theoretically slotted-in).
- The typography tokens are now usable in AC and Focus.
- The top-level components are usable in AC and Focus.
- The components would be refactored to have theme defaults from the Acorn system, but would be extensible.
- Any further enhancements to the design system are available to all consumers (e.g. grid system).
- Minimal refactoring within Fenix.
Implementation from a high-level
Usage
Currently we can do the following in Fenix:
FirefoxTheme {
...
color = FirefoxTheme.colors.layer1,
}
The idea would be to now be able to do:
AcornTheme {
...
color = AcornTheme.colors.layer1,
}
within AC, Fenix, and Focus, and be able to extend off of AcornTheme as needed/desired.
Extensibility
Colors
- The theme wrapper would, by default, have a light and dark palette implemented.
- Firefox would override this and pass down light, dark, and private palettes.
- Focus would provide its own custom color palette.
Typography
Similar to colors, there would be defaults provided via AcornTheme, but this could be overridden.
App-specific global singletons
- Apps can create singletons that fulfill a unique use case for that app.
- This allows the consumers of
AcornThemeto subscribe to whatever they want and add anything further they need.- Maybe Focus gets a unique shape system, or we prototype a grid-sizing system in
FirefoxThemebefore upstreaming it toAcornTheme.
- Maybe Focus gets a unique shape system, or we prototype a grid-sizing system in
In practice
Theme set-up
class AcornColors(
layer1: Color,
...
)
object AcornTheme {
val colors: AcornColors
@Composable
get() = LocalAcornColors.current
val colors: AcornTypography
@Composable
get() = LocalAcornTypography.current
}
internal val LocalColors = staticCompositionLocalOf { lightColorPalette }
internal val LocalTypography = staticCompositionLocalOf { Typography() }
val lightColorPalette = AcornColors(...)
val darkColorPalette = AcornColors(...)
@Composable
fun AcornTheme(
colors: AcornColors = AcornTheme.colors,
typography: AcornTypography = AcornTheme.typography,
content: @Composable () -> Unit,
) {
// This is taken from FirefoxTheme and MaterialTheme
val rememberedColors = remember { colors.copy() }.apply { updateColorsFrom(colors) }
CompositionLocalProvider(
LocalAcornColors provides rememberColors
LocalTypography provides typography
) {
MaterialTheme(
content = content,
)
}
}
Firefox
object FirefoxTheme {
val colors: AcornColors
@Composable
get() = AcornTheme.colors
val colors: AcornTypography
@Composable
get() = AcornTheme.Typography
}
private val privateColorPalette = darkColorPalette.copy(...)
@Composable
fun FirefoxTheme(
theme: Theme = Theme.getTheme(),
content: @Composable () -> Unit,
) {
val colors = when (theme) {
Theme.Light -> AcornTheme.lightColorPalette
Theme.Dark -> AcornTheme.darkColorPalette
Theme.Private -> FirefoxTheme.privateColorPalette
}
AcornTheme(
colors = colors,
content = content,
)
}
@Composable
fun SomeComposable() {
FirefoxTheme {
Text(
text = "Hello world",
color = FirefoxTheme.colors.textPrimary,
)
}
}
Focus
In this example, the FocusTheme singleton doesn't need to be made unless we explicitly wanted the theme calls to be FocusTheme.colors instead of AcornTheme.colors.
object FocusTheme {
val colors: AcornColors
@Composable
get() = AcornTheme.colors
val colors: AcornTypography
@Composable
get() = AcornTheme.Typography
}
private val focusColorPalette = AcornColorPalette(...) // palette from scratch
@Composable
fun FocusTheme(
content: @Composable () -> Unit,
) {
AcornTheme(
colors = focusColorPalette,
content = content,
)
}
@Composable
fun SomeComposable() {
FocusTheme {
Text(
text = "Hello world",
color = FocusTheme.colors.textPrimary,
)
}
}
``
| Assignee | ||
Updated•2 years ago
|
| Assignee | ||
Comment 1•2 years ago
|
||
Plan of attack
Module creation
Stand up modules in AC for:
- Components
- Color tokens
- Typography
- Acorn (this will be the megazord module for theming)
Token migration
Colors
- Create
AcornColorsclass - Duplicate the color tokens
- Duplicate the light and dark color palettes
Typography
- Create
AcornTypographyclass with the typography tokens - Create the default typography (make a secondary constructor with the optionals being the existing text styles)
Acorn theme setup
Theme singleton
Create the AcornTheme object with colors and typography getters (as outlined in the example above)
Theme wrapper
Create the AcornTheme composable wrapper (as outlined in the example above)
Fenix theme transition
FirefoxTheme singleton
Have the singleton serve as a wrapper layer for calling into AcornTheme (as outlined in the example above). This will make it so there is little refactoring on the Fenix side for all of the call sites of FirefoxTheme.colors and FirefoxTheme.typography.
FirefoxTheme wrapper
- Change
privateColorPaletteto copydarkColorPalettefromAcornTheme. - Make no changes to the
themeparameter. - Inside the when, plug in the dark and light color palettes from
AcornTheme. - Call
AcornThemeinstead ofMaterialTheme.
Component migration
- Move over one component at a time
- Refactor the colors of each component so they are passed-in as a function dependency.
- If one color, just simply insert the color parameter with the AcornTheme value as the default.
- If 2+ colors, create a
COMPONENTColorsdata class as the dependency.- Similar to
MessageCardColors, but just use a secondary constructor instead of a companion object, if possible.
- Similar to
- The
Buttoncomposable should be refactored to use this pattern.
- Ensure the previews are still working.
- Fix any usages.
Clean-up
- Remove
FenixTypography - Remove light and dark color palettes from
FirefoxTheme - Remove
FirefoxColors
| Assignee | ||
Comment 2•9 months ago
|
||
Marking this meta as closed. Thanks for handling this, GL!
| Assignee | ||
Updated•9 months ago
|
Description
•