Browse Source

fix: style fix

main
AnranYus 1 month ago
parent
commit
54bb2c8a0c
  1. 165
      SystemBars_README.md
  2. 52
      composeApp/src/androidMain/kotlin/com/whitefish/app/Platform.android.kt
  3. 11
      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. 469
      composeApp/src/commonMain/kotlin/com/whitefish/app/ui/home/exercise/ExerciseScreen.kt
  8. 64
      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. 45
      composeApp/src/iosMain/kotlin/com/whitefish/app/Platform.ios.kt
  11. 2
      iosApp/iosApp.xcodeproj/project.pbxproj
  12. 65
      iosApp/iosApp/ContentView.swift
  13. 13
      iosApp/iosApp/Info.plist
  14. 20
      iosApp/iosApp/StatusBarConfig.swift
  15. 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平台效果一致
- 代码简洁,易于维护

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

@ -1,9 +1,59 @@
package com.whitefish.app
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 {
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
}

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

@ -1,7 +1,16 @@
package com.whitefish.app
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
interface Platform {
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.recovery.RecoveryScreen
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 ringappkmp.composeapp.generated.resources.bg
import ringappkmp.composeapp.generated.resources.ic_nav_exercise
@ -39,6 +42,7 @@ fun HomeScreen(
val uiState by viewModel.uiState.collectAsState()
Box(modifier = modifier.fillMaxSize()) {
// 背景图片,延伸到整个屏幕(包括状态栏和导航栏区域)
Image(
painter = painterResource(Res.drawable.bg),
contentDescription = null,
@ -46,11 +50,13 @@ fun HomeScreen(
contentScale = ContentScale.Crop
)
// 内容区域,添加系统栏内边距
Column(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding() // 添加状态栏和导航栏的内边距
) {
// 主内容区域
Box(
modifier = Modifier
.weight(1f)
@ -69,17 +75,15 @@ fun HomeScreen(
selectedTab = uiState.selectedTab,
onTabSelected = viewModel::selectTab
)
}
// 加载动画覆盖层
// 加载动画覆盖层(不需要内边距,覆盖整个屏幕)
if (uiState.isLoading) {
LoadingOverlay()
}
}
}
@Composable
private fun BottomNavigationBar(
selectedTab: HomeTab,

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

@ -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
)
}
}
}

64
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
data class ExerciseUiState(
val exerciseData: List<ExerciseData> = emptyList(),
// val exerciseData: List<ExerciseData> = emptyList(),
val isLoading: Boolean = false
)
@ -21,38 +21,38 @@ class ExerciseViewModel : ViewModel() {
private fun loadExerciseData() {
// 模拟加载运动数据
val mockData = listOf(
ExerciseData(
type = "跑步",
date = "今天",
duration = "30分钟",
calories = 245,
distance = 3.2f
),
ExerciseData(
type = "骑行",
date = "昨天",
duration = "45分钟",
calories = 320,
distance = 8.5f
),
ExerciseData(
type = "游泳",
date = "2天前",
duration = "25分钟",
calories = 180,
distance = 1.0f
),
ExerciseData(
type = "健走",
date = "3天前",
duration = "60分钟",
calories = 150,
distance = 4.8f
)
)
// val mockData = listOf(
// ExerciseData(
// type = "跑步",
// date = "今天",
// duration = "30分钟",
// calories = 245,
// distance = 3.2f
// ),
// ExerciseData(
// type = "骑行",
// date = "昨天",
// duration = "45分钟",
// calories = 320,
// distance = 8.5f
// ),
// ExerciseData(
// type = "游泳",
// date = "2天前",
// duration = "25分钟",
// calories = 180,
// distance = 1.0f
// ),
// ExerciseData(
// type = "健走",
// date = "3天前",
// duration = "60分钟",
// calories = 150,
// distance = 4.8f
// )
// )
_uiState.value = _uiState.value.copy(exerciseData = mockData)
// _uiState.value = _uiState.value.copy(exerciseData = mockData)
}
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.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import ringappkmp.composeapp.generated.resources.Res
import ringappkmp.composeapp.generated.resources.bg_exercise_target
import ringappkmp.composeapp.generated.resources.compose_multiplatform
data class ExerciseGoalData(
val title: String = "运动目标",

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

@ -1,9 +1,52 @@
package com.whitefish.app
import platform.UIKit.UIDevice
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
class IOSPlatform: Platform {
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
}
}
}

2
iosApp/iosApp.xcodeproj/project.pbxproj

@ -376,4 +376,4 @@
/* End XCConfigurationList section */
};
rootObject = B9DA97A92DC1472C00A4DA20 /* Project object */;
}
}

65
iosApp/iosApp/ContentView.swift

@ -2,9 +2,65 @@ import UIKit
import SwiftUI
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 {
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController()
return TransparentStatusBarViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
@ -13,9 +69,14 @@ struct ComposeView: UIViewControllerRepresentable {
struct ContentView: View {
var body: some View {
ComposeView()
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler
.ignoresSafeArea(.all) //
.preferredColorScheme(.light) //
}
}
#Preview {
ContentView()
}

13
iosApp/iosApp/Info.plist

@ -4,5 +4,18 @@
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<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>
</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 {
WindowGroup {
ContentView()
.onAppear {
// 使
UIApplication.configureStatusBar()
}
}
}
}
Loading…
Cancel
Save