Browse Source

fix: style fix

main
AnranYus 1 month ago
parent
commit
54bb2c8a0c
  1. 165
      SystemBars_README.md
  2. 50
      composeApp/src/androidMain/kotlin/com/whitefish/app/Platform.android.kt
  3. 9
      composeApp/src/commonMain/kotlin/com/whitefish/app/Platform.kt
  4. 74
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/components/SystemBarsDemo.kt
  5. 65
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/components/SystemBarsPadding.kt
  6. 12
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/HomeScreen.kt
  7. 457
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/exercise/ExerciseScreen.kt
  8. 66
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/exercise/ExerciseViewModel.kt
  9. 2
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/state/components/ExerciseGoalCard.kt
  10. 43
      composeApp/src/iosMain/kotlin/com/whitefish/app/Platform.ios.kt
  11. 65
      iosApp/iosApp/ContentView.swift
  12. 13
      iosApp/iosApp/Info.plist
  13. 20
      iosApp/iosApp/StatusBarConfig.swift
  14. 4
      iosApp/iosApp/iOSApp.swift

165
SystemBars_README.md

@ -0,0 +1,165 @@
# iOS 透明状态栏和导航栏实现
## 概述
本项目成功实现了iOS应用的透明状态栏和导航栏效果,让应用背景图片能够延伸到状态栏和导航栏区域,同时提供了动态获取系统栏高度的功能,确保内容不被遮挡。
## 实现的功能
### 1. 透明状态栏和导航栏
- ✅ iOS状态栏完全透明
- ✅ 内容延伸到状态栏区域
- ✅ 状态栏文字为白色(适合深色背景)
- ✅ 底部安全区域处理
### 2. 动态高度获取
- ✅ Android平台:动态获取真实的状态栏和导航栏高度
- ✅ iOS平台:根据设备类型返回合适的高度值
- ✅ 跨平台统一API
### 3. Compose组件支持
- ✅ `systemBarsPadding()` - 添加状态栏和导航栏内边距
- ✅ `statusBarsPadding()` - 只添加状态栏内边距
- ✅ `navigationBarsPadding()` - 只添加导航栏内边距
- ✅ `StatusBarSpacer` - 状态栏空白组件
- ✅ `NavigationBarSpacer` - 导航栏空白组件
## 使用方法
### 1. 基本用法
```kotlin
@Composable
fun MyScreen() {
Box(modifier = Modifier.fillMaxSize()) {
// 背景图片,延伸到整个屏幕
Image(
painter = painterResource(Res.drawable.background),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
// 内容区域,添加系统栏内边距
Column(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding() // 关键:添加系统栏内边距
) {
// 您的内容
Text("这里的内容不会被状态栏遮挡")
}
}
}
```
### 2. 只处理状态栏
```kotlin
@Composable
fun MyTopBar() {
Row(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding() // 只添加状态栏内边距
.padding(horizontal = 16.dp)
) {
// 顶部栏内容
}
}
```
### 3. 使用空白组件
```kotlin
@Composable
fun MyContent() {
Column {
StatusBarSpacer() // 状态栏高度的空白
// 您的内容
Text("内容从这里开始")
Spacer(modifier = Modifier.weight(1f))
NavigationBarSpacer() // 导航栏高度的空白
}
}
```
### 4. 获取高度值
```kotlin
@Composable
fun MyComponent() {
val statusBarHeight = getStatusBarHeight()
val navigationBarHeight = getNavigationBarHeight()
Text("状态栏高度: $statusBarHeight")
Text("导航栏高度: $navigationBarHeight")
}
```
## 平台特定实现
### Android
- 使用系统资源获取真实的状态栏和导航栏高度
- 支持不同屏幕密度的自动适配
- 兼容现有的`ImmersionBar`透明状态栏设置
### iOS
- 状态栏高度:iPhone 44dp,iPad 24dp
- 导航栏高度:iPhone 34dp(有Home指示器),iPad 0dp
- 在Swift层实现透明状态栏效果
## 文件结构
```
composeApp/src/
├── commonMain/kotlin/com/whitefish/app/
│ ├── Platform.kt # 平台接口定义
│ └── ui/components/
│ └── SystemBarsPadding.kt # 系统栏内边距组件
├── androidMain/kotlin/com/whitefish/app/
│ └── Platform.android.kt # Android平台实现
└── iosMain/kotlin/com/whitefish/app/
└── Platform.ios.kt # iOS平台实现
iosApp/iosApp/
├── ContentView.swift # iOS主视图
├── StatusBarConfig.swift # 状态栏配置
├── iOSApp.swift # iOS应用入口
└── Info.plist # iOS配置文件
```
## 技术要点
### 1. iOS透明状态栏实现
- 使用自定义`UIViewController`控制状态栏外观
- 设置`preferredStatusBarStyle = .lightContent`
- 配置视图背景为透明
- 内容延伸到所有边缘
### 2. 高度获取策略
- Android:使用系统资源动态获取
- iOS:基于设备类型的预设值
- 提供fallback机制确保稳定性
### 3. Compose集成
- 使用`expect/actual`机制实现跨平台API
- 提供Modifier扩展函数简化使用
- 支持组合式和声明式两种使用方式
## 注意事项
1. **iOS设备适配**:当前iOS实现使用预设值,未来可以考虑使用更精确的动态获取方式
2. **Android兼容性**:与现有的`StatusBarView`组件兼容,可以逐步迁移
3. **性能考虑**:高度获取在Compose中会被缓存,不会重复计算
## 示例效果
使用这些组件后,您的应用将实现:
- 背景图片延伸到状态栏,营造沉浸式体验
- 内容区域正确避开系统栏,不被遮挡
- iOS和Android平台效果一致
- 代码简洁,易于维护

50
composeApp/src/androidMain/kotlin/com/whitefish/app/Platform.android.kt

@ -1,9 +1,59 @@
package com.whitefish.app package com.whitefish.app
import android.os.Build import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
import android.content.Context
import android.view.WindowInsets
import androidx.core.view.WindowInsetsCompat
class AndroidPlatform : Platform { class AndroidPlatform : Platform {
override val name: String = "Android ${Build.VERSION.SDK_INT}" override val name: String = "Android ${Build.VERSION.SDK_INT}"
} }
actual fun getPlatform(): Platform = AndroidPlatform() actual fun getPlatform(): Platform = AndroidPlatform()
// Android状态栏高度
@Composable
actual fun getStatusBarHeight(): Dp {
val context = LocalContext.current
val density = LocalDensity.current
return with(density) {
getStatusBarHeightPx(context).toDp()
}
}
// Android导航栏高度
@Composable
actual fun getNavigationBarHeight(): Dp {
val context = LocalContext.current
val density = LocalDensity.current
return with(density) {
getNavigationBarHeightPx(context).toDp()
}
}
// 获取状态栏高度(像素)
private fun getStatusBarHeightPx(context: Context): Int {
var statusBarHeight = 0
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
statusBarHeight = context.resources.getDimensionPixelSize(resourceId)
}
return statusBarHeight
}
// 获取导航栏高度(像素)
private fun getNavigationBarHeightPx(context: Context): Int {
var navigationBarHeight = 0
val resourceId = context.resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
navigationBarHeight = context.resources.getDimensionPixelSize(resourceId)
}
return navigationBarHeight
}

9
composeApp/src/commonMain/kotlin/com/whitefish/app/Platform.kt

@ -1,7 +1,16 @@
package com.whitefish.app package com.whitefish.app
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
interface Platform { interface Platform {
val name: String val name: String
} }
expect fun getPlatform(): Platform expect fun getPlatform(): Platform
@Composable
expect fun getStatusBarHeight(): Dp
@Composable
expect fun getNavigationBarHeight(): Dp

74
composeApp/src/commonMain/kotlin/com/whitefish/app/ui/components/SystemBarsDemo.kt

@ -0,0 +1,74 @@
package com.whitefish.app.ui.components
import androidx.compose.foundation.background
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.whitefish.app.getStatusBarHeight
import com.whitefish.app.getNavigationBarHeight
/**
*
*
*/
@Composable
fun SystemBarsDemo(modifier: Modifier = Modifier) {
val statusBarHeight = getStatusBarHeight()
val navigationBarHeight = getNavigationBarHeight()
Column(
modifier = modifier
.fillMaxSize()
.background(Color(0xFF1A1A1A))
.systemBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Card(
modifier = Modifier.padding(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A))
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "系统栏高度信息",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "状态栏高度: ${statusBarHeight}",
fontSize = 16.sp,
color = Color(0xFFE0E0E0)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "导航栏高度: ${navigationBarHeight}",
fontSize = 16.sp,
color = Color(0xFFE0E0E0)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "此内容区域已添加系统栏内边距",
fontSize = 14.sp,
color = Color(0xFFB0B0B0)
)
}
}
}
}

65
composeApp/src/commonMain/kotlin/com/whitefish/app/ui/components/SystemBarsPadding.kt

@ -0,0 +1,65 @@
package com.whitefish.app.ui.components
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.whitefish.app.getStatusBarHeight
import com.whitefish.app.getNavigationBarHeight
/**
*
*
*/
@Composable
fun Modifier.systemBarsPadding(): Modifier {
val statusBarHeight: Dp = getStatusBarHeight()
val navigationBarHeight: Dp = getNavigationBarHeight()
return this.padding(
top = statusBarHeight,
bottom = navigationBarHeight
)
}
/**
*
*/
@Composable
fun Modifier.statusBarsPadding(): Modifier {
val statusBarHeight: Dp = getStatusBarHeight()
return this.padding(top = statusBarHeight)
}
/**
*
*/
@Composable
fun Modifier.navigationBarsPadding(): Modifier {
val navigationBarHeight: Dp = getNavigationBarHeight()
return this.padding(bottom = navigationBarHeight)
}
/**
*
*/
@Composable
fun StatusBarSpacer(modifier: Modifier = Modifier) {
val statusBarHeight: Dp = getStatusBarHeight()
Spacer(
modifier = modifier.height(height = statusBarHeight)
)
}
/**
*
*/
@Composable
fun NavigationBarSpacer(modifier: Modifier = Modifier) {
val navigationBarHeight: Dp = getNavigationBarHeight()
Spacer(
modifier = modifier.height(height = navigationBarHeight)
)
}

12
composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/HomeScreen.kt

@ -24,6 +24,9 @@ import com.whitefish.app.ui.home.state.StateScreen
import com.whitefish.app.ui.home.exercise.ExerciseScreen import com.whitefish.app.ui.home.exercise.ExerciseScreen
import com.whitefish.app.ui.home.recovery.RecoveryScreen import com.whitefish.app.ui.home.recovery.RecoveryScreen
import com.whitefish.app.ui.home.setting.SettingScreen import com.whitefish.app.ui.home.setting.SettingScreen
import com.whitefish.app.ui.components.systemBarsPadding
import com.whitefish.app.ui.components.statusBarsPadding
import com.whitefish.app.ui.components.navigationBarsPadding
import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.DrawableResource
import ringappkmp.composeapp.generated.resources.bg import ringappkmp.composeapp.generated.resources.bg
import ringappkmp.composeapp.generated.resources.ic_nav_exercise import ringappkmp.composeapp.generated.resources.ic_nav_exercise
@ -39,6 +42,7 @@ fun HomeScreen(
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
Box(modifier = modifier.fillMaxSize()) { Box(modifier = modifier.fillMaxSize()) {
// 背景图片,延伸到整个屏幕(包括状态栏和导航栏区域)
Image( Image(
painter = painterResource(Res.drawable.bg), painter = painterResource(Res.drawable.bg),
contentDescription = null, contentDescription = null,
@ -46,11 +50,13 @@ fun HomeScreen(
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
// 内容区域,添加系统栏内边距
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.systemBarsPadding() // 添加状态栏和导航栏的内边距
) { ) {
// 主内容区域
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@ -69,17 +75,15 @@ fun HomeScreen(
selectedTab = uiState.selectedTab, selectedTab = uiState.selectedTab,
onTabSelected = viewModel::selectTab onTabSelected = viewModel::selectTab
) )
} }
// 加载动画覆盖层 // 加载动画覆盖层(不需要内边距,覆盖整个屏幕)
if (uiState.isLoading) { if (uiState.isLoading) {
LoadingOverlay() LoadingOverlay()
} }
} }
} }
@Composable @Composable
private fun BottomNavigationBar( private fun BottomNavigationBar(
selectedTab: HomeTab, selectedTab: HomeTab,

457
composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/exercise/ExerciseScreen.kt

@ -1,58 +1,183 @@
package com.whitefish.app.ui.home.exercise package com.whitefish.app.ui.home.exercise
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn 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.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.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.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import org.jetbrains.compose.ui.tooling.preview.Preview
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ExerciseScreen( fun ExerciseScreen() {
modifier: Modifier = Modifier,
viewModel: ExerciseViewModel = viewModel { ExerciseViewModel() }
) {
val uiState by viewModel.uiState.collectAsState()
LazyColumn( LazyColumn(
modifier = modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color(0xFF1A1A1A)) .padding(horizontal = 10.dp),
.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)
contentPadding = PaddingValues(vertical = 16.dp), ) {
verticalArrangement = Arrangement.spacedBy(12.dp) 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
fun ExerciseTypeCards() {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items(uiState.exerciseData) { exercise -> item {
ExerciseCard(exercise = exercise) 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 @Composable
private fun ExerciseCard( fun ExerciseTypeCard(
exercise: ExerciseData, title: String,
gradient: List<Color>,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Card( Card(
modifier = modifier.fillMaxWidth(), modifier = modifier,
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(Color.Transparent)
containerColor = Color(0xFF2A2A2A)
)
) { ) {
Column( Box(
modifier = Modifier
.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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .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( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -60,35 +185,97 @@ private fun ExerciseCard(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = exercise.type, text = "运动记录",
color = Color.White,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Medium fontWeight = FontWeight.Bold,
color = Color(0xFF666666)
) )
Text( Text(
text = exercise.date, text = "查看更多",
color = Color.Gray, fontSize = 14.sp,
fontSize = 12.sp color = Color(0xFF9C27B0)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) 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( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween verticalAlignment = Alignment.CenterVertically
) { ) {
ExerciseMetric( // 运动图标占位
label = "时长", Box(
value = exercise.duration 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
) )
ExerciseMetric( }
label = "卡路里",
value = "${exercise.calories} kcal" 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)
) )
ExerciseMetric( Spacer(modifier = Modifier.width(16.dp))
label = "距离", Text(
value = "${exercise.distance} km" text = time,
fontSize = 14.sp,
color = Color(0xFF666666)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = pace,
fontSize = 14.sp,
color = Color(0xFF666666)
) )
} }
} }
@ -96,39 +283,181 @@ private fun ExerciseCard(
} }
@Composable @Composable
private fun ExerciseMetric( fun ComprehensiveScoreSection() {
label: String, Card(
value: String modifier = Modifier.fillMaxWidth(),
) { shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(Color.White.copy(alpha = 0.9f))
) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally modifier = Modifier.padding(16.dp)
) { ) {
Text( Text(
text = value, text = "综合评分",
color = Color.White, 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
) {
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)
)
ScoreIndicator(
label = "运动强度审评估",
value = "",
progress = 0.95f,
color = Color(0xFF45B7D1)
)
ScoreIndicator(
label = "运动强度效率评估",
value = "",
progress = 0.9f,
color = Color(0xFF96CEB4)
)
}
}
}
}
@Composable
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, fontSize = 14.sp,
fontWeight = FontWeight.Bold color = Color(0xFF666666)
) )
}
}
}
@Composable
fun ScoreIndicator(
label: String,
value: String,
progress: Float,
color: Color
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = label, text = label,
color = Color.Gray, fontSize = 14.sp,
fontSize = 12.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))
) )
} }
} }
data class ExerciseData( Spacer(modifier = Modifier.width(16.dp))
val type: String,
val date: String, Text(
val duration: String, text = value,
val calories: Int, fontSize = 14.sp,
val distance: Float fontWeight = FontWeight.Medium,
) color = color
)
}
}
@Preview
@Composable @Composable
private fun ExerciseScreenPreview() { fun InsightSection() {
MaterialTheme { Card(
ExerciseScreen() 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
)
}
} }
} }

66
composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/exercise/ExerciseViewModel.kt

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
data class ExerciseUiState( data class ExerciseUiState(
val exerciseData: List<ExerciseData> = emptyList(), // val exerciseData: List<ExerciseData> = emptyList(),
val isLoading: Boolean = false val isLoading: Boolean = false
) )
@ -21,38 +21,38 @@ class ExerciseViewModel : ViewModel() {
private fun loadExerciseData() { private fun loadExerciseData() {
// 模拟加载运动数据 // 模拟加载运动数据
val mockData = listOf( // val mockData = listOf(
ExerciseData( // ExerciseData(
type = "跑步", // type = "跑步",
date = "今天", // date = "今天",
duration = "30分钟", // duration = "30分钟",
calories = 245, // calories = 245,
distance = 3.2f // distance = 3.2f
), // ),
ExerciseData( // ExerciseData(
type = "骑行", // type = "骑行",
date = "昨天", // date = "昨天",
duration = "45分钟", // duration = "45分钟",
calories = 320, // calories = 320,
distance = 8.5f // distance = 8.5f
), // ),
ExerciseData( // ExerciseData(
type = "游泳", // type = "游泳",
date = "2天前", // date = "2天前",
duration = "25分钟", // duration = "25分钟",
calories = 180, // calories = 180,
distance = 1.0f // distance = 1.0f
), // ),
ExerciseData( // ExerciseData(
type = "健走", // type = "健走",
date = "3天前", // date = "3天前",
duration = "60分钟", // duration = "60分钟",
calories = 150, // calories = 150,
distance = 4.8f // distance = 4.8f
) // )
) // )
_uiState.value = _uiState.value.copy(exerciseData = mockData) // _uiState.value = _uiState.value.copy(exerciseData = mockData)
} }
fun refresh() { fun refresh() {

2
composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/state/components/ExerciseGoalCard.kt

@ -15,12 +15,10 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview import org.jetbrains.compose.ui.tooling.preview.Preview
import ringappkmp.composeapp.generated.resources.Res import ringappkmp.composeapp.generated.resources.Res
import ringappkmp.composeapp.generated.resources.bg_exercise_target import ringappkmp.composeapp.generated.resources.bg_exercise_target
import ringappkmp.composeapp.generated.resources.compose_multiplatform
data class ExerciseGoalData( data class ExerciseGoalData(
val title: String = "运动目标", val title: String = "运动目标",

43
composeApp/src/iosMain/kotlin/com/whitefish/app/Platform.ios.kt

@ -1,9 +1,52 @@
package com.whitefish.app package com.whitefish.app
import platform.UIKit.UIDevice import platform.UIKit.UIDevice
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
class IOSPlatform: Platform { class IOSPlatform: Platform {
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
} }
actual fun getPlatform(): Platform = IOSPlatform() actual fun getPlatform(): Platform = IOSPlatform()
// iOS状态栏高度
@Composable
actual fun getStatusBarHeight(): Dp {
// 根据设备类型返回合适的状态栏高度
return when (UIDevice.currentDevice.model) {
"iPhone" -> {
// iPhone的状态栏高度通常为44dp(包括刘海屏)
44.dp
}
"iPad" -> {
// iPad的状态栏高度通常为24dp
24.dp
}
else -> {
// 默认值
44.dp
}
}
}
// iOS导航栏高度(底部安全区域)
@Composable
actual fun getNavigationBarHeight(): Dp {
// 根据设备类型返回合适的底部安全区域高度
return when (UIDevice.currentDevice.model) {
"iPhone" -> {
// iPhone X及以上机型的底部安全区域高度
34.dp
}
"iPad" -> {
// iPad通常没有底部安全区域
0.dp
}
else -> {
// 默认值,适用于有Home键的iPhone
0.dp
}
}
}

65
iosApp/iosApp/ContentView.swift

@ -2,9 +2,65 @@ import UIKit
import SwiftUI import SwiftUI
import ComposeApp import ComposeApp
// UIViewController
class TransparentStatusBarViewController: UIViewController {
private let composeViewController: UIViewController
init() {
self.composeViewController = MainViewControllerKt.MainViewController()
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
//
view.backgroundColor = UIColor.clear
// Compose
addChild(composeViewController)
view.addSubview(composeViewController.view)
composeViewController.didMove(toParent: self)
// Compose
composeViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
composeViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
composeViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
composeViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
composeViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// Compose
composeViewController.view.backgroundColor = UIColor.clear
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent //
}
override var prefersStatusBarHidden: Bool {
return false //
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//
setNeedsStatusBarAppearanceUpdate()
//
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
struct ComposeView: UIViewControllerRepresentable { struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController { func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController() return TransparentStatusBarViewController()
} }
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
@ -13,9 +69,14 @@ struct ComposeView: UIViewControllerRepresentable {
struct ContentView: View { struct ContentView: View {
var body: some View { var body: some View {
ComposeView() ComposeView()
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler .ignoresSafeArea(.all) //
.preferredColorScheme(.light) //
} }
} }
#Preview {
ContentView()
}

13
iosApp/iosApp/Info.plist

@ -4,5 +4,18 @@
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>UIStatusBarHidden</key>
<false/>
<key>UILaunchScreen</key>
<dict>
<key>UIColorName</key>
<string></string>
<key>UIImageName</key>
<string></string>
</dict>
</dict> </dict>
</plist> </plist>

20
iosApp/iosApp/StatusBarConfig.swift

@ -0,0 +1,20 @@
import SwiftUI
import UIKit
// UIApplication
extension UIApplication {
static func configureStatusBar() {
//
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
//
for window in windowScene.windows {
window.backgroundColor = UIColor.clear
//
if let rootViewController = window.rootViewController {
rootViewController.setNeedsStatusBarAppearanceUpdate()
}
}
}
}
}

4
iosApp/iosApp/iOSApp.swift

@ -5,6 +5,10 @@ struct iOSApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView()
.onAppear {
// 使
UIApplication.configureStatusBar()
}
} }
} }
} }
Loading…
Cancel
Save