fix(ui): 修复照片编辑页错位、亮色主题统一与添加切换过渡动画

- 切换动画:ToolPanel 引入 AnimatedContent,在 旋转 / 裁剪 / 涂鸦 模式切换时平滑淡入滑动过渡
- 修复错位:将“撤销”按钮从限制宽高的 OutlinedIconButton 改为 OutlinedButton(contentPadding),解决“撤销”汉字被压缩竖向换行错位的问题
- 亮色主题统一:将界面配色调整为与鸭鸭日历应用一致的亮色 Material 3 Theme (MaterialTheme.colorScheme.surface / surfaceContainer)
This commit is contained in:
xfy 2026-07-23 14:59:05 +08:00
parent 088860861e
commit 0a6b9449a3

View File

@ -1,9 +1,15 @@
package plus.rua.project.ui package plus.rua.project.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -14,6 +20,7 @@ import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
@ -34,7 +41,6 @@ import androidx.compose.material.icons.outlined.RotateLeft
import androidx.compose.material.icons.outlined.RotateRight import androidx.compose.material.icons.outlined.RotateRight
import androidx.compose.material.icons.outlined.Undo import androidx.compose.material.icons.outlined.Undo
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -43,7 +49,7 @@ import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
@ -119,9 +125,6 @@ fun PhotoEditorScreen(
LaunchedEffect(savedPath) { onSaved(savedPath) } LaunchedEffect(savedPath) { onSaved(savedPath) }
} }
val darkBackground = Color(0xFF121316)
val topBarContainer = Color(0xFF1A1B1F)
Scaffold( Scaffold(
modifier = modifier.semantics { testTagsAsResourceId = true }, modifier = modifier.semantics { testTagsAsResourceId = true },
topBar = { topBar = {
@ -129,16 +132,14 @@ fun PhotoEditorScreen(
title = { title = {
Text( Text(
text = "照片图层编辑", text = "照片图层编辑",
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold), style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold)
color = Color.White
) )
}, },
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack, imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "返回", contentDescription = "返回"
tint = Color.White
) )
} }
}, },
@ -158,10 +159,10 @@ fun PhotoEditorScreen(
Text("完成", fontWeight = FontWeight.Bold) Text("完成", fontWeight = FontWeight.Bold)
} }
}, },
colors = TopAppBarDefaults.topAppBarColors(containerColor = topBarContainer) colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surface)
) )
}, },
containerColor = darkBackground containerColor = MaterialTheme.colorScheme.surface
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
@ -172,7 +173,7 @@ fun PhotoEditorScreen(
state.loading -> Box( state.loading -> Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { CircularProgressIndicator(color = MaterialTheme.colorScheme.primary) } ) { CircularProgressIndicator() }
state.error != null -> Box( state.error != null -> Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -231,7 +232,6 @@ private fun EditorBody(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(Color(0xFF1A1B1F))
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 8.dp)
) { ) {
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
@ -240,12 +240,6 @@ private fun EditorBody(
selected = activeTab == tab, selected = activeTab == tab,
onClick = { onTabChange(tab) }, onClick = { onTabChange(tab) },
shape = SegmentedButtonDefaults.itemShape(index, EditTab.entries.size), shape = SegmentedButtonDefaults.itemShape(index, EditTab.entries.size),
colors = SegmentedButtonDefaults.colors(
activeContainerColor = MaterialTheme.colorScheme.primaryContainer,
activeContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
inactiveContainerColor = Color(0xFF25262B),
inactiveContentColor = Color.LightGray
),
icon = { icon = {
Icon( Icon(
when (tab) { when (tab) {
@ -283,8 +277,8 @@ private fun EditorBody(
Card( Card(
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = Color.Black), colors = CardDefaults.cardColors(containerColor = Color.Black),
border = BorderStroke(1.dp, Color(0xFF2C2D32)), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)),
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) { ) {
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -303,7 +297,7 @@ private fun EditorBody(
if (saving) { if (saving) {
Surface( Surface(
color = Color.Black.copy(alpha = 0.6f), color = Color.Black.copy(alpha = 0.7f),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
@ -326,9 +320,18 @@ private fun EditorBody(
} }
} }
// Tool Control Panel at Bottom // Tool Control Panel with Smooth Animated Transition
AnimatedContent(
targetState = activeTab,
transitionSpec = {
(slideInHorizontally { width -> if (targetState > initialState) width else -width } + fadeIn(tween(220)))
.togetherWith(slideOutHorizontally { width -> if (targetState > initialState) -width else width } + fadeOut(tween(180)))
},
label = "tabTransition",
modifier = Modifier.fillMaxWidth()
) { targetMode ->
ToolPanel( ToolPanel(
mode = activeTab, mode = targetMode,
state = state, state = state,
onRotate = onRotate, onRotate = onRotate,
onCropToggle = onCropToggle, onCropToggle = onCropToggle,
@ -338,6 +341,7 @@ private fun EditorBody(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
}
} }
@Composable @Composable
@ -603,8 +607,9 @@ private fun ToolPanel(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Surface( Surface(
color = Color(0xFF1A1B1F), color = MaterialTheme.colorScheme.surfaceContainer,
shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
tonalElevation = 3.dp,
modifier = modifier modifier = modifier
) { ) {
Column( Column(
@ -622,11 +627,7 @@ private fun ToolPanel(
) { ) {
FilledTonalButton( FilledTonalButton(
onClick = { onRotate(-90) }, onClick = { onRotate(-90) },
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp)
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color(0xFF2B2C32),
contentColor = Color.White
)
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.RotateLeft, imageVector = Icons.Outlined.RotateLeft,
@ -638,9 +639,9 @@ private fun ToolPanel(
} }
Surface( Surface(
color = Color(0xFF26272C), color = MaterialTheme.colorScheme.surfaceContainerHigh,
shape = CircleShape, shape = CircleShape,
border = BorderStroke(1.dp, Color(0xFF3A3B42)) border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
) { ) {
Text( Text(
text = "${state.rotationDegrees}°", text = "${state.rotationDegrees}°",
@ -653,11 +654,7 @@ private fun ToolPanel(
FilledTonalButton( FilledTonalButton(
onClick = { onRotate(90) }, onClick = { onRotate(90) },
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp)
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color(0xFF2B2C32),
contentColor = Color.White
)
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.RotateRight, imageVector = Icons.Outlined.RotateRight,
@ -679,7 +676,7 @@ private fun ToolPanel(
Text( Text(
text = if (state.cropEnabled) "拖动四角或框架调整裁剪区域" else "点击开启自由裁剪", text = if (state.cropEnabled) "拖动四角或框架调整裁剪区域" else "点击开启自由裁剪",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = Color.LightGray color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Row( Row(
@ -730,34 +727,32 @@ private fun ToolPanel(
Text( Text(
text = "涂鸦画笔颜色", text = "涂鸦画笔颜色",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = Color.LightGray color = MaterialTheme.colorScheme.onSurfaceVariant
) )
OutlinedIconButton( // 使用 OutlinedButton 避免数字/汉字在宽高度受限的 IconButtons 里错位换行
OutlinedButton(
onClick = onUndoStroke, onClick = onUndoStroke,
enabled = state.strokes.isNotEmpty(), enabled = state.strokes.isNotEmpty(),
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(12.dp),
border = BorderStroke(1.dp, if (state.strokes.isNotEmpty()) Color.White else Color.Gray) contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp),
) { border = BorderStroke(
Row( 1.dp,
modifier = Modifier.padding(horizontal = 8.dp), if (state.strokes.isNotEmpty()) MaterialTheme.colorScheme.outline else MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)
verticalAlignment = Alignment.CenterVertically )
) { ) {
Icon( Icon(
imageVector = Icons.Outlined.Undo, imageVector = Icons.Outlined.Undo,
contentDescription = "撤销笔触", contentDescription = "撤销笔触",
tint = if (state.strokes.isNotEmpty()) Color.White else Color.Gray,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Text( Text(
text = "撤销", text = "撤销",
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelMedium
color = if (state.strokes.isNotEmpty()) Color.White else Color.Gray
) )
} }
} }
}
val palette = listOf( val palette = listOf(
Color(0xFFFF453A), // Red Color(0xFFFF453A), // Red
@ -765,8 +760,8 @@ private fun ToolPanel(
Color(0xFFFFD60A), // Yellow Color(0xFFFFD60A), // Yellow
Color(0xFF30D158), // Green Color(0xFF30D158), // Green
Color(0xFF64D2FF), // Cyan Color(0xFF64D2FF), // Cyan
Color(0xFFFFFFFF), // White Color(0xFF007AFF), // Blue
Color(0xFF000000) // Black Color(0xFF1D1B20) // Dark Gray / Black
) )
Row( Row(
@ -782,7 +777,7 @@ private fun ToolPanel(
.background(c, CircleShape) .background(c, CircleShape)
.border( .border(
width = if (selected) 2.5.dp else 1.dp, width = if (selected) 2.5.dp else 1.dp,
color = if (selected) MaterialTheme.colorScheme.primary else Color.Gray, color = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant,
shape = CircleShape shape = CircleShape
) )
.clickable { onStrokeColorChange(c) }, .clickable { onStrokeColorChange(c) },
@ -792,7 +787,7 @@ private fun ToolPanel(
Icon( Icon(
imageVector = Icons.Filled.Check, imageVector = Icons.Filled.Check,
contentDescription = null, contentDescription = null,
tint = if (c == Color.White || c == Color(0xFFFFD60A)) Color.Black else Color.White, tint = if (c == Color(0xFFFFD60A) || c == Color(0xFF64D2FF)) Color.Black else Color.White,
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
) )
} }