From 4055c53362ec3ee92a95e32e20d26cae54b69991 Mon Sep 17 00:00:00 2001 From: KremeCN <3265494849@qq.com> Date: Wed, 26 Aug 2026 09:37:10 +0800 Subject: [PATCH] feat: add dual-renderer design tokens --- .../abox/manager/ui/AppScreenshotSmokeTest.kt | 13 +- android/core/designsystem/build.gradle.kts | 3 + .../abox/manager/designsystem/Shape.kt | 11 +- .../abox/manager/designsystem/Theme.kt | 169 +++++++++++++++--- .../abox/manager/designsystem/Type.kt | 1 + .../renderer/ABoxRendererAdapter.kt | 20 +++ .../renderer/Material3RendererAdapter.kt | 23 +++ .../renderer/MiuixRendererAdapter.kt | 25 +++ .../manager/designsystem/tokens/ABoxTokens.kt | 139 ++++++++++++++ .../designsystem/ScreenshotSmokeTest.kt | 13 +- .../renderer/ABoxRendererAdapterTest.kt | 19 ++ .../tokens/ABoxThemeResolverTest.kt | 116 ++++++++++++ gradle/libs.versions.toml | 2 + 13 files changed, 521 insertions(+), 33 deletions(-) create mode 100644 android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapter.kt create mode 100644 android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/Material3RendererAdapter.kt create mode 100644 android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/MiuixRendererAdapter.kt create mode 100644 android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxTokens.kt create mode 100644 android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapterTest.kt create mode 100644 android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxThemeResolverTest.kt diff --git a/android/app/src/screenshotTest/kotlin/com/flagship/abox/manager/ui/AppScreenshotSmokeTest.kt b/android/app/src/screenshotTest/kotlin/com/flagship/abox/manager/ui/AppScreenshotSmokeTest.kt index e5ab6c5..bd5b8aa 100644 --- a/android/app/src/screenshotTest/kotlin/com/flagship/abox/manager/ui/AppScreenshotSmokeTest.kt +++ b/android/app/src/screenshotTest/kotlin/com/flagship/abox/manager/ui/AppScreenshotSmokeTest.kt @@ -5,10 +5,21 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import com.android.tools.screenshot.PreviewTest import com.flagship.abox.manager.designsystem.ABoxTheme +import com.flagship.abox.manager.preferences.AppThemeMode +import com.flagship.abox.manager.preferences.DesignRenderer +import com.flagship.abox.manager.preferences.UiPreferences @PreviewTest @Preview @Composable fun AppSmokePreview() { - ABoxTheme(darkTheme = false) { Text("ABox Manager") } + ABoxTheme( + preferences = UiPreferences( + renderer = DesignRenderer.MATERIAL3, + themeMode = AppThemeMode.LIGHT, + dynamicColorEnabled = false, + ), + ) { + Text("ABox Manager") + } } diff --git a/android/core/designsystem/build.gradle.kts b/android/core/designsystem/build.gradle.kts index aa2fb22..d28fb6b 100644 --- a/android/core/designsystem/build.gradle.kts +++ b/android/core/designsystem/build.gradle.kts @@ -32,11 +32,14 @@ kotlin { } dependencies { + implementation(project(":android:core:preferences")) implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) implementation(libs.compose.ui.tooling.preview) implementation(libs.compose.material3) implementation(libs.compose.material.icons.extended) + implementation(libs.miuix.ui.android) debugImplementation(libs.compose.ui.tooling) screenshotTestImplementation(libs.compose.screenshot.test) + testImplementation(libs.junit) } diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Shape.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Shape.kt index 5bc6176..f19373c 100644 --- a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Shape.kt +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Shape.kt @@ -2,10 +2,11 @@ package com.flagship.abox.manager.designsystem import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Shapes -import androidx.compose.ui.unit.dp +import com.flagship.abox.manager.designsystem.tokens.ABoxShapeTokens +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeDefaults -val ABoxShapes = Shapes( - small = RoundedCornerShape(8.dp), - medium = RoundedCornerShape(16.dp), - large = RoundedCornerShape(24.dp), +fun ABoxShapes(tokens: ABoxShapeTokens = ABoxThemeDefaults.material3Shapes) = Shapes( + small = RoundedCornerShape(tokens.smallCornerRadius), + medium = RoundedCornerShape(tokens.mediumCornerRadius), + large = RoundedCornerShape(tokens.largeCornerRadius), ) diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Theme.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Theme.kt index 7d83a34..4f68164 100644 --- a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Theme.kt +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Theme.kt @@ -1,41 +1,158 @@ package com.flagship.abox.manager.designsystem -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme +import android.os.Build +import androidx.compose.material3.ColorScheme import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import com.flagship.abox.manager.designsystem.renderer.ABoxRendererAdapters +import com.flagship.abox.manager.preferences.DesignRenderer +import com.flagship.abox.manager.designsystem.tokens.ABoxColorTokens +import com.flagship.abox.manager.designsystem.tokens.ABoxSpacing +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeDefaults +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeResolution +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeResolver +import com.flagship.abox.manager.preferences.AppThemeMode +import com.flagship.abox.manager.preferences.UiPreferences -private val LightColorScheme = lightColorScheme( - primary = Color(0xFF006780), - onPrimary = Color.White, - primaryContainer = Color(0xFFB8EAFF), - onPrimaryContainer = Color(0xFF001F29), - secondary = Color(0xFF4D616A), - surface = Color(0xFFF8FAFC), - background = Color(0xFFF8FAFC), -) - -private val DarkColorScheme = darkColorScheme( - primary = Color(0xFF5DD5F8), - onPrimary = Color(0xFF003544), - primaryContainer = Color(0xFF004D61), - onPrimaryContainer = Color(0xFFB8EAFF), - secondary = Color(0xFFB4CBD4), - surface = Color(0xFF101416), - background = Color(0xFF101416), -) +val LocalABoxColors = staticCompositionLocalOf { ABoxThemeDefaults.lightColors } +val LocalABoxSpacing = staticCompositionLocalOf { ABoxSpacing() } +val LocalABoxTypography = staticCompositionLocalOf { ABoxTypography } +val LocalABoxShapes = staticCompositionLocalOf { ABoxThemeDefaults.material3Shapes } +val LocalABoxRenderer = staticCompositionLocalOf { DesignRenderer.MATERIAL3 } +val LocalABoxReducedMotion = staticCompositionLocalOf { false } @Composable fun ABoxTheme( - darkTheme: Boolean = isSystemInDarkTheme(), + preferences: UiPreferences, content: @Composable () -> Unit, ) { - MaterialTheme( - colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, - typography = ABoxTypography, - shapes = ABoxShapes, + val systemInDarkTheme = androidx.compose.foundation.isSystemInDarkTheme() + val isDarkTheme = when (preferences.themeMode) { + AppThemeMode.SYSTEM -> systemInDarkTheme + AppThemeMode.LIGHT -> false + AppThemeMode.DARK -> true + } + val context = LocalContext.current + val dynamicTokens = if (preferences.dynamicColorEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val dynamicColorScheme = if (isDarkTheme) { + dynamicDarkColorScheme(context) + } else { + dynamicLightColorScheme(context) + } + dynamicColorScheme.toABoxColorTokens() + } else { + null + } + val resolution = ABoxThemeResolver.resolve( + preferences = preferences, + systemInDarkTheme = systemInDarkTheme, + sdkInt = Build.VERSION.SDK_INT, + dynamicColorTokens = dynamicTokens, + ) + val renderer = ABoxRendererAdapters.forRenderer(resolution.renderer) + + CompositionLocalProvider( + LocalABoxColors provides resolution.colorTokens, + LocalABoxSpacing provides ABoxSpacing(), + LocalABoxTypography provides ABoxTypography, + LocalABoxShapes provides resolution.shapeTokens, + LocalABoxRenderer provides resolution.renderer, + LocalABoxReducedMotion provides false, + ) { + renderer.Render( + resolution = resolution, + content = content, + ) + } +} + +@Composable +fun ABoxTheme( + darkTheme: Boolean = androidx.compose.foundation.isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + ABoxTheme( + preferences = UiPreferences( + themeMode = if (darkTheme) AppThemeMode.DARK else AppThemeMode.LIGHT, + dynamicColorEnabled = false, + ), content = content, ) } + +internal fun materialColorScheme(resolution: ABoxThemeResolution): ColorScheme { + val colors = resolution.colorTokens + val onPrimary = if (resolution.isDarkTheme) Color(0xFF003544) else Color.White + val onSurface = if (resolution.isDarkTheme) Color(0xFFE0E3E5) else Color(0xFF191C1E) + val onSurfaceVariant = if (resolution.isDarkTheme) Color(0xFFC0C8CC) else Color(0xFF40484C) + val primaryContainer = colors.primary.copy(alpha = if (resolution.isDarkTheme) 0.35f else 0.20f) + val secondaryContainer = colors.surfaceContainer + + return if (resolution.isDarkTheme) { + darkColorScheme( + primary = colors.primary, + onPrimary = onPrimary, + primaryContainer = primaryContainer, + onPrimaryContainer = colors.primary, + secondary = colors.pending, + onSecondary = Color(0xFF3F2E00), + secondaryContainer = secondaryContainer, + onSecondaryContainer = onSurface, + tertiary = colors.success, + onTertiary = Color(0xFF00391E), + surface = colors.surface, + onSurface = onSurface, + surfaceVariant = colors.surfaceContainer, + onSurfaceVariant = onSurfaceVariant, + background = colors.surface, + onBackground = onSurface, + outline = colors.outline, + error = colors.error, + onError = Color(0xFF690005), + ) + } else { + lightColorScheme( + primary = colors.primary, + onPrimary = onPrimary, + primaryContainer = primaryContainer, + onPrimaryContainer = Color(0xFF001F29), + secondary = colors.pending, + onSecondary = Color.White, + secondaryContainer = secondaryContainer, + onSecondaryContainer = onSurface, + tertiary = colors.success, + onTertiary = Color.White, + surface = colors.surface, + onSurface = onSurface, + surfaceVariant = colors.surfaceContainer, + onSurfaceVariant = onSurfaceVariant, + background = colors.surface, + onBackground = onSurface, + outline = colors.outline, + error = colors.error, + onError = Color.White, + ) + } +} + +private fun ColorScheme.toABoxColorTokens() = ABoxColorTokens( + primary = primary, + surface = surface, + surfaceContainer = surfaceContainer, + outline = outline, + success = tertiary, + warning = secondary, + error = error, + offline = outline, + pending = secondary, + deviceSocket = primary, + deviceSensor = tertiary, + deviceInfrared = secondary, +) diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Type.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Type.kt index 0048478..1f4b05a 100644 --- a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Type.kt +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/Type.kt @@ -2,4 +2,5 @@ package com.flagship.abox.manager.designsystem import androidx.compose.material3.Typography +/** Default typography supplied through [LocalABoxTypography]. */ val ABoxTypography = Typography() diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapter.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapter.kt new file mode 100644 index 0000000..e129305 --- /dev/null +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapter.kt @@ -0,0 +1,20 @@ +package com.flagship.abox.manager.designsystem.renderer + +import androidx.compose.runtime.Composable +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeResolution +import com.flagship.abox.manager.preferences.DesignRenderer + +internal interface ABoxRendererAdapter { + @Composable + fun Render( + resolution: ABoxThemeResolution, + content: @Composable () -> Unit, + ) +} + +internal object ABoxRendererAdapters { + fun forRenderer(renderer: DesignRenderer): ABoxRendererAdapter = when (renderer) { + DesignRenderer.MATERIAL3 -> Material3RendererAdapter + DesignRenderer.MIUIX -> MiuixRendererAdapter + } +} diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/Material3RendererAdapter.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/Material3RendererAdapter.kt new file mode 100644 index 0000000..9a7a4a7 --- /dev/null +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/Material3RendererAdapter.kt @@ -0,0 +1,23 @@ +package com.flagship.abox.manager.designsystem.renderer + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import com.flagship.abox.manager.designsystem.ABoxShapes +import com.flagship.abox.manager.designsystem.ABoxTypography +import com.flagship.abox.manager.designsystem.materialColorScheme +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeResolution + +object Material3RendererAdapter : ABoxRendererAdapter { + @Composable + override fun Render( + resolution: ABoxThemeResolution, + content: @Composable () -> Unit, + ) { + MaterialTheme( + colorScheme = materialColorScheme(resolution), + typography = ABoxTypography, + shapes = ABoxShapes(resolution.shapeTokens), + content = content, + ) + } +} diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/MiuixRendererAdapter.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/MiuixRendererAdapter.kt new file mode 100644 index 0000000..394a3dd --- /dev/null +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/renderer/MiuixRendererAdapter.kt @@ -0,0 +1,25 @@ +package com.flagship.abox.manager.designsystem.renderer + +import androidx.compose.runtime.Composable +import com.flagship.abox.manager.designsystem.tokens.ABoxThemeResolution +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.theme.darkColorScheme +import top.yukonga.miuix.kmp.theme.lightColorScheme + +internal object MiuixRendererAdapter : ABoxRendererAdapter { + @Composable + override fun Render( + resolution: ABoxThemeResolution, + content: @Composable () -> Unit, + ) { + val colors = if (resolution.isDarkTheme) darkColorScheme() else lightColorScheme() + MiuixTheme( + colors = colors, + ) { + Material3RendererAdapter.Render( + resolution = resolution, + content = content, + ) + } + } +} diff --git a/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxTokens.kt b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxTokens.kt new file mode 100644 index 0000000..004b947 --- /dev/null +++ b/android/core/designsystem/src/main/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxTokens.kt @@ -0,0 +1,139 @@ +package com.flagship.abox.manager.designsystem.tokens + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.flagship.abox.manager.preferences.AppThemeMode +import com.flagship.abox.manager.preferences.DesignRenderer +import com.flagship.abox.manager.preferences.UiPreferences + +@Immutable +data class ABoxColorTokens( + val primary: Color, + val surface: Color, + val surfaceContainer: Color, + val outline: Color, + val success: Color, + val warning: Color, + val error: Color, + val offline: Color, + val pending: Color, + val deviceSocket: Color, + val deviceSensor: Color, + val deviceInfrared: Color, +) + +@Immutable +data class ABoxSpacing( + val dp4: Dp = 4.dp, + val dp8: Dp = 8.dp, + val dp12: Dp = 12.dp, + val dp16: Dp = 16.dp, + val dp20: Dp = 20.dp, + val dp24: Dp = 24.dp, + val dp32: Dp = 32.dp, + val minimumTouchTarget: Dp = 48.dp, +) + +@Immutable +data class ABoxComponentState( + val enabled: Boolean = true, + val pending: Boolean = false, + val offline: Boolean = false, + val dangerous: Boolean = false, +) + +@Immutable +data class ABoxShapeTokens( + val smallCornerRadius: Dp, + val mediumCornerRadius: Dp, + val largeCornerRadius: Dp, +) + +@Immutable +data class ABoxThemeResolution( + val isDarkTheme: Boolean, + val colorTokens: ABoxColorTokens, + val shapeTokens: ABoxShapeTokens, + val blurEnabled: Boolean, + val renderer: DesignRenderer, +) + +object ABoxThemeDefaults { + val lightColors = ABoxColorTokens( + primary = Color(0xFF006780), + surface = Color(0xFFF8FAFC), + surfaceContainer = Color(0xFFE2E8F0), + outline = Color(0xFF70797D), + success = Color(0xFF1B7F4B), + warning = Color(0xFF8A5800), + error = Color(0xFFBA1A1A), + offline = Color(0xFF5F6B72), + pending = Color(0xFF8A5800), + deviceSocket = Color(0xFF006E90), + deviceSensor = Color(0xFF476B23), + deviceInfrared = Color(0xFF77536F), + ) + val darkColors = ABoxColorTokens( + primary = Color(0xFF5DD5F8), + surface = Color(0xFF101416), + surfaceContainer = Color(0xFF1F292D), + outline = Color(0xFF8A969B), + success = Color(0xFF7EDFA7), + warning = Color(0xFFFFB95B), + error = Color(0xFFFFB4AB), + offline = Color(0xFFBFC9CD), + pending = Color(0xFFFFB95B), + deviceSocket = Color(0xFF5DD5F8), + deviceSensor = Color(0xFFAFD67C), + deviceInfrared = Color(0xFFE6B5D6), + ) + val material3Shapes = ABoxShapeTokens( + smallCornerRadius = 8.dp, + mediumCornerRadius = 16.dp, + largeCornerRadius = 24.dp, + ) + val miuixShapes = ABoxShapeTokens( + smallCornerRadius = 12.dp, + mediumCornerRadius = 20.dp, + largeCornerRadius = 28.dp, + ) +} + +object ABoxThemeResolver { + fun resolve( + preferences: UiPreferences, + systemInDarkTheme: Boolean, + sdkInt: Int, + dynamicColorTokens: ABoxColorTokens?, + ): ABoxThemeResolution { + val isDarkTheme = when (preferences.themeMode) { + AppThemeMode.SYSTEM -> systemInDarkTheme + AppThemeMode.LIGHT -> false + AppThemeMode.DARK -> true + } + val brandTokens = if (isDarkTheme) { + ABoxThemeDefaults.darkColors + } else { + ABoxThemeDefaults.lightColors + } + val colorTokens = if (preferences.dynamicColorEnabled && sdkInt >= 31 && dynamicColorTokens != null) { + dynamicColorTokens + } else { + brandTokens + } + val shapeTokens = when (preferences.renderer) { + DesignRenderer.MATERIAL3 -> ABoxThemeDefaults.material3Shapes + DesignRenderer.MIUIX -> ABoxThemeDefaults.miuixShapes + } + + return ABoxThemeResolution( + isDarkTheme = isDarkTheme, + colorTokens = colorTokens, + shapeTokens = shapeTokens, + blurEnabled = preferences.blurEnabled && preferences.renderer == DesignRenderer.MIUIX, + renderer = preferences.renderer, + ) + } +} diff --git a/android/core/designsystem/src/screenshotTest/kotlin/com/flagship/abox/manager/designsystem/ScreenshotSmokeTest.kt b/android/core/designsystem/src/screenshotTest/kotlin/com/flagship/abox/manager/designsystem/ScreenshotSmokeTest.kt index 49b0538..85219f3 100644 --- a/android/core/designsystem/src/screenshotTest/kotlin/com/flagship/abox/manager/designsystem/ScreenshotSmokeTest.kt +++ b/android/core/designsystem/src/screenshotTest/kotlin/com/flagship/abox/manager/designsystem/ScreenshotSmokeTest.kt @@ -4,10 +4,21 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import com.android.tools.screenshot.PreviewTest +import com.flagship.abox.manager.preferences.AppThemeMode +import com.flagship.abox.manager.preferences.DesignRenderer +import com.flagship.abox.manager.preferences.UiPreferences @PreviewTest @Preview @Composable fun DesignSystemSmokePreview() { - ABoxTheme(darkTheme = false) { Text("截图基线") } + ABoxTheme( + preferences = UiPreferences( + renderer = DesignRenderer.MATERIAL3, + themeMode = AppThemeMode.LIGHT, + dynamicColorEnabled = false, + ), + ) { + Text("截图基线") + } } diff --git a/android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapterTest.kt b/android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapterTest.kt new file mode 100644 index 0000000..d4dacf6 --- /dev/null +++ b/android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/renderer/ABoxRendererAdapterTest.kt @@ -0,0 +1,19 @@ +package com.flagship.abox.manager.designsystem.renderer + +import com.flagship.abox.manager.preferences.DesignRenderer +import org.junit.Assert.assertSame +import org.junit.Test + +class ABoxRendererAdapterTest { + @Test + fun selectsMaterial3AndMiuixAdaptersFromRendererPreference() { + assertSame( + Material3RendererAdapter, + ABoxRendererAdapters.forRenderer(DesignRenderer.MATERIAL3), + ) + assertSame( + MiuixRendererAdapter, + ABoxRendererAdapters.forRenderer(DesignRenderer.MIUIX), + ) + } +} diff --git a/android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxThemeResolverTest.kt b/android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxThemeResolverTest.kt new file mode 100644 index 0000000..e0a23ce --- /dev/null +++ b/android/core/designsystem/src/test/kotlin/com/flagship/abox/manager/designsystem/tokens/ABoxThemeResolverTest.kt @@ -0,0 +1,116 @@ +package com.flagship.abox.manager.designsystem.tokens + +import androidx.compose.ui.graphics.Color +import com.flagship.abox.manager.preferences.AppThemeMode +import com.flagship.abox.manager.preferences.DesignRenderer +import com.flagship.abox.manager.preferences.UiPreferences +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Test + +class ABoxThemeResolverTest { + @Test + fun systemLightAndDarkResolveDeterministically() { + val light = ABoxThemeResolver.resolve( + preferences = UiPreferences(themeMode = AppThemeMode.SYSTEM), + systemInDarkTheme = false, + sdkInt = 30, + dynamicColorTokens = null, + ) + val dark = ABoxThemeResolver.resolve( + preferences = UiPreferences(themeMode = AppThemeMode.SYSTEM), + systemInDarkTheme = true, + sdkInt = 30, + dynamicColorTokens = null, + ) + + assertFalse(light.isDarkTheme) + assertEquals(ABoxThemeDefaults.lightColors, light.colorTokens) + assertEquals(ABoxThemeDefaults.darkColors, dark.colorTokens) + } + + @Test + fun dynamicColorDisabledUsesBrandTokens() { + val resolution = ABoxThemeResolver.resolve( + preferences = UiPreferences( + themeMode = AppThemeMode.LIGHT, + dynamicColorEnabled = false, + ), + systemInDarkTheme = false, + sdkInt = 31, + dynamicColorTokens = dynamicTokens, + ) + + assertEquals(ABoxThemeDefaults.lightColors, resolution.colorTokens) + } + + @Test + fun pre31DynamicColorFallsBackToBrandTokens() { + val resolution = ABoxThemeResolver.resolve( + preferences = UiPreferences( + themeMode = AppThemeMode.DARK, + dynamicColorEnabled = true, + ), + systemInDarkTheme = false, + sdkInt = 30, + dynamicColorTokens = dynamicTokens, + ) + + assertEquals(ABoxThemeDefaults.darkColors, resolution.colorTokens) + } + + @Test + fun blurDisabledResolvesOpaqueSurface() { + val resolution = ABoxThemeResolver.resolve( + preferences = UiPreferences(blurEnabled = false), + systemInDarkTheme = false, + sdkInt = 31, + dynamicColorTokens = null, + ) + + assertFalse(resolution.blurEnabled) + assertEquals(1f, resolution.colorTokens.surface.alpha) + assertEquals(1f, resolution.colorTokens.surfaceContainer.alpha) + } + + @Test + fun rendererChangesShapeButNotSemanticStateRoles() { + val material = ABoxThemeResolver.resolve( + preferences = UiPreferences(renderer = DesignRenderer.MATERIAL3), + systemInDarkTheme = false, + sdkInt = 31, + dynamicColorTokens = null, + ) + val miuix = ABoxThemeResolver.resolve( + preferences = UiPreferences(renderer = DesignRenderer.MIUIX), + systemInDarkTheme = false, + sdkInt = 31, + dynamicColorTokens = null, + ) + + assertEquals(material.colorTokens.success, miuix.colorTokens.success) + assertEquals(material.colorTokens.warning, miuix.colorTokens.warning) + assertEquals(material.colorTokens.error, miuix.colorTokens.error) + assertEquals(material.colorTokens.offline, miuix.colorTokens.offline) + assertEquals(material.colorTokens.pending, miuix.colorTokens.pending) + assertNotEquals(material.shapeTokens, miuix.shapeTokens) + } + + private companion object { + val dynamicTokens = ABoxColorTokens( + primary = Color(0xFF111111), + surface = Color(0xFF222222), + surfaceContainer = Color(0xFF333333), + outline = Color(0xFF444444), + success = Color(0xFF555555), + warning = Color(0xFF666666), + error = Color(0xFF777777), + offline = Color(0xFF888888), + pending = Color(0xFF999999), + deviceSocket = Color(0xFFAAAAAA), + deviceSensor = Color(0xFFBBBBBB), + deviceInfrared = Color(0xFFCCCCCC), + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 11f7d0e..fda3fcc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,7 @@ adaptive = "1.3.0" adaptiveNavigationSuite = "1.4.0" window = "1.5.1" composeScreenshot = "0.0.1-alpha16" +miuix = "0.9.3" junit = "4.13.2" androidxJunit = "1.2.1" espresso = "3.6.1" @@ -40,6 +41,7 @@ compose-material-icons-extended = { module = "androidx.compose.material:material compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } compose-screenshot-test = { module = "com.android.tools.screenshot:screenshot-validation-api", version.ref = "composeScreenshot" } +miuix-ui-android = { module = "top.yukonga.miuix.kmp:miuix-ui-android", version.ref = "miuix" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }