feat(ui): complete app experience redesign

This commit is contained in:
2026-08-27 11:45:30 +08:00
parent 816606234b
commit 54aa74126f
70 changed files with 4510 additions and 985 deletions
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -476,13 +477,19 @@ fun ABoxDeviceDetailScaffold(
title: String,
onBack: () -> Unit,
modifier: Modifier = Modifier,
subtitle: String? = null,
icon: ABoxIconKey = ABoxIconKey.UNKNOWN_DEVICE,
action: (@Composable () -> Unit)? = null,
content: @Composable (PaddingValues) -> Unit,
) {
Scaffold(
modifier = modifier.fillMaxSize(),
topBar = {
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 8.dp),
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(horizontal = 8.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ABoxIconButton(
@@ -490,11 +497,38 @@ fun ABoxDeviceDetailScaffold(
contentDescription = "返回",
onClick = onBack,
)
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(start = 8.dp),
)
Surface(
shape = RoundedCornerShape(14.dp),
color = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier.padding(start = 4.dp),
) {
ABoxIcon(
key = icon,
contentDescription = subtitle ?: title,
modifier = Modifier.padding(9.dp).size(24.dp),
)
}
Column(
modifier = Modifier.weight(1f).padding(start = 12.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
action?.invoke()
}
},
content = content,
@@ -12,6 +12,8 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import com.flagship.abox.manager.designsystem.renderer.ABoxRendererAdapters
import com.flagship.abox.manager.preferences.DesignRenderer
import com.flagship.abox.manager.designsystem.tokens.ABoxColorTokens
@@ -20,6 +22,7 @@ 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.NavigationPresentation
import com.flagship.abox.manager.preferences.UiPreferences
val LocalABoxColors = staticCompositionLocalOf { ABoxThemeDefaults.lightColors }
@@ -29,6 +32,9 @@ val LocalABoxShapes = staticCompositionLocalOf { ABoxThemeDefaults.material3Shap
val LocalABoxRenderer = staticCompositionLocalOf { DesignRenderer.MATERIAL3 }
val LocalABoxReducedMotion = staticCompositionLocalOf { false }
val LocalABoxBlurEnabled = staticCompositionLocalOf { false }
val LocalABoxNavigationPresentation = staticCompositionLocalOf { NavigationPresentation.STANDARD }
val LocalABoxLiquidGlassEnabled = staticCompositionLocalOf { false }
val LocalABoxPredictiveBackEnabled = staticCompositionLocalOf { false }
@Composable
fun ABoxTheme(
@@ -78,8 +84,14 @@ internal fun ABoxThemeContent(
dynamicColorTokens = dynamicColorTokens,
)
val renderer = ABoxRendererAdapters.forRenderer(resolution.renderer)
val density = LocalDensity.current
val scaledDensity = Density(
density = density.density * (preferences.interfaceScale.percent / 100f),
fontScale = density.fontScale,
)
CompositionLocalProvider(
LocalDensity provides scaledDensity,
LocalABoxColors provides resolution.colorTokens,
LocalABoxSpacing provides ABoxSpacing(),
LocalABoxTypography provides ABoxTypography,
@@ -87,6 +99,9 @@ internal fun ABoxThemeContent(
LocalABoxRenderer provides resolution.renderer,
LocalABoxReducedMotion provides reducedMotion,
LocalABoxBlurEnabled provides resolution.blurEnabled,
LocalABoxNavigationPresentation provides preferences.navigationPresentation,
LocalABoxLiquidGlassEnabled provides preferences.liquidGlassEnabled,
LocalABoxPredictiveBackEnabled provides preferences.predictiveBackEnabled,
) {
renderer.Render(
resolution = resolution,
@@ -11,6 +11,11 @@ import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Tune
import androidx.compose.material.icons.outlined.Vibration
import androidx.compose.material.icons.outlined.ViewCarousel
import androidx.compose.material.icons.outlined.Layers
import androidx.compose.material.icons.outlined.Power
import androidx.compose.material.icons.outlined.PowerSettingsNew
@@ -51,6 +56,11 @@ enum class ABoxIconKey {
WARNING,
ERROR,
UNKNOWN_DEVICE,
SETTINGS,
THEME,
EFFECTS,
FEEDBACK,
SCALE,
}
/** Design-system-owned device categories; this module does not depend on domain models. */
@@ -94,6 +104,11 @@ object ABoxIconMapper {
ABoxIconKey.WARNING -> Icons.Outlined.Warning
ABoxIconKey.ERROR -> Icons.Outlined.ErrorOutline
ABoxIconKey.UNKNOWN_DEVICE -> Icons.AutoMirrored.Outlined.HelpOutline
ABoxIconKey.SETTINGS -> Icons.Outlined.Settings
ABoxIconKey.THEME -> Icons.Outlined.Palette
ABoxIconKey.EFFECTS -> Icons.Outlined.ViewCarousel
ABoxIconKey.FEEDBACK -> Icons.Outlined.Vibration
ABoxIconKey.SCALE -> Icons.Outlined.Tune
}
}
@@ -6,10 +6,7 @@ import com.flagship.abox.manager.preferences.DesignRenderer
internal interface ABoxRendererAdapter {
@Composable
fun Render(
resolution: ABoxThemeResolution,
content: @Composable () -> Unit,
)
fun Render(resolution: ABoxThemeResolution, content: @Composable () -> Unit)
}
internal object ABoxRendererAdapters {
@@ -13,11 +13,11 @@ internal object MiuixRendererAdapter : ABoxRendererAdapter {
content: @Composable () -> Unit,
) {
val colors = if (resolution.isDarkTheme) darkColorScheme() else lightColorScheme()
MiuixTheme(
colors = colors,
) {
Material3RendererAdapter.Render(
resolution = resolution,
MiuixTheme(colors = colors) {
androidx.compose.material3.MaterialTheme(
colorScheme = com.flagship.abox.manager.designsystem.materialColorScheme(resolution),
typography = com.flagship.abox.manager.designsystem.ABoxTypography,
shapes = com.flagship.abox.manager.designsystem.ABoxShapes(resolution.shapeTokens),
content = content,
)
}
@@ -5,6 +5,7 @@ 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.AccentPreference
import com.flagship.abox.manager.preferences.DesignRenderer
import com.flagship.abox.manager.preferences.UiPreferences
@@ -121,7 +122,7 @@ object ABoxThemeResolver {
val colorTokens = if (preferences.dynamicColorEnabled && sdkInt >= 31 && dynamicColorTokens != null) {
dynamicColorTokens
} else {
brandTokens
brandTokens.withAccent(preferences.accentPreference, isDarkTheme)
}
val shapeTokens = when (preferences.renderer) {
DesignRenderer.MATERIAL3 -> ABoxThemeDefaults.material3Shapes
@@ -137,3 +138,14 @@ object ABoxThemeResolver {
)
}
}
private fun ABoxColorTokens.withAccent(accent: AccentPreference, dark: Boolean): ABoxColorTokens {
val selected = when (accent) {
AccentPreference.BLUE -> if (dark) Color(0xFF9CCAFF) else Color(0xFF385F9D)
AccentPreference.GREEN -> if (dark) Color(0xFF7EDFA7) else Color(0xFF247A4B)
AccentPreference.AMBER -> if (dark) Color(0xFFFFC46B) else Color(0xFF8A5800)
AccentPreference.ROSE -> if (dark) Color(0xFFFFB1C1) else Color(0xFFA43C59)
AccentPreference.VIOLET -> if (dark) Color(0xFFD4BBFF) else Color(0xFF6D52A2)
}
return copy(primary = selected, deviceSocket = selected)
}