|
|
@ -1,58 +1,183 @@ |
|
|
|
package com.whitefish.app.ui.home.exercise |
|
|
|
|
|
|
|
import androidx.compose.foundation.Canvas |
|
|
|
import androidx.compose.foundation.background |
|
|
|
import androidx.compose.foundation.layout.* |
|
|
|
import androidx.compose.foundation.lazy.LazyColumn |
|
|
|
import androidx.compose.foundation.lazy.items |
|
|
|
import androidx.compose.foundation.lazy.LazyRow |
|
|
|
import androidx.compose.foundation.shape.CircleShape |
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape |
|
|
|
import androidx.compose.material3.* |
|
|
|
import androidx.compose.runtime.* |
|
|
|
import androidx.compose.ui.Alignment |
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
import androidx.compose.ui.geometry.Offset |
|
|
|
import androidx.compose.ui.geometry.Size |
|
|
|
import androidx.compose.ui.graphics.Brush |
|
|
|
import androidx.compose.ui.graphics.Color |
|
|
|
import androidx.compose.ui.graphics.StrokeCap |
|
|
|
import androidx.compose.ui.graphics.drawscope.Stroke |
|
|
|
import androidx.compose.ui.text.font.FontWeight |
|
|
|
import androidx.compose.ui.text.style.TextAlign |
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
import androidx.compose.ui.unit.sp |
|
|
|
import androidx.lifecycle.viewmodel.compose.viewModel |
|
|
|
import org.jetbrains.compose.ui.tooling.preview.Preview |
|
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class) |
|
|
|
@Composable |
|
|
|
fun ExerciseScreen( |
|
|
|
modifier: Modifier = Modifier, |
|
|
|
viewModel: ExerciseViewModel = viewModel { ExerciseViewModel() } |
|
|
|
) { |
|
|
|
val uiState by viewModel.uiState.collectAsState() |
|
|
|
|
|
|
|
fun ExerciseScreen() { |
|
|
|
LazyColumn( |
|
|
|
modifier = modifier |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxSize() |
|
|
|
.background(Color(0xFF1A1A1A)) |
|
|
|
.padding(horizontal = 16.dp), |
|
|
|
contentPadding = PaddingValues(vertical = 16.dp), |
|
|
|
verticalArrangement = Arrangement.spacedBy(12.dp) |
|
|
|
.padding(horizontal = 10.dp), |
|
|
|
verticalArrangement = Arrangement.spacedBy(16.dp) |
|
|
|
) { |
|
|
|
items(uiState.exerciseData) { exercise -> |
|
|
|
ExerciseCard(exercise = exercise) |
|
|
|
item { |
|
|
|
// 用户问候区域 |
|
|
|
UserGreetingSection() |
|
|
|
} |
|
|
|
|
|
|
|
item { |
|
|
|
// 运动类型卡片 |
|
|
|
ExerciseTypeCards() |
|
|
|
} |
|
|
|
|
|
|
|
item { |
|
|
|
// 添加运动记录按钮 |
|
|
|
AddExerciseButton() |
|
|
|
} |
|
|
|
|
|
|
|
item { |
|
|
|
// 运动记录列表 |
|
|
|
ExerciseRecordsSection() |
|
|
|
} |
|
|
|
|
|
|
|
item { |
|
|
|
// 综合评分 |
|
|
|
ComprehensiveScoreSection() |
|
|
|
} |
|
|
|
|
|
|
|
item { |
|
|
|
// 解读和建议 |
|
|
|
InsightSection() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun UserGreetingSection() { |
|
|
|
Column { |
|
|
|
Text( |
|
|
|
text = "用户名", |
|
|
|
fontSize = 18.sp, |
|
|
|
color = Color.White, |
|
|
|
fontWeight = FontWeight.Medium |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
|
Text( |
|
|
|
text = "准备好开始运动了吗?", |
|
|
|
fontSize = 24.sp, |
|
|
|
color = Color.White, |
|
|
|
fontWeight = FontWeight.Bold |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(8.dp)) |
|
|
|
Text( |
|
|
|
text = "今天你已经坚持了152天了,当当目日标达了98千卡!", |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color.White.copy(alpha = 0.8f) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
private fun ExerciseCard( |
|
|
|
exercise: ExerciseData, |
|
|
|
fun ExerciseTypeCards() { |
|
|
|
LazyRow( |
|
|
|
horizontalArrangement = Arrangement.spacedBy(12.dp) |
|
|
|
) { |
|
|
|
item { |
|
|
|
ExerciseTypeCard( |
|
|
|
title = "跑步", |
|
|
|
gradient = listOf(Color(0xFF4FC3F7), Color(0xFF29B6F6)), |
|
|
|
modifier = Modifier.size(160.dp, 120.dp) |
|
|
|
) |
|
|
|
} |
|
|
|
item { |
|
|
|
ExerciseTypeCard( |
|
|
|
title = "骑行", |
|
|
|
gradient = listOf(Color(0xFFFFB74D), Color(0xFFFF9800)), |
|
|
|
modifier = Modifier.size(160.dp, 120.dp) |
|
|
|
) |
|
|
|
} |
|
|
|
item { |
|
|
|
ExerciseTypeCard( |
|
|
|
title = "游泳", |
|
|
|
gradient = listOf(Color(0xFF4DD0E1), Color(0xFF00BCD4)), |
|
|
|
modifier = Modifier.size(160.dp, 120.dp) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun ExerciseTypeCard( |
|
|
|
title: String, |
|
|
|
gradient: List<Color>, |
|
|
|
modifier: Modifier = Modifier |
|
|
|
) { |
|
|
|
Card( |
|
|
|
modifier = modifier.fillMaxWidth(), |
|
|
|
shape = RoundedCornerShape(12.dp), |
|
|
|
colors = CardDefaults.cardColors( |
|
|
|
containerColor = Color(0xFF2A2A2A) |
|
|
|
) |
|
|
|
modifier = modifier, |
|
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
colors = CardDefaults.cardColors(Color.Transparent) |
|
|
|
) { |
|
|
|
Column( |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.padding(16.dp) |
|
|
|
.fillMaxSize() |
|
|
|
.background( |
|
|
|
Brush.linearGradient(gradient), |
|
|
|
RoundedCornerShape(16.dp) |
|
|
|
), |
|
|
|
contentAlignment = Alignment.Center |
|
|
|
) { |
|
|
|
Text( |
|
|
|
text = title, |
|
|
|
fontSize = 18.sp, |
|
|
|
color = Color.White, |
|
|
|
fontWeight = FontWeight.Bold |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun AddExerciseButton() { |
|
|
|
Card( |
|
|
|
onClick = { }, |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.height(56.dp), |
|
|
|
colors = CardDefaults.cardColors(containerColor = Color(0xff352764)), |
|
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
elevation = CardDefaults.cardElevation(10.dp) |
|
|
|
) { |
|
|
|
Row(modifier = Modifier.fillMaxSize(),verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) { |
|
|
|
Text( |
|
|
|
text = "+ 添加运动记录", |
|
|
|
fontSize = 16.sp, |
|
|
|
color = Color.White, |
|
|
|
fontWeight = FontWeight.Medium |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun ExerciseRecordsSection() { |
|
|
|
Card( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
colors = CardDefaults.cardColors(Color.White.copy(alpha = 0.9f)) |
|
|
|
) { |
|
|
|
Column( |
|
|
|
modifier = Modifier.padding(16.dp) |
|
|
|
) { |
|
|
|
Row( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
@ -60,35 +185,159 @@ private fun ExerciseCard( |
|
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
|
) { |
|
|
|
Text( |
|
|
|
text = exercise.type, |
|
|
|
color = Color.White, |
|
|
|
text = "运动记录", |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Medium |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = Color(0xFF666666) |
|
|
|
) |
|
|
|
Text( |
|
|
|
text = exercise.date, |
|
|
|
color = Color.Gray, |
|
|
|
fontSize = 12.sp |
|
|
|
text = "查看更多", |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color(0xFF9C27B0) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(8.dp)) |
|
|
|
|
|
|
|
Row( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
horizontalArrangement = Arrangement.SpaceBetween |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(16.dp)) |
|
|
|
|
|
|
|
// 户外跑步记录 |
|
|
|
ExerciseRecordItem( |
|
|
|
iconColor = Color(0xFF2196F3), |
|
|
|
title = "户外跑步", |
|
|
|
distance = "2.85公里", |
|
|
|
time = "00:20:36", |
|
|
|
pace = "7'59\"公里" |
|
|
|
) |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(12.dp)) |
|
|
|
|
|
|
|
// 自由训练记录 |
|
|
|
ExerciseRecordItem( |
|
|
|
iconColor = Color(0xFF4CAF50), |
|
|
|
title = "自由训练", |
|
|
|
distance = "161千卡", |
|
|
|
time = "00:57:06", |
|
|
|
pace = "104次/分钟" |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun ExerciseRecordItem( |
|
|
|
iconColor: Color, |
|
|
|
title: String, |
|
|
|
distance: String, |
|
|
|
time: String, |
|
|
|
pace: String |
|
|
|
) { |
|
|
|
Row( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
|
) { |
|
|
|
// 运动图标占位 |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.size(40.dp) |
|
|
|
.background(iconColor, CircleShape), |
|
|
|
contentAlignment = Alignment.Center |
|
|
|
) { |
|
|
|
Text( |
|
|
|
text = title.first().toString(), |
|
|
|
color = Color.White, |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Bold |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(12.dp)) |
|
|
|
|
|
|
|
Column(modifier = Modifier.weight(1f)) { |
|
|
|
Text( |
|
|
|
text = title, |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Medium, |
|
|
|
color = Color(0xFF333333) |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
|
Row { |
|
|
|
Text( |
|
|
|
text = distance, |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color(0xFF666666) |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.width(16.dp)) |
|
|
|
Text( |
|
|
|
text = time, |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color(0xFF666666) |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.width(16.dp)) |
|
|
|
Text( |
|
|
|
text = pace, |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color(0xFF666666) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun ComprehensiveScoreSection() { |
|
|
|
Card( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
colors = CardDefaults.cardColors(Color.White.copy(alpha = 0.9f)) |
|
|
|
) { |
|
|
|
Column( |
|
|
|
modifier = Modifier.padding(16.dp) |
|
|
|
) { |
|
|
|
Text( |
|
|
|
text = "综合评分", |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = Color(0xFF333333) |
|
|
|
) |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(20.dp)) |
|
|
|
|
|
|
|
// 圆形评分图表 |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.height(200.dp), |
|
|
|
contentAlignment = Alignment.Center |
|
|
|
) { |
|
|
|
ExerciseMetric( |
|
|
|
label = "时长", |
|
|
|
value = exercise.duration |
|
|
|
CircularScoreChart(score = 75) |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(20.dp)) |
|
|
|
|
|
|
|
// 评估指标 |
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { |
|
|
|
ScoreIndicator( |
|
|
|
label = "活动小时数得分", |
|
|
|
value = "佳", |
|
|
|
progress = 0.8f, |
|
|
|
color = Color(0xFFFF6B6B) |
|
|
|
) |
|
|
|
ScoreIndicator( |
|
|
|
label = "有氧活动评估", |
|
|
|
value = "佳", |
|
|
|
progress = 0.85f, |
|
|
|
color = Color(0xFF4ECDC4) |
|
|
|
) |
|
|
|
ExerciseMetric( |
|
|
|
label = "卡路里", |
|
|
|
value = "${exercise.calories} kcal" |
|
|
|
ScoreIndicator( |
|
|
|
label = "运动强度审评估", |
|
|
|
value = "优", |
|
|
|
progress = 0.95f, |
|
|
|
color = Color(0xFF45B7D1) |
|
|
|
) |
|
|
|
ExerciseMetric( |
|
|
|
label = "距离", |
|
|
|
value = "${exercise.distance} km" |
|
|
|
ScoreIndicator( |
|
|
|
label = "运动强度效率评估", |
|
|
|
value = "优", |
|
|
|
progress = 0.9f, |
|
|
|
color = Color(0xFF96CEB4) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
@ -96,39 +345,119 @@ private fun ExerciseCard( |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
private fun ExerciseMetric( |
|
|
|
fun CircularScoreChart(score: Int) { |
|
|
|
Box(contentAlignment = Alignment.Center) { |
|
|
|
Canvas(modifier = Modifier.size(120.dp)) { |
|
|
|
val strokeWidth = 12.dp.toPx() |
|
|
|
val radius = (size.minDimension - strokeWidth) / 2 |
|
|
|
val center = Offset(size.width / 2, size.height / 2) |
|
|
|
|
|
|
|
// 背景圆 |
|
|
|
drawCircle( |
|
|
|
color = Color(0xFFE0E0E0), |
|
|
|
radius = radius, |
|
|
|
center = center, |
|
|
|
style = Stroke(strokeWidth) |
|
|
|
) |
|
|
|
|
|
|
|
// 进度圆弧 |
|
|
|
val sweepAngle = (score / 100f) * 360f |
|
|
|
drawArc( |
|
|
|
color = Color(0xFF5E35B1), |
|
|
|
startAngle = -90f, |
|
|
|
sweepAngle = sweepAngle, |
|
|
|
useCenter = false, |
|
|
|
style = Stroke(strokeWidth, cap = StrokeCap.Round), |
|
|
|
topLeft = Offset(center.x - radius, center.y - radius), |
|
|
|
size = Size(radius * 2, radius * 2) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
Column(horizontalAlignment = Alignment.CenterHorizontally) { |
|
|
|
Text( |
|
|
|
text = score.toString(), |
|
|
|
fontSize = 36.sp, |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = Color(0xFF333333) |
|
|
|
) |
|
|
|
Text( |
|
|
|
text = "运动得分", |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color(0xFF666666) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun ScoreIndicator( |
|
|
|
label: String, |
|
|
|
value: String |
|
|
|
value: String, |
|
|
|
progress: Float, |
|
|
|
color: Color |
|
|
|
) { |
|
|
|
Column( |
|
|
|
horizontalAlignment = Alignment.CenterHorizontally |
|
|
|
Row( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
|
) { |
|
|
|
Column(modifier = Modifier.weight(1f)) { |
|
|
|
Text( |
|
|
|
text = label, |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color(0xFF666666) |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.height(6.dp) |
|
|
|
.background(Color(0xFFE0E0E0), RoundedCornerShape(3.dp)) |
|
|
|
) { |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth(progress) |
|
|
|
.fillMaxHeight() |
|
|
|
.background(color, RoundedCornerShape(3.dp)) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(16.dp)) |
|
|
|
|
|
|
|
Text( |
|
|
|
text = value, |
|
|
|
color = Color.White, |
|
|
|
fontSize = 14.sp, |
|
|
|
fontWeight = FontWeight.Bold |
|
|
|
) |
|
|
|
Text( |
|
|
|
text = label, |
|
|
|
color = Color.Gray, |
|
|
|
fontSize = 12.sp |
|
|
|
fontWeight = FontWeight.Medium, |
|
|
|
color = color |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
data class ExerciseData( |
|
|
|
val type: String, |
|
|
|
val date: String, |
|
|
|
val duration: String, |
|
|
|
val calories: Int, |
|
|
|
val distance: Float |
|
|
|
) |
|
|
|
|
|
|
|
@Preview |
|
|
|
@Composable |
|
|
|
private fun ExerciseScreenPreview() { |
|
|
|
MaterialTheme { |
|
|
|
ExerciseScreen() |
|
|
|
fun InsightSection() { |
|
|
|
Card( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
colors = CardDefaults.cardColors(Color(0xFF5E35B1)) |
|
|
|
) { |
|
|
|
Column( |
|
|
|
modifier = Modifier.padding(16.dp) |
|
|
|
) { |
|
|
|
Text( |
|
|
|
text = "解读和建议", |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = Color.White |
|
|
|
) |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(12.dp)) |
|
|
|
|
|
|
|
Text( |
|
|
|
text = "对于初跑者,建议采用MAF180训练法或储备心率百分比的区间,最大心率百分比的Z3区间进行慢跑或其他有氧跑练,这种低强度的运动有助于减重或提高储,同时能够较好地保持较长时间的有氧运动状态,高步频可以降低足部水平承受伤并提升跑步效率,高步频可以降低减重或不适,使可以说一些话的跑步状态。在增大跑量阶段跑者可承受的压力,增加落地的次数,在跑时过程中更容易驾驭承受伤并提升跑速度。", |
|
|
|
fontSize = 14.sp, |
|
|
|
color = Color.White.copy(alpha = 0.9f), |
|
|
|
lineHeight = 20.sp |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |