feat: add dual-renderer design tokens
This commit is contained in:
+12
-1
@@ -5,10 +5,21 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.android.tools.screenshot.PreviewTest
|
import com.android.tools.screenshot.PreviewTest
|
||||||
import com.flagship.abox.manager.designsystem.ABoxTheme
|
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
|
@PreviewTest
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun AppSmokePreview() {
|
fun AppSmokePreview() {
|
||||||
ABoxTheme(darkTheme = false) { Text("ABox Manager") }
|
ABoxTheme(
|
||||||
|
preferences = UiPreferences(
|
||||||
|
renderer = DesignRenderer.MATERIAL3,
|
||||||
|
themeMode = AppThemeMode.LIGHT,
|
||||||
|
dynamicColorEnabled = false,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text("ABox Manager")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,14 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(":android:core:preferences"))
|
||||||
implementation(platform(libs.compose.bom))
|
implementation(platform(libs.compose.bom))
|
||||||
implementation(libs.compose.ui)
|
implementation(libs.compose.ui)
|
||||||
implementation(libs.compose.ui.tooling.preview)
|
implementation(libs.compose.ui.tooling.preview)
|
||||||
implementation(libs.compose.material3)
|
implementation(libs.compose.material3)
|
||||||
implementation(libs.compose.material.icons.extended)
|
implementation(libs.compose.material.icons.extended)
|
||||||
|
implementation(libs.miuix.ui.android)
|
||||||
debugImplementation(libs.compose.ui.tooling)
|
debugImplementation(libs.compose.ui.tooling)
|
||||||
screenshotTestImplementation(libs.compose.screenshot.test)
|
screenshotTestImplementation(libs.compose.screenshot.test)
|
||||||
|
testImplementation(libs.junit)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -2,10 +2,11 @@ package com.flagship.abox.manager.designsystem
|
|||||||
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Shapes
|
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(
|
fun ABoxShapes(tokens: ABoxShapeTokens = ABoxThemeDefaults.material3Shapes) = Shapes(
|
||||||
small = RoundedCornerShape(8.dp),
|
small = RoundedCornerShape(tokens.smallCornerRadius),
|
||||||
medium = RoundedCornerShape(16.dp),
|
medium = RoundedCornerShape(tokens.mediumCornerRadius),
|
||||||
large = RoundedCornerShape(24.dp),
|
large = RoundedCornerShape(tokens.largeCornerRadius),
|
||||||
)
|
)
|
||||||
|
|||||||
+143
-26
@@ -1,41 +1,158 @@
|
|||||||
package com.flagship.abox.manager.designsystem
|
package com.flagship.abox.manager.designsystem
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import android.os.Build
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
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.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(
|
val LocalABoxColors = staticCompositionLocalOf { ABoxThemeDefaults.lightColors }
|
||||||
primary = Color(0xFF006780),
|
val LocalABoxSpacing = staticCompositionLocalOf { ABoxSpacing() }
|
||||||
onPrimary = Color.White,
|
val LocalABoxTypography = staticCompositionLocalOf { ABoxTypography }
|
||||||
primaryContainer = Color(0xFFB8EAFF),
|
val LocalABoxShapes = staticCompositionLocalOf { ABoxThemeDefaults.material3Shapes }
|
||||||
onPrimaryContainer = Color(0xFF001F29),
|
val LocalABoxRenderer = staticCompositionLocalOf { DesignRenderer.MATERIAL3 }
|
||||||
secondary = Color(0xFF4D616A),
|
val LocalABoxReducedMotion = staticCompositionLocalOf { false }
|
||||||
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),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ABoxTheme(
|
fun ABoxTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
preferences: UiPreferences,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
val systemInDarkTheme = androidx.compose.foundation.isSystemInDarkTheme()
|
||||||
colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme,
|
val isDarkTheme = when (preferences.themeMode) {
|
||||||
typography = ABoxTypography,
|
AppThemeMode.SYSTEM -> systemInDarkTheme
|
||||||
shapes = ABoxShapes,
|
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,
|
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,
|
||||||
|
)
|
||||||
|
|||||||
+1
@@ -2,4 +2,5 @@ package com.flagship.abox.manager.designsystem
|
|||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
|
|
||||||
|
/** Default typography supplied through [LocalABoxTypography]. */
|
||||||
val ABoxTypography = Typography()
|
val ABoxTypography = Typography()
|
||||||
|
|||||||
+20
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-1
@@ -4,10 +4,21 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.android.tools.screenshot.PreviewTest
|
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
|
@PreviewTest
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun DesignSystemSmokePreview() {
|
fun DesignSystemSmokePreview() {
|
||||||
ABoxTheme(darkTheme = false) { Text("截图基线") }
|
ABoxTheme(
|
||||||
|
preferences = UiPreferences(
|
||||||
|
renderer = DesignRenderer.MATERIAL3,
|
||||||
|
themeMode = AppThemeMode.LIGHT,
|
||||||
|
dynamicColorEnabled = false,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text("截图基线")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
@@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ adaptive = "1.3.0"
|
|||||||
adaptiveNavigationSuite = "1.4.0"
|
adaptiveNavigationSuite = "1.4.0"
|
||||||
window = "1.5.1"
|
window = "1.5.1"
|
||||||
composeScreenshot = "0.0.1-alpha16"
|
composeScreenshot = "0.0.1-alpha16"
|
||||||
|
miuix = "0.9.3"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
androidxJunit = "1.2.1"
|
androidxJunit = "1.2.1"
|
||||||
espresso = "3.6.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-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
||||||
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
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" }
|
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-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", 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" }
|
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
||||||
|
|||||||
Reference in New Issue
Block a user