feat:新 UI
This commit is contained in:
@@ -13,11 +13,11 @@ android {
|
||||
applicationId = "com.healthflow.app"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 30
|
||||
versionName = "2.0.7"
|
||||
versionCode = 31
|
||||
versionName = "2.0.8"
|
||||
|
||||
buildConfigField("String", "API_BASE_URL", "\"https://health.amos.us.kg/api/\"")
|
||||
buildConfigField("int", "VERSION_CODE", "30")
|
||||
buildConfigField("int", "VERSION_CODE", "31")
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.healthflow.app.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -16,7 +20,6 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.healthflow.app.R
|
||||
import com.healthflow.app.data.model.User
|
||||
import com.healthflow.app.ui.screen.*
|
||||
import com.healthflow.app.ui.theme.*
|
||||
@@ -57,39 +60,22 @@ fun MainNavigation(
|
||||
val error by epochViewModel.error.collectAsState()
|
||||
val profileStats by epochViewModel.profileStats.collectAsState()
|
||||
|
||||
// 初始化加载
|
||||
LaunchedEffect(Unit) {
|
||||
epochViewModel.loadAll()
|
||||
}
|
||||
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
// 判断是否显示底部导航
|
||||
val showBottomNav = currentRoute in tabs.map { it.route }
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
if (showBottomNav) {
|
||||
BottomNavBar(
|
||||
currentRoute = currentRoute,
|
||||
onTabSelected = { tab ->
|
||||
navController.navigate(tab.route) {
|
||||
popUpTo(Tab.Epoch.route) { saveState = true }
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Tab.Epoch.route,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(bottom = if (showBottomNav) 64.dp else 0.dp)
|
||||
) {
|
||||
// 纪元 Tab
|
||||
composable(Tab.Epoch.route) {
|
||||
EpochScreen(
|
||||
epochs = epochList,
|
||||
@@ -104,7 +90,6 @@ fun MainNavigation(
|
||||
)
|
||||
}
|
||||
|
||||
// 计划 Tab
|
||||
composable(Tab.Plan.route) {
|
||||
PlanScreen(
|
||||
activeEpoch = activeEpoch,
|
||||
@@ -124,7 +109,6 @@ fun MainNavigation(
|
||||
)
|
||||
}
|
||||
|
||||
// 我的 Tab
|
||||
composable(Tab.Profile.route) {
|
||||
ProfileScreen(
|
||||
user = user,
|
||||
@@ -134,7 +118,6 @@ fun MainNavigation(
|
||||
)
|
||||
}
|
||||
|
||||
// 创建纪元页面
|
||||
composable(Routes.CREATE_EPOCH) {
|
||||
CreateEpochScreen(
|
||||
isLoading = isLoading,
|
||||
@@ -149,7 +132,6 @@ fun MainNavigation(
|
||||
)
|
||||
}
|
||||
|
||||
// 周计划详情页面
|
||||
composable(
|
||||
route = Routes.WEEK_PLAN_DETAIL,
|
||||
arguments = listOf(
|
||||
@@ -180,38 +162,47 @@ fun MainNavigation(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomNavBar(
|
||||
currentRoute: String?,
|
||||
onTabSelected: (Tab) -> Unit
|
||||
) {
|
||||
Surface(
|
||||
color = Color.White,
|
||||
shadowElevation = 8.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.height(64.dp),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
tabs.forEach { tab ->
|
||||
val selected = currentRoute == tab.route
|
||||
NavBarItem(
|
||||
label = tab.label,
|
||||
selected = selected,
|
||||
icon = when (tab) {
|
||||
Tab.Epoch -> TabIcon.Circle
|
||||
Tab.Plan -> TabIcon.Square
|
||||
Tab.Profile -> TabIcon.Triangle
|
||||
},
|
||||
onClick = { onTabSelected(tab) }
|
||||
)
|
||||
// 底部导航栏
|
||||
if (showBottomNav) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
) {
|
||||
HorizontalDivider(color = Slate200, thickness = 1.dp)
|
||||
Surface(
|
||||
color = Color.White,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.height(64.dp),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
tabs.forEach { tab ->
|
||||
val selected = currentRoute == tab.route
|
||||
NavBarItem(
|
||||
label = tab.label,
|
||||
selected = selected,
|
||||
icon = when (tab) {
|
||||
Tab.Epoch -> TabIcon.Circle
|
||||
Tab.Plan -> TabIcon.Square
|
||||
Tab.Profile -> TabIcon.Triangle
|
||||
},
|
||||
onClick = {
|
||||
navController.navigate(tab.route) {
|
||||
popUpTo(Tab.Epoch.route) { saveState = true }
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,73 +215,62 @@ private fun NavBarItem(
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
icon: TabIcon,
|
||||
onClick: () -> Unit
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val color = if (selected) Slate800 else Slate400
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onClick
|
||||
)
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
IconButton(onClick = onClick) {
|
||||
Box(
|
||||
modifier = Modifier.size(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (icon) {
|
||||
TabIcon.Circle -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.then(
|
||||
if (selected) {
|
||||
Modifier
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.foundation.Canvas(modifier = Modifier.size(20.dp)) {
|
||||
drawCircle(
|
||||
color = color,
|
||||
style = androidx.compose.ui.graphics.drawscope.Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
}
|
||||
Canvas(modifier = Modifier.size(20.dp)) {
|
||||
drawCircle(
|
||||
color = color,
|
||||
style = Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
}
|
||||
}
|
||||
TabIcon.Square -> {
|
||||
Box(
|
||||
modifier = Modifier.size(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.foundation.Canvas(modifier = Modifier.size(18.dp)) {
|
||||
drawRect(
|
||||
color = color,
|
||||
style = androidx.compose.ui.graphics.drawscope.Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
}
|
||||
Canvas(modifier = Modifier.size(18.dp)) {
|
||||
drawRect(
|
||||
color = color,
|
||||
style = Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
}
|
||||
}
|
||||
TabIcon.Triangle -> {
|
||||
Box(
|
||||
modifier = Modifier.size(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.foundation.Canvas(modifier = Modifier.size(20.dp)) {
|
||||
val path = androidx.compose.ui.graphics.Path().apply {
|
||||
moveTo(size.width / 2, 0f)
|
||||
lineTo(size.width, size.height)
|
||||
lineTo(0f, size.height)
|
||||
close()
|
||||
}
|
||||
drawPath(
|
||||
path = path,
|
||||
color = color,
|
||||
style = androidx.compose.ui.graphics.drawscope.Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
Canvas(modifier = Modifier.size(20.dp)) {
|
||||
val path = Path().apply {
|
||||
moveTo(size.width / 2, 1f)
|
||||
lineTo(size.width - 1f, size.height - 1f)
|
||||
lineTo(1f, size.height - 1f)
|
||||
close()
|
||||
}
|
||||
drawPath(
|
||||
path = path,
|
||||
color = color,
|
||||
style = Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 12.sp,
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
@@ -14,7 +13,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.healthflow.app.ui.theme.*
|
||||
@@ -43,35 +41,38 @@ fun CreateEpochScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 24.dp)
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 返回按钮
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable(onClick = onBack)
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onBack)
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
tint = Slate600,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "取消",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
color = Slate600
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
text = "开启新纪元",
|
||||
fontSize = 28.sp,
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Slate900
|
||||
)
|
||||
@@ -80,15 +81,15 @@ fun CreateEpochScreen(
|
||||
text = "为你的健康阶段设定一个开端",
|
||||
fontSize = 14.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// 纪元名称
|
||||
Text(
|
||||
text = "纪元名称",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate700
|
||||
)
|
||||
@@ -96,24 +97,25 @@ fun CreateEpochScreen(
|
||||
OutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
placeholder = { Text("例如:盛夏减脂阶段", color = Slate400) },
|
||||
placeholder = { Text("例如:盛夏减脂阶段", color = Slate400, fontSize = 15.sp) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = Slate300,
|
||||
unfocusedBorderColor = Slate200,
|
||||
focusedContainerColor = Color.White,
|
||||
unfocusedContainerColor = Color.White
|
||||
),
|
||||
singleLine = true
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 15.sp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 起始日期
|
||||
Text(
|
||||
text = "起始日期",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate700
|
||||
)
|
||||
@@ -124,12 +126,12 @@ fun CreateEpochScreen(
|
||||
onClick = { showStartDatePicker = true }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 截止日期
|
||||
Text(
|
||||
text = "截止日期",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate700
|
||||
)
|
||||
@@ -146,7 +148,7 @@ fun CreateEpochScreen(
|
||||
Text(
|
||||
text = it,
|
||||
color = ErrorRed,
|
||||
fontSize = 14.sp
|
||||
fontSize = 13.sp
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,9 +167,9 @@ fun CreateEpochScreen(
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
.height(52.dp),
|
||||
enabled = startDate != null && endDate != null && !isLoading,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Slate900,
|
||||
disabledContainerColor = Slate300
|
||||
@@ -176,19 +178,19 @@ fun CreateEpochScreen(
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
color = Color.White,
|
||||
modifier = Modifier.size(24.dp),
|
||||
modifier = Modifier.size(22.dp),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "确认开启",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
}
|
||||
|
||||
// 日期选择器
|
||||
@@ -221,29 +223,34 @@ private fun DatePickerField(
|
||||
placeholder: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = {},
|
||||
placeholder = { Text(placeholder, color = Slate400) },
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
enabled = false,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
disabledBorderColor = Slate200,
|
||||
disabledContainerColor = Color.White,
|
||||
disabledTextColor = Slate900
|
||||
),
|
||||
trailingIcon = {
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
color = Color.White,
|
||||
border = ButtonDefaults.outlinedButtonBorder.copy(width = 1.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 14.dp, vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = value.ifEmpty { placeholder },
|
||||
fontSize = 15.sp,
|
||||
color = if (value.isEmpty()) Slate400 else Slate900
|
||||
)
|
||||
Icon(
|
||||
Icons.Default.DateRange,
|
||||
contentDescription = "选择日期",
|
||||
tint = Slate400
|
||||
tint = Slate400,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
@@ -32,14 +32,17 @@ fun EpochScreen(
|
||||
onEpochClick: (WeightEpoch) -> Unit,
|
||||
onCreateNew: () -> Unit
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 24.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
Spacer(modifier = Modifier.height(56.dp))
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
@@ -49,69 +52,86 @@ fun EpochScreen(
|
||||
color = Slate900
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = "追踪你的健康演变",
|
||||
fontSize = 14.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
fontSize = 15.sp,
|
||||
color = Slate400
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
|
||||
if (isLoading && epochs.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = Brand500)
|
||||
when {
|
||||
isLoading && epochs.isEmpty() -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = Brand500)
|
||||
}
|
||||
}
|
||||
} else if (epochs.isEmpty()) {
|
||||
// 空状态
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = "还没有纪元",
|
||||
fontSize = 16.sp,
|
||||
color = Slate500
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Button(
|
||||
onClick = onCreateNew,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Slate900)
|
||||
) {
|
||||
Text("开启新纪元")
|
||||
epochs.isEmpty() -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = "还没有纪元",
|
||||
fontSize = 16.sp,
|
||||
color = Slate500
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Button(
|
||||
onClick = onCreateNew,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Slate900),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Text("开启新纪元", modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(bottom = 100.dp)
|
||||
) {
|
||||
items(epochs) { epoch ->
|
||||
EpochCard(
|
||||
epoch = epoch,
|
||||
onClick = { onEpochClick(epoch) }
|
||||
)
|
||||
else -> {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(bottom = 100.dp)
|
||||
) {
|
||||
items(epochs, key = { it.id }) { epoch ->
|
||||
EpochCard(
|
||||
epoch = epoch,
|
||||
onClick = { onEpochClick(epoch) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FAB
|
||||
// FAB - 右下角圆形按钮
|
||||
FloatingActionButton(
|
||||
onClick = onCreateNew,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(24.dp),
|
||||
.padding(end = 24.dp, bottom = 24.dp)
|
||||
.size(56.dp),
|
||||
containerColor = Slate900,
|
||||
contentColor = Color.White,
|
||||
shape = CircleShape
|
||||
shape = CircleShape,
|
||||
elevation = FloatingActionButtonDefaults.elevation(
|
||||
defaultElevation = 4.dp
|
||||
)
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = "创建纪元")
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
contentDescription = "创建纪元",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +153,7 @@ private fun EpochCard(
|
||||
val totalDays = ChronoUnit.DAYS.between(start, end).toFloat()
|
||||
val passedDays = ChronoUnit.DAYS.between(start, now).toFloat()
|
||||
(passedDays / totalDays).coerceIn(0f, 1f)
|
||||
} catch (e: Exception) {
|
||||
0f
|
||||
}
|
||||
} catch (e: Exception) { 0f }
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
@@ -150,30 +168,27 @@ private fun EpochCard(
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable { onClick() },
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Slate50),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||
color = Slate50
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
// 左侧绿色指示条
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(4.dp)
|
||||
.height(100.dp)
|
||||
.background(
|
||||
if (isActive) Brand500 else Slate300,
|
||||
RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp)
|
||||
)
|
||||
.height(108.dp)
|
||||
.background(if (isActive) Brand500 else Slate200)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(16.dp)
|
||||
.padding(start = 16.dp, end = 20.dp, top = 18.dp, bottom = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -187,16 +202,14 @@ private fun EpochCard(
|
||||
color = Slate900
|
||||
)
|
||||
|
||||
// 状态标签
|
||||
if (isCompleted) {
|
||||
Icon(
|
||||
when {
|
||||
isCompleted -> Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = "已完成",
|
||||
tint = Brand500,
|
||||
modifier = Modifier.size(24.dp)
|
||||
tint = Brand500.copy(alpha = 0.6f),
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
} else if (isActive) {
|
||||
Text(
|
||||
isActive -> Text(
|
||||
text = "进行中",
|
||||
fontSize = 14.sp,
|
||||
color = Brand500,
|
||||
@@ -205,15 +218,15 @@ private fun EpochCard(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Text(
|
||||
text = dateRange,
|
||||
fontSize = 14.sp,
|
||||
color = Slate500
|
||||
color = Slate400
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// 进度条
|
||||
Box(
|
||||
@@ -227,9 +240,7 @@ private fun EpochCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(progress)
|
||||
.fillMaxHeight()
|
||||
.background(
|
||||
if (isCompleted) Slate400 else Slate900
|
||||
)
|
||||
.background(Slate900)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ fun PlanScreen(
|
||||
onCreateEpoch: () -> Unit
|
||||
) {
|
||||
if (activeEpoch == null) {
|
||||
// 没有活跃纪元
|
||||
EmptyPlanState(onCreateEpoch = onCreateEpoch)
|
||||
return
|
||||
}
|
||||
@@ -42,24 +41,25 @@ fun PlanScreen(
|
||||
val currentYear = now.year
|
||||
val currentWeek = now.get(WeekFields.ISO.weekOfWeekBasedYear())
|
||||
|
||||
// 分离当前周和后续周
|
||||
// 后续周计划
|
||||
val futurePlans = weeklyPlans.filter { plan ->
|
||||
plan.plan.year > currentYear ||
|
||||
(plan.plan.year == currentYear && plan.plan.week > currentWeek)
|
||||
}.take(5) // 只显示接下来5周
|
||||
}.take(5)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 24.dp)
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
text = "计划中心",
|
||||
fontSize = 28.sp,
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Slate900
|
||||
)
|
||||
@@ -68,40 +68,38 @@ fun PlanScreen(
|
||||
text = "专注于本阶段的周计划",
|
||||
fontSize = 14.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
contentPadding = PaddingValues(bottom = 24.dp)
|
||||
) {
|
||||
// 正在进行中
|
||||
item {
|
||||
Text(
|
||||
text = "正在进行中",
|
||||
fontSize = 14.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
fontSize = 13.sp,
|
||||
color = Slate500
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
// 当前周计划卡片
|
||||
item {
|
||||
currentWeekPlan?.let { plan ->
|
||||
if (currentWeekPlan != null) {
|
||||
CurrentWeekCard(
|
||||
epoch = activeEpoch,
|
||||
plan = plan,
|
||||
epochDetail = epochDetail,
|
||||
onClick = { onPlanClick(plan) }
|
||||
plan = currentWeekPlan,
|
||||
onClick = { onPlanClick(currentWeekPlan) }
|
||||
)
|
||||
} ?: run {
|
||||
// 没有当前周计划
|
||||
Card(
|
||||
} else {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Slate50)
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = Slate50
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -109,10 +107,7 @@ fun PlanScreen(
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "本周暂无计划",
|
||||
color = Slate500
|
||||
)
|
||||
Text(text = "本周暂无计划", color = Slate500, fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,20 +116,17 @@ fun PlanScreen(
|
||||
// 后续序列
|
||||
if (futurePlans.isNotEmpty()) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Text(
|
||||
text = "后续序列",
|
||||
fontSize = 14.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
fontSize = 13.sp,
|
||||
color = Slate500
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
|
||||
items(futurePlans) { plan ->
|
||||
FuturePlanItem(
|
||||
plan = plan,
|
||||
onClick = { onPlanClick(plan) }
|
||||
)
|
||||
FuturePlanItem(plan = plan, onClick = { onPlanClick(plan) })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +138,8 @@ private fun EmptyPlanState(onCreateEpoch: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White),
|
||||
.background(Color.White)
|
||||
.statusBarsPadding(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
@@ -155,11 +148,11 @@ private fun EmptyPlanState(onCreateEpoch: () -> Unit) {
|
||||
) {
|
||||
Text(
|
||||
text = "还没有进行中的纪元",
|
||||
fontSize = 18.sp,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate700
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = "创建一个纪元来开始你的健康计划",
|
||||
fontSize = 14.sp,
|
||||
@@ -170,11 +163,12 @@ private fun EmptyPlanState(onCreateEpoch: () -> Unit) {
|
||||
onClick = onCreateEpoch,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Slate900),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
modifier = Modifier.fillMaxWidth(0.6f)
|
||||
modifier = Modifier.height(48.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "开启新纪元",
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -185,7 +179,6 @@ private fun EmptyPlanState(onCreateEpoch: () -> Unit) {
|
||||
private fun CurrentWeekCard(
|
||||
epoch: WeightEpoch,
|
||||
plan: WeeklyPlanDetail,
|
||||
epochDetail: EpochDetail?,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
// 计算周内天数
|
||||
@@ -218,32 +211,31 @@ private fun CurrentWeekCard(
|
||||
try {
|
||||
val epochStart = LocalDate.parse(epoch.startDate.take(10))
|
||||
val planStart = LocalDate.parse(plan.plan.startDate.take(10))
|
||||
val weeks = ChronoUnit.WEEKS.between(epochStart, planStart) + 1
|
||||
weeks.toInt()
|
||||
(ChronoUnit.WEEKS.between(epochStart, planStart) + 1).toInt()
|
||||
} catch (e: Exception) { plan.plan.week }
|
||||
}
|
||||
|
||||
Card(
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Slate50),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable { onClick() },
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = Slate50
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
// 左侧绿色指示条
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(4.dp)
|
||||
.height(120.dp)
|
||||
.background(Brand500, RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp))
|
||||
.height(110.dp)
|
||||
.background(Brand500)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(16.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -252,20 +244,20 @@ private fun CurrentWeekCard(
|
||||
) {
|
||||
Text(
|
||||
text = "${epoch.name} - 第 $weekIndex 周",
|
||||
fontSize = 18.sp,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Slate900
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "第 $dayInWeek 天",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = Brand500,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
// 目标信息
|
||||
val targetText = buildString {
|
||||
@@ -278,18 +270,18 @@ private fun CurrentWeekCard(
|
||||
}
|
||||
Text(
|
||||
text = targetText,
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = Slate600
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
|
||||
// 进度条
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp)
|
||||
.clip(RoundedCornerShape(3.dp))
|
||||
.height(5.dp)
|
||||
.clip(RoundedCornerShape(2.5.dp))
|
||||
.background(Slate200)
|
||||
) {
|
||||
Box(
|
||||
@@ -309,48 +301,46 @@ private fun FuturePlanItem(
|
||||
plan: WeeklyPlanDetail,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
// 格式化日期
|
||||
val dateRange = remember(plan) {
|
||||
try {
|
||||
val start = LocalDate.parse(plan.plan.startDate.take(10))
|
||||
val end = LocalDate.parse(plan.plan.endDate.take(10))
|
||||
val formatter = DateTimeFormatter.ofPattern("MM.dd")
|
||||
"${start.format(formatter)} - ${end.format(formatter)}"
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
} catch (e: Exception) { "" }
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.padding(vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "第 ${plan.plan.week} 周",
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate800
|
||||
)
|
||||
Text(
|
||||
text = dateRange,
|
||||
fontSize = 13.sp,
|
||||
color = Slate500
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "第 ${plan.plan.week} 周",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate800
|
||||
)
|
||||
Text(
|
||||
text = dateRange,
|
||||
fontSize = 14.sp,
|
||||
color = Slate500
|
||||
text = "待启动",
|
||||
fontSize = 13.sp,
|
||||
color = Slate400
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "待启动",
|
||||
fontSize = 14.sp,
|
||||
color = Slate400
|
||||
)
|
||||
HorizontalDivider(color = Slate100, thickness = 1.dp)
|
||||
}
|
||||
|
||||
HorizontalDivider(color = Slate100)
|
||||
}
|
||||
|
||||
private fun formatWeight(weight: Double): String {
|
||||
|
||||
@@ -31,28 +31,27 @@ fun ProfileScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 24.dp)
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
text = "个人中心",
|
||||
fontSize = 28.sp,
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Slate900
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
|
||||
// 用户信息
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
// 头像
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(72.dp)
|
||||
.size(64.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Slate100)
|
||||
) {
|
||||
@@ -64,14 +63,13 @@ fun ProfileScreen(
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
// 默认头像占位
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = user?.nickname?.firstOrNull()?.toString() ?: "U",
|
||||
fontSize = 28.sp,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate400
|
||||
)
|
||||
@@ -79,29 +77,29 @@ fun ProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Spacer(modifier = Modifier.width(14.dp))
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = user?.nickname ?: "用户",
|
||||
fontSize = 22.sp,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Slate900
|
||||
)
|
||||
Text(
|
||||
text = "坚持 ${stats.persistDays} 天",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = Slate500
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 统计卡片
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
StatCard(
|
||||
title = "今年减重",
|
||||
@@ -119,7 +117,7 @@ fun ProfileScreen(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 年度进度卡片
|
||||
YearProgressCard(daysRemaining = stats.daysRemaining)
|
||||
@@ -134,13 +132,13 @@ fun ProfileScreen(
|
||||
Text(
|
||||
text = "退出登录",
|
||||
color = ErrorRed,
|
||||
fontSize = 16.sp
|
||||
fontSize = 15.sp
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(color = Slate100)
|
||||
HorizontalDivider(color = Slate100, thickness = 1.dp)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,36 +150,31 @@ private fun StatCard(
|
||||
valueColor: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Slate50),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = Slate50
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(14.dp)) {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = Slate500
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text(
|
||||
text = value,
|
||||
fontSize = 28.sp,
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = valueColor
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Spacer(modifier = Modifier.width(3.dp))
|
||||
Text(
|
||||
text = unit,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 14.sp,
|
||||
color = valueColor,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
modifier = Modifier.padding(bottom = 3.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -192,56 +185,51 @@ private fun StatCard(
|
||||
private fun YearProgressCard(daysRemaining: Int) {
|
||||
val currentYear = LocalDate.now().year
|
||||
|
||||
Card(
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Navy900),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = Navy900
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
// 右上角年份标签
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(12.dp),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
.padding(10.dp),
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
color = Slate700
|
||||
) {
|
||||
Text(
|
||||
text = "$currentYear PROGRESS",
|
||||
fontSize = 12.sp,
|
||||
fontSize = 11.sp,
|
||||
color = Slate300,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(24.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(20.dp)) {
|
||||
Text(
|
||||
text = "今年剩余天数",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = Slate400
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text(
|
||||
text = String.format("%02d", daysRemaining),
|
||||
fontSize = 56.sp,
|
||||
fontSize = 52.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "DAYS",
|
||||
fontSize = 20.sp,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate400,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
modifier = Modifier.padding(bottom = 10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.healthflow.app.data.model.WeeklyPlanDetail
|
||||
@@ -29,7 +28,9 @@ fun WeekPlanDetailScreen(
|
||||
) {
|
||||
if (planDetail == null) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(color = Brand500)
|
||||
@@ -41,42 +42,44 @@ fun WeekPlanDetailScreen(
|
||||
var showWeightDialog by remember { mutableStateOf(false) }
|
||||
var showTargetDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// 计算周序号(简化处理)
|
||||
val weekIndex = plan.week
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.White)
|
||||
.padding(horizontal = 24.dp)
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 返回按钮
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable(onClick = onBack)
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onBack)
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
tint = Slate600,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "返回",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
color = Slate600
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// 标题
|
||||
Text(
|
||||
text = "第${weekIndex}周计划",
|
||||
fontSize = 28.sp,
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Slate900
|
||||
)
|
||||
@@ -85,7 +88,7 @@ fun WeekPlanDetailScreen(
|
||||
text = "正在为了本周目标努力",
|
||||
fontSize = 14.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
@@ -95,21 +98,19 @@ fun WeekPlanDetailScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text(
|
||||
text = plan.finalWeight?.let { formatWeight(it) } ?: "--",
|
||||
fontSize = 72.sp,
|
||||
fontSize = 64.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Slate900
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "kg",
|
||||
fontSize = 24.sp,
|
||||
fontSize = 22.sp,
|
||||
color = Slate500,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
modifier = Modifier.padding(bottom = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,38 +121,37 @@ fun WeekPlanDetailScreen(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
|
||||
// 操作按钮
|
||||
Button(
|
||||
onClick = { showWeightDialog = true },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Slate900)
|
||||
) {
|
||||
Text(
|
||||
text = "添加体重记录",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { showTargetDialog = true },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = Slate900),
|
||||
border = ButtonDefaults.outlinedButtonBorder.copy(width = 1.dp)
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = Slate900)
|
||||
) {
|
||||
Text(
|
||||
text = "修改周目标",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
@@ -161,50 +161,50 @@ fun WeekPlanDetailScreen(
|
||||
// 本周指标
|
||||
Text(
|
||||
text = "本周指标",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = Slate500
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 周初始
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 12.dp),
|
||||
.padding(vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "周初始",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate800
|
||||
)
|
||||
Text(
|
||||
text = plan.initialWeight?.let { "${formatWeight(it)} kg" } ?: "--",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
color = Slate600
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(color = Slate100)
|
||||
HorizontalDivider(color = Slate100, thickness = 1.dp)
|
||||
|
||||
// 周目标
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 12.dp),
|
||||
.padding(vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "周目标",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Slate800
|
||||
)
|
||||
Text(
|
||||
text = plan.targetWeight?.let { "${formatWeight(it)} kg" } ?: "--",
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
color = Slate600
|
||||
)
|
||||
}
|
||||
@@ -249,10 +249,7 @@ private fun WeightInputDialog(
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = title,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(text = title, fontWeight = FontWeight.SemiBold, fontSize = 17.sp)
|
||||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
@@ -261,24 +258,24 @@ private fun WeightInputDialog(
|
||||
label = { Text("体重 (kg)") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
weightText.toDoubleOrNull()?.let { onConfirm(it) }
|
||||
},
|
||||
onClick = { weightText.toDoubleOrNull()?.let { onConfirm(it) } },
|
||||
enabled = weightText.toDoubleOrNull() != null
|
||||
) {
|
||||
Text("确定", color = Brand500)
|
||||
Text("确定", color = Brand500, fontSize = 15.sp)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("取消", color = Slate500)
|
||||
Text("取消", color = Slate500, fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -295,10 +292,7 @@ private fun TargetEditDialog(
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = "修改周目标",
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(text = "修改周目标", fontWeight = FontWeight.SemiBold, fontSize = 17.sp)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
@@ -308,36 +302,36 @@ private fun TargetEditDialog(
|
||||
label = { Text("周初始体重 (kg)") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
OutlinedTextField(
|
||||
value = targetText,
|
||||
onValueChange = { targetText = it },
|
||||
label = { Text("周目标体重 (kg)") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onConfirm(
|
||||
initialText.toDoubleOrNull(),
|
||||
targetText.toDoubleOrNull()
|
||||
)
|
||||
onConfirm(initialText.toDoubleOrNull(), targetText.toDoubleOrNull())
|
||||
}
|
||||
) {
|
||||
Text("确定", color = Brand500)
|
||||
Text("确定", color = Brand500, fontSize = 15.sp)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("取消", color = Slate500)
|
||||
Text("取消", color = Slate500, fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user