# ๐ŸŽญ Theming This document provides a detailed explanation of the theming system used in our applications. It covers the theme architecture, components, customization, and usage. - **โœจ Material Design 3**: Based on Material Design 3 principles - **๐ŸŽจ Colors**: Custom color schemes with light and dark modes - **๐ŸŒ“ Dark Mode**: Full support for light and dark themes - **๐ŸŒˆ Dynamic Color**: Support for dynamic color based on system settings - **๐Ÿชœ Elevations**: Consistent elevation system for shadows - **๐Ÿ–ผ๏ธ Images**: Images and icons consistent with the theme - **๐Ÿ”ถ Shapes**: Customizable shape system for components - **๐Ÿ“ Sizes**: Standardized sizes for components - **๐Ÿ“ Spacings**: Consistent spacing system for layout - **๐Ÿ…ฐ๏ธ Typography**: Consistent typography system ## ๐Ÿ“ฑ Theme Architecture Our theme architecture is designed with several key principles in mind: 1. **Consistency**: Provide a unified look and feel across all applications while allowing for brand-specific customization 2. **Flexibility**: Support different visual identities for different applications (Thunderbird, K-9 Mail) using the same underlying system 3. **Extensibility**: Enable easy addition of new theme components or modification of existing ones 4. **Maintainability**: Centralize theme definitions to simplify updates and changes 5. **Material Design Compatibility**: Build on top of Material Design 3 while extending it with our specific needs The theming system follows a hierarchical structure: ```mermaid graph TD subgraph APP_THEMES["App-Specific Themes"] TB_THEME[ThunderbirdTheme2] K9_THEME[K9MailTheme2] end subgraph MAIN["Main Theme"] MAIN_THEME[MainTheme] THEME_CONFIG[ThemeConfig] end subgraph MATERIAL["Material Design 3"] MAT_THEME[MaterialTheme] end TB_THEME --> |uses| MAIN_THEME TB_THEME --> |defines| THEME_CONFIG K9_THEME --> |uses| MAIN_THEME K9_THEME --> |defines| THEME_CONFIG THEME_CONFIG --> |configures| MAIN_THEME MAIN_THEME --> |wraps| MAT_THEME classDef app_theme fill:#d9ffd9,stroke:#000000,color:#000000 classDef main_theme fill:#d9e9ff,stroke:#000000,color:#000000 classDef material fill:#ffe6cc,stroke:#000000,color:#000000 linkStyle default stroke:#999,stroke-width:2px class TB_THEME,K9_THEME app_theme class MAIN_THEME,THEME_CONFIG main_theme class MAT_THEME material ``` ### ๐Ÿ—๏ธ Architecture Layers The theme system consists of three main layers: 1. **App-Specific Themes Layer**: The top layer contains theme implementations for specific applications (ThunderbirdTheme2, K9MailTheme2). Each app theme: - Defines its own brand colors, logos, and other app-specific visual elements - Creates a ThemeConfig with these customizations - Uses the MainTheme as its foundation 2. **Main Theme Layer**: The middle layer provides our extended theming system: - MainTheme: A composable function that sets up the theme environment - ThemeConfig: A data class that holds all theme components - This layer extends Material Design with additional components like custom spacings, elevations, and app-specific colors 3. **Material Design Layer**: The foundation layer is Material Design 3: - Provides the base theming system (colors, typography, shapes) - Ensures compatibility with standard Material components - Our MainTheme wraps MaterialTheme and converts our theme components to Material 3 format when needed ### ๐Ÿ”„ Data Flow The theme data flows through the system as follows: 1. App-specific themes (ThunderbirdTheme2, K9MailTheme2) define their visual identity through a ThemeConfig 2. ThemeConfig is passed to MainTheme, which: - Selects the appropriate color scheme based on dark/light mode - Configures system bars (status bar, navigation bar) - Provides all theme components through CompositionLocal providers - Converts our theme components to Material 3 format and configures MaterialTheme 3. Composables access theme properties through the MainTheme object 4. Material components automatically use the Material 3 theme derived from our theme ### ๐ŸŒŸ Benefits This architecture provides several benefits: - **Separation of Concerns**: Each layer has a specific responsibility - **Code Reuse**: Common theme logic is shared between applications - **Customization**: Each application can have its own visual identity - **Consistency**: All applications share the same theming structure and components - **Extensibility**: New theme components can be added without changing the overall architecture - **Compatibility**: Works with both our custom components and standard Material components ## ๐Ÿงฉ Theme Components The theming system consists of several components that work together to provide a comprehensive and consistent visual experience across the application. Each component is responsible for a specific aspect of the UI design. ### ๐Ÿ”ง ThemeConfig The `ThemeConfig` is the central configuration class that holds all theme components. It serves as a container for all theme-related settings and is passed to the `MainTheme` composable. ```kotlin data class ThemeConfig( val colors: ThemeColorSchemeVariants, val elevations: ThemeElevations, val images: ThemeImageVariants, val shapes: ThemeShapes, val sizes: ThemeSizes, val spacings: ThemeSpacings, val typography: ThemeTypography, ) ``` The `ThemeConfig` allows for: - Centralized management of all theme components - Easy switching between light and dark themes - Simplified theme customization for different applications - Consistent theme application throughout the app ### ๐ŸŽจ ThemeColorScheme The `ThemeColorScheme` defines all colors used in the application. It extends Material Design 3's color system with additional colors specific to our applications. ```kotlin data class ThemeColorScheme( // Material 3 colors val primary: Color, val onPrimary: Color, val primaryContainer: Color, val onPrimaryContainer: Color, val secondary: Color, val onSecondary: Color, val secondaryContainer: Color, val onSecondaryContainer: Color, val tertiary: Color, val onTertiary: Color, val tertiaryContainer: Color, val onTertiaryContainer: Color, val error: Color, val onError: Color, val errorContainer: Color, val onErrorContainer: Color, val surfaceDim: Color, val surface: Color, val surfaceBright: Color, val onSurface: Color, val onSurfaceVariant: Color, val surfaceContainerLowest: Color, val surfaceContainerLow: Color, val surfaceContainer: Color, val surfaceContainerHigh: Color, val surfaceContainerHighest: Color, val inverseSurface: Color, val inverseOnSurface: Color, val inversePrimary: Color, val outline: Color, val outlineVariant: Color, val scrim: Color, // Extra colors val info: Color, val onInfo: Color, val infoContainer: Color, val onInfoContainer: Color, val success: Color, val onSuccess: Color, val successContainer: Color, val onSuccessContainer: Color, val warning: Color, val onWarning: Color, val warningContainer: Color, val onWarningContainer: Color, ) ``` The color scheme is organized into: - **Base colors**: Primary, secondary, and tertiary colors that define the app's brand identity - **Surface colors**: Colors for backgrounds, cards, and other surfaces - **Content colors**: Colors for text and icons that appear on various backgrounds (prefixed with "on") - **Container colors**: Colors for containers like buttons, chips, and other interactive elements - **Utility colors**: Colors for specific purposes like errors, outlines, and scrims Colors are provided in variants for both light and dark themes through the `ThemeColorSchemeVariants` class: ```kotlin data class ThemeColorSchemeVariants( val light: ThemeColorScheme, val dark: ThemeColorScheme, ) ``` ### ๐Ÿชœ ThemeElevations The `ThemeElevations` component defines standard elevation values used throughout the application to create a consistent sense of depth and hierarchy. ```kotlin data class ThemeElevations( val level0: Dp, val level1: Dp, val level2: Dp, val level3: Dp, val level4: Dp, val level5: Dp, ) ``` Typical usage includes: - **level0**: For elements that are flush with their background (0dp) - **level1**: For subtle elevation like dividers (1dp) - **level2**: For cards, buttons in their resting state (3dp) - **level3**: For floating action buttons, navigation drawers (6dp) - **level4**: For dialogs, bottom sheets (8dp) - **level5**: For modal surfaces that should appear prominently (12dp) ### ๐Ÿ–ผ๏ธ ThemeImages The `ThemeImages` component stores references to app-specific images like logos, icons, and illustrations. ```kotlin data class ThemeImages( val logo: Int, // Resource ID // ... other image resources ) ``` These images can have light and dark variants through the `ThemeImageVariants` class: ```kotlin data class ThemeImageVariants( val light: ThemeImages, val dark: ThemeImages, ) ``` ### ๐Ÿ”ถ ThemeShapes The `ThemeShapes` component defines the corner shapes used for UI elements throughout the application. ```kotlin data class ThemeShapes( val extraSmall: CornerBasedShape, val small: CornerBasedShape, val medium: CornerBasedShape, val large: CornerBasedShape, val extraLarge: CornerBasedShape, ) ``` These shapes are used for: - **extraSmall**: Subtle rounding for elements like text fields (4dp) - **small**: Light rounding for cards, buttons (8dp) - **medium**: Moderate rounding for floating elements (12dp) - **large**: Significant rounding for prominent elements (16dp) - **extraLarge**: Very rounded corners for special elements (28dp) Note: For no rounding (0% corner radius), use `RectangleShape`. For completely rounded corners (50% corner radius) for circular elements, use `CircleShape`. The `ThemeShapes` can be converted to Material 3 shapes using the `toMaterial3Shapes()` method for compatibility with Material components. ### ๐Ÿ“ ThemeSizes The `ThemeSizes` component defines standard size values for UI elements to ensure consistent sizing throughout the application. ```kotlin data class ThemeSizes( val smaller: Dp, val small: Dp, val medium: Dp, val large: Dp, val larger: Dp, val huge: Dp, val huger: Dp, val iconSmall: Dp, val icon: Dp, val iconLarge: Dp, val iconAvatar: Dp, val topBarHeight: Dp, val bottomBarHeight: Dp, val bottomBarHeightWithFab: Dp, ) ``` These sizes are used for: - **General sizes**: `smaller`, `small`, `medium`, `large`, `larger`, `huge`, `huger` for component dimensions (width, height), button heights, and other UI element dimensions that need standardization - **Icon sizes**: `iconSmall`, `icon`, `iconLarge` for different icon sizes throughout the app - **Avatar size**: `iconAvatar` for user avatars and profile pictures - **Layout sizes**: `topBarHeight`, `bottomBarHeight`, `bottomBarHeightWithFab` for consistent app bar and navigation bar heights ### ๐Ÿ“ ThemeSpacings The `ThemeSpacings` component defines standard spacing values used for margins, padding, and gaps between elements. ```kotlin data class ThemeSpacings( val zero: Dp, val quarter: Dp, val half: Dp, val default: Dp, val oneHalf: Dp, val double: Dp, val triple: Dp, val quadruple: Dp, ) ``` Consistent spacing helps create a rhythmic and harmonious layout: - **zero**: No spacing (0dp) - **quarter**: Quarter of the default spacing, for very tight layouts (4dp) - **half**: Half of the default spacing, for tight layouts (8dp) - **default**: The standard spacing unit for general use (16dp) - **oneHalf**: One and a half times the default spacing (24dp) - **double**: Twice the default spacing, for separating sections (32dp) - **triple**: Three times the default spacing, for major layout divisions (48dp) - **quadruple**: Four times the default spacing, for maximum separation (64dp) ### ๐Ÿ…ฐ๏ธ ThemeTypography The `ThemeTypography` component defines text styles for different types of content throughout the application. ```kotlin data class ThemeTypography( // Display styles for large headlines val displayLarge: TextStyle, val displayMedium: TextStyle, val displaySmall: TextStyle, // Headline styles for section headers val headlineLarge: TextStyle, val headlineMedium: TextStyle, val headlineSmall: TextStyle, // Title styles for content titles val titleLarge: TextStyle, val titleMedium: TextStyle, val titleSmall: TextStyle, // Body styles for main content val bodyLarge: TextStyle, val bodyMedium: TextStyle, val bodySmall: TextStyle, // Label styles for buttons and small text val labelLarge: TextStyle, val labelMedium: TextStyle, val labelSmall: TextStyle, ) ``` Each `TextStyle` includes: - Font family - Font weight - Font size - Line height - Letter spacing - Other typographic attributes The `ThemeTypography` can be converted to Material 3 typography using the `toMaterial3Typography()` method for compatibility with Material components. ### โ†”๏ธ Component Interaction These theme components work together to create a cohesive design system: 1. **ThemeConfig** aggregates all components and provides them to the `MainTheme` 2. **MainTheme** makes components available through `CompositionLocal` providers 3. Composables access theme components through the `MainTheme` object 4. Components like `ThemeColorScheme` and `ThemeShapes` are converted to Material 3 equivalents for use with Material components This structured approach ensures consistent design application throughout the app while providing flexibility for customization. ## ๐ŸŒŸ MainTheme The `MainTheme` is the foundation of our theming system: - Acts as a wrapper around Material Design 3's `MaterialTheme` - Provides additional theme components beyond what Material Design offers - Configurable through a `ThemeConfig` parameter - Supports dark mode and dynamic color - Exposes theme components through the `MainTheme` object ### ๐Ÿ”Œ Theme Provider Implementation and Usage #### ๐Ÿ› ๏ธ How the Theme Provider Works The `MainTheme` function uses Jetpack Compose's `CompositionLocalProvider` to make theme components available throughout the composition tree: ```kotlin @Composable fun MainTheme( themeConfig: ThemeConfig, darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = true, content: @Composable () -> Unit, ) { val themeColorScheme = selectThemeColorScheme( themeConfig = themeConfig, darkTheme = darkTheme, dynamicColor = dynamicColor, ) val themeImages = selectThemeImages( themeConfig = themeConfig, darkTheme = darkTheme, ) SystemBar( darkTheme = darkTheme, colorScheme = themeColorScheme, ) CompositionLocalProvider( LocalThemeColorScheme provides themeColorScheme, LocalThemeElevations provides themeConfig.elevations, LocalThemeImages provides themeImages, LocalThemeShapes provides themeConfig.shapes, LocalThemeSizes provides themeConfig.sizes, LocalThemeSpacings provides themeConfig.spacings, LocalThemeTypography provides themeConfig.typography, ) { MaterialTheme( colorScheme = themeColorScheme.toMaterial3ColorScheme(), shapes = themeConfig.shapes.toMaterial3Shapes(), typography = themeConfig.typography.toMaterial3Typography(), content = content, ) } } ``` Each theme component is provided through a `CompositionLocal` that makes it available to all composables in the composition tree. These `CompositionLocal` values are defined using `staticCompositionLocalOf` in their respective files: ```kotlin internal val LocalThemeColorScheme = staticCompositionLocalOf { error("No ThemeColorScheme provided") } internal val LocalThemeElevations = staticCompositionLocalOf { error("No ThemeElevations provided") } // ... other LocalTheme* definitions ``` The `MainTheme` object provides properties to access these values from anywhere in the composition tree: ```kotlin object MainTheme { val colors: ThemeColorScheme @Composable @ReadOnlyComposable get() = LocalThemeColorScheme.current val elevations: ThemeElevations @Composable @ReadOnlyComposable get() = LocalThemeElevations.current // ... other properties } ``` This theme provider mechanism ensures that theme components are available throughout the app without having to pass them as parameters to every composable. ## ๐ŸŽญ App-Specific Themes The app-specific themes (`ThunderbirdTheme2` and `K9MailTheme2`) customize the `MainTheme` for each application: - Provide app-specific color schemes - Include app-specific assets (like logos) - Configure theme components through `ThemeConfig` - Use default values for common components (elevations, sizes, spacings, shapes, typography) ### ThunderbirdTheme2 ```kotlin @Composable fun ThunderbirdTheme2( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = false, content: @Composable () -> Unit, ) { val images = ThemeImages( logo = R.drawable.core_ui_theme2_thunderbird_logo, ) val themeConfig = ThemeConfig( colors = ThemeColorSchemeVariants( dark = darkThemeColorScheme, light = lightThemeColorScheme, ), elevations = defaultThemeElevations, images = ThemeImageVariants( light = images, dark = images, ), sizes = defaultThemeSizes, spacings = defaultThemeSpacings, shapes = defaultThemeShapes, typography = defaultTypography, ) MainTheme( themeConfig = themeConfig, darkTheme = darkTheme, dynamicColor = dynamicColor, content = content, ) } ``` ### K9MailTheme2 ```kotlin @Composable fun K9MailTheme2( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = false, content: @Composable () -> Unit, ) { val images = ThemeImages( logo = R.drawable.core_ui_theme2_k9mail_logo, ) val themeConfig = ThemeConfig( colors = ThemeColorSchemeVariants( dark = darkThemeColorScheme, light = lightThemeColorScheme, ), elevations = defaultThemeElevations, images = ThemeImageVariants( light = images, dark = images, ), sizes = defaultThemeSizes, spacings = defaultThemeSpacings, shapes = defaultThemeShapes, typography = defaultTypography, ) MainTheme( themeConfig = themeConfig, darkTheme = darkTheme, dynamicColor = dynamicColor, content = content, ) } ``` ## ๐ŸŽจ Using Themes in the App ### ๐Ÿงฉ Applying a Theme To apply a theme to your UI, wrap your composables with the appropriate theme composable: ```kotlin // For Thunderbird app @Composable fun ThunderbirdApp() { ThunderbirdTheme2 { // App content } } // For K9Mail app @Composable fun K9MailApp() { K9MailTheme2 { // App content } } ``` ### ๐Ÿ”‘ Accessing Theme Components Inside themed content, you can access theme properties through the `MainTheme` object: ```kotlin @Composable fun ThemedButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( onClick = onClick, modifier = modifier, colors = ButtonDefaults.buttonColors( containerColor = MainTheme.colors.primary, contentColor = MainTheme.colors.onPrimary, ), shape = MainTheme.shapes.medium, ) { Text( text = text, style = MainTheme.typography.labelLarge, ) } } ``` ## ๐ŸŒ“ Dark Mode and Dynamic Color The theming system supports both dark mode and dynamic color: - **Dark Mode**: Automatically applies the appropriate color scheme based on the system's dark mode setting - **Dynamic Color**: Optionally uses the device's wallpaper colors for the theme (Android 12+) ```kotlin @Composable fun ThunderbirdTheme2( darkTheme: Boolean = isSystemInDarkTheme(), // Default to system setting dynamicColor: Boolean = false, // Disabled by default content: @Composable () -> Unit, ) { // ... } ``` ## ๐Ÿ”ง Customizing Themes To customize a theme, you can create a new theme composable that wraps `MainTheme` with your custom `ThemeConfig`: ```kotlin @Composable fun CustomTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = false, content: @Composable () -> Unit, ) { val images = ThemeImages( logo = R.drawable.custom_logo, ) val themeConfig = ThemeConfig( colors = ThemeColorSchemeVariants( dark = customDarkThemeColorScheme, light = customLightThemeColorScheme, ), elevations = customThemeElevations, images = ThemeImageVariants( light = images, dark = images, ), sizes = customThemeSizes, spacings = customThemeSpacings, shapes = customThemeShapes, typography = customTypography, ) MainTheme( themeConfig = themeConfig, darkTheme = darkTheme, dynamicColor = dynamicColor, content = content, ) } ``` ## ๐Ÿงช Testing with Themes When writing tests for composables that use theme components, you need to wrap them in a theme: ```kotlin @Test fun testThemedButton() { composeTestRule.setContent { ThunderbirdTheme2 { ThemedButton( text = "Click Me", onClick = {}, ) } } composeTestRule.onNodeWithText("Click Me").assertExists() } ```