diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2c07f93..371d743 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ vico = "2.1.3" androidDatabaseSqlcipher = "4.5.4" roomKtx = "2.6.1" activityKtx = "1.10.1" +uiTooling = "1.8.2" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -28,6 +29,7 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" } androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index da47a1f..b101715 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -93,6 +93,7 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + buildToolsVersion = "36.0.0" dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) @@ -103,4 +104,5 @@ android { } dependencies { implementation(libs.androidx.activity.ktx) + debugImplementation(libs.androidx.ui.tooling) } diff --git a/shared/src/commonMain/composeResources/drawable/ic_state_heart_rate.png b/shared/src/commonMain/composeResources/drawable/ic_state_heart_rate.png new file mode 100644 index 0000000..08ba6f5 Binary files /dev/null and b/shared/src/commonMain/composeResources/drawable/ic_state_heart_rate.png differ diff --git a/shared/src/commonMain/composeResources/drawable/ic_state_oxygen.png b/shared/src/commonMain/composeResources/drawable/ic_state_oxygen.png new file mode 100644 index 0000000..90802fe Binary files /dev/null and b/shared/src/commonMain/composeResources/drawable/ic_state_oxygen.png differ diff --git a/shared/src/commonMain/composeResources/drawable/ic_state_pressure.png b/shared/src/commonMain/composeResources/drawable/ic_state_pressure.png new file mode 100644 index 0000000..5314bb7 Binary files /dev/null and b/shared/src/commonMain/composeResources/drawable/ic_state_pressure.png differ diff --git a/shared/src/commonMain/composeResources/drawable/ic_state_sleep.png b/shared/src/commonMain/composeResources/drawable/ic_state_sleep.png new file mode 100644 index 0000000..9c6e253 Binary files /dev/null and b/shared/src/commonMain/composeResources/drawable/ic_state_sleep.png differ diff --git a/shared/src/commonMain/composeResources/drawable/ic_state_temperature.png b/shared/src/commonMain/composeResources/drawable/ic_state_temperature.png new file mode 100644 index 0000000..6c0d9a1 Binary files /dev/null and b/shared/src/commonMain/composeResources/drawable/ic_state_temperature.png differ diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Oximetry.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Oximetry.kt new file mode 100644 index 0000000..21ae590 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Oximetry.kt @@ -0,0 +1,6 @@ +package com.whitefish.ring.bean.ui + +data class Oximetry( + val time: Long, + val value: Float, +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/SleepState.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/SleepState.kt index bd7dd5f..857f383 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/SleepState.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/SleepState.kt @@ -5,5 +5,7 @@ import kotlinx.datetime.Clock data class SleepState ( val totalSeconds: Int, //秒 - val segments: List + val segments: List, + val start: Long, + val end: Long ) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Temperature.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Temperature.kt new file mode 100644 index 0000000..934252b --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Temperature.kt @@ -0,0 +1,6 @@ +package com.whitefish.ring.bean.ui + +data class Temperature ( + val time: Long, + val value: Float, +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/TimeComponents.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/TimeComponents.kt new file mode 100644 index 0000000..0f61740 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/TimeComponents.kt @@ -0,0 +1,15 @@ +package com.whitefish.ring.bean.ui + +data class TimeComponents( + val hours: Int, + val minutes: Int, + val seconds: Int +) + +fun Int.convertSecondsToTime(): TimeComponents { + val hours = this / 3600 + val minutes = (this % 3600) / 60 + val seconds = this % 60 + + return TimeComponents(hours, minutes, seconds) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.kt index c66291d..df2c4a6 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.kt @@ -1,10 +1,14 @@ package com.whitefish.ring.data import com.whitefish.ring.bean.ui.HeartRate +import com.whitefish.ring.bean.ui.Oximetry import com.whitefish.ring.bean.ui.SleepState +import com.whitefish.ring.bean.ui.Temperature import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map suspend fun mac() = obtainDataStore().data.map { it[address] }.first() expect suspend fun getHeartRate(start: Long,end: Long): List -expect suspend fun getSleep(start: Long, end: Long): SleepState \ No newline at end of file +expect suspend fun getSleep(start: Long, end: Long): SleepState +expect suspend fun getOximetry(start: Long, end: Long): List +expect suspend fun getTemperature(start: Long, end: Long): List \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/ComposeMultiplatformBasicLineChart.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/ComposeMultiplatformBasicLineChart.kt index 6e76671..15b0f08 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/ComposeMultiplatformBasicLineChart.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/ComposeMultiplatformBasicLineChart.kt @@ -19,7 +19,7 @@ import com.patrykandpatrick.vico.multiplatform.common.Fill import com.patrykandpatrick.vico.multiplatform.common.fill @Composable -fun ComposeMultiplatformBasicLineChart(data: List, modifier: Modifier = Modifier) { +fun ComposeMultiplatformBasicLineChart(data: List, modifier: Modifier = Modifier) { val modelProducer = remember { CartesianChartModelProducer() } LaunchedEffect(Unit) { modelProducer.runTransaction { @@ -52,6 +52,6 @@ fun ComposeMultiplatformBasicLineChart(data: List, modifier: Modifier = Mod ), modelProducer = modelProducer, modifier = modifier, - zoomState = VicoZoomState(false, Zoom.Content,Zoom.Content,Zoom.Content) - ) + zoomState = VicoZoomState(false, Zoom.Content, Zoom.Content, Zoom.Content) + ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChart.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChart.kt index 0eba0fe..ce5392f 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChart.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChart.kt @@ -115,7 +115,7 @@ private fun DrawScope.drawRecoveryChart( // 绘制水平虚线 val midLineY = chartBottom - chartHeight / 2 - drawDashedLine( + drawDashLine( start = Offset(padding, midLineY), end = Offset(size.width - padding, midLineY), color = Color.White.copy(alpha = 0.4f) @@ -185,7 +185,7 @@ private fun DrawScope.drawRecoveryChart( } } -private fun DrawScope.drawDashedLine( +private fun DrawScope.drawDashLine( start: Offset, end: Offset, color: Color, diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChartWithTimeLabels.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChartWithTimeLabels.kt index 2659f23..b196e1e 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChartWithTimeLabels.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryChartWithTimeLabels.kt @@ -240,7 +240,9 @@ private fun DrawScope.drawRecoveryChartWithLabels( drawSmoothCurve( points = actualPoints, brush = gradientBrush, - strokeWidth = 4.dp.toPx() + strokeWidth = 2.dp.toPx(), + color = Color.Transparent, + isDashed = false, ) } @@ -248,8 +250,10 @@ private fun DrawScope.drawRecoveryChartWithLabels( if (predictedPoints.isNotEmpty()) { drawSmoothCurve( points = predictedPoints, + brush = null, color = Color.White.copy(alpha = 0.9f), - strokeWidth = 3.dp.toPx() + strokeWidth = 2.dp.toPx(), + isDashed = false, ) } @@ -309,10 +313,10 @@ private fun DrawScope.drawDashedLine( private fun DrawScope.drawSmoothCurve( points: List, - brush: Brush? = null, - color: Color = Color.Transparent, - strokeWidth: Float = 4f, - isDashed: Boolean = false + brush: Brush?, + color: Color, + strokeWidth: Float, + isDashed: Boolean ) { if (points.size < 2) return diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryStateCard.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryStateCard.kt new file mode 100644 index 0000000..716f156 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/RecoveryStateCard.kt @@ -0,0 +1,122 @@ +package com.whitefish.ring.ui.chart + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.whitefish.app.ui.chart.sleep.SleepChart +import com.whitefish.ring.bean.ui.SleepState +import com.whitefish.ring.utils.buildStyledText +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import ring.shared.generated.resources.Res + + +data class RecoveryStateCardItem( + val icon: DrawableResource, + val title: String, + val tip: String, + val value: AnnotatedString, + val type: RecoveryStateCardType +) + +sealed class RecoveryStateCardType() { + class Sleep(val state: SleepState) : RecoveryStateCardType() + class HeartRate(val values: List) : RecoveryStateCardType() + class Oximetry(val values: List) : RecoveryStateCardType() + class Pressure(val values: List) : RecoveryStateCardType() + class Temperature(val values: List) : RecoveryStateCardType() +} + +@Composable +fun RecoveryStateCard(states: List) { + Card( + shape = RoundedCornerShape(40.dp), + modifier = Modifier.fillMaxWidth().wrapContentHeight() + ) { + Spacer(modifier = Modifier.height(6.dp)) + Box(modifier = Modifier.width(92.dp).height(6.dp).clip(RoundedCornerShape(15.dp)).background(Color(0xff352764)).align( + Alignment.CenterHorizontally)) + Column(modifier = Modifier.padding(top = 28.dp)) { + states.forEach { + StateItem(painterResource(it.icon), it.title, it.tip, it.value) { modifier -> + when (it.type) { + is RecoveryStateCardType.Sleep -> { + SleepChart( + it.type.state.segments,modifier = modifier + ) + } + + is RecoveryStateCardType.HeartRate -> { + ComposeMultiplatformBasicLineChart(it.type.values,modifier = modifier) + } + is RecoveryStateCardType.Oximetry -> { + ComposeMultiplatformBasicLineChart(it.type.values,modifier = modifier) + } + is RecoveryStateCardType.Pressure -> TODO() + is RecoveryStateCardType.Temperature -> { + ComposeMultiplatformBasicLineChart(it.type.values,modifier = modifier) + } + } + } + Spacer(modifier = Modifier.height(38.dp)) + } + } + } +} + +@Composable +fun StateItem( + icon: Painter, + title: String, + tip: String, + value: AnnotatedString, + chart: @Composable (Modifier) -> Unit +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(start = 20.dp).height(72.dp) + ) { + Image(painter = icon, contentDescription = null, modifier = Modifier.size(40.dp)) + + Spacer(modifier = Modifier.width(21.dp)) + Column { + Text(title, fontSize = 18.sp) + Spacer(modifier = Modifier.height(7.dp)) + Text(tip, fontSize = 12.sp, color = Color(0xff61B7C8), modifier = Modifier.width(76.dp)) + } + + Spacer(modifier = Modifier.width(20.dp)) + chart(Modifier.fillMaxHeight().width(72.dp)) + Spacer(modifier = Modifier.width(28.dp)) + Text(value) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryScreen.kt index 738f6dc..0caa5d9 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryScreen.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryScreen.kt @@ -1,18 +1,20 @@ package com.whitefish.ring.ui.home.recovery -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.whitefish.ring.ui.chart.RecoveryChartWithTimeLabels -import com.whitefish.ring.ui.components.TopNavigationBar -import com.whitefish.ring.ui.components.systemBarsPadding +import com.whitefish.ring.ui.chart.RecoveryStateCard import com.whitefish.ring.ui.components.CircularProgressCard +import com.whitefish.ring.ui.components.TopNavigationBar import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @@ -20,43 +22,47 @@ fun RecoveryScreen( modifier: Modifier = Modifier, viewModel: RecoveryViewModel = viewModel { RecoveryViewModel() } ) { - Column( + val states by viewModel.uiState.collectAsState() + + LazyColumn( modifier = modifier .fillMaxSize() - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color(0xFF6B73FF).copy(alpha = 0.1f), - Color(0xFF9DD5EA).copy(alpha = 0.05f), - MaterialTheme.colorScheme.background - ) - ) - ) - .systemBarsPadding() ) { // 顶部导航栏 - TopNavigationBar( - title = "恢复得分", - month = 7, - day = 14, - onNavigateClick = { - // TODO: 实现导航逻辑 - } - ) - + item { + TopNavigationBar( + title = "恢复得分", + month = 7, + day = 14, + onNavigateClick = { + // TODO: 实现导航逻辑 + } + ) + } + // 圆弧进度卡片 - CircularProgressCard( - currentMinutes = 75, - totalMinutes = 120, // 假设总时长为120分钟 - completionTime = "预计7/15日18:36后完全恢复" - ) + item { + CircularProgressCard( + currentMinutes = 75, + totalMinutes = 120, // 假设总时长为120分钟 + completionTime = "预计7/15日18:36后完全恢复" + ) + } - RecoveryChartWithTimeLabels( - modifier = Modifier.fillMaxWidth().height(300.dp), - actualData = arrayListOf(1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f,1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f,1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f,1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f), - predictedData = arrayListOf() - ) + item { + RecoveryChartWithTimeLabels( + modifier = Modifier.fillMaxWidth().height(300.dp), + actualData = arrayListOf(1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f,1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f,1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f,1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.1f), + predictedData = arrayListOf() + ) + } + item { Spacer(modifier = Modifier.height(7.dp)) } + item { + RecoveryStateCard( + states = states.stateCards + ) + } } } diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryViewModel.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryViewModel.kt index 29622ed..c1e95db 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryViewModel.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/recovery/RecoveryViewModel.kt @@ -1,9 +1,102 @@ package com.whitefish.ring.ui.home.recovery + +import androidx.compose.ui.unit.sp import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.whitefish.ring.bean.ui.convertSecondsToTime +import com.whitefish.ring.data.getHeartRate +import com.whitefish.ring.data.getOximetry +import com.whitefish.ring.data.getSleep +import com.whitefish.ring.data.getTemperature +import com.whitefish.ring.obtainDeviceManager +import com.whitefish.ring.ui.chart.RecoveryStateCardItem +import com.whitefish.ring.ui.chart.RecoveryStateCardType +import com.whitefish.ring.utils.buildStyledText +import com.whitefish.ring.utils.getPastDayStartMillis +import com.whitefish.ring.utils.getTodayEndMillis +import com.whitefish.ring.utils.today +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import ring.shared.generated.resources.Res +import ring.shared.generated.resources.ic_state_heart_rate +import ring.shared.generated.resources.ic_state_oxygen +import ring.shared.generated.resources.ic_state_sleep +import ring.shared.generated.resources.ic_state_temperature class RecoveryViewModel : ViewModel() { + private val _uiState = MutableStateFlow(RecoveryUiState(emptyList())) + val uiState = _uiState.asStateFlow() + + init { + viewModelScope.launch { + obtainDeviceManager().bleReadyStateFlow.collectLatest { + if (it) { + val sleepState = getSleep(getPastDayStartMillis(1), getTodayEndMillis()) + + val heartRate = getHeartRate(sleepState.start, sleepState.end) + val oximetry = getOximetry(sleepState.start, sleepState.end) + val temperature = getTemperature(sleepState.start, sleepState.end) + + _uiState.value = RecoveryUiState( + listOf( + RecoveryStateCardItem( + icon = Res.drawable.ic_state_sleep, + title = "睡眠", + tip = "睡眠质量较好", + value = buildStyledText { + val time = sleepState.totalSeconds.convertSecondsToTime() + append(time.hours.toString(),20.sp) + append("小时",11.sp) + append(time.minutes.toString(),20.sp) + append("分钟",11.sp) + }, + type = RecoveryStateCardType.Sleep(sleepState) + ), + RecoveryStateCardItem( + icon = Res.drawable.ic_state_heart_rate, + title = "心率", + tip = "睡眠质量较好", + value = buildStyledText { + append("${heartRate.lastOrNull()?.value}", 20.sp) + append("次/分钟", 11.sp) + }, + type = RecoveryStateCardType.HeartRate(heartRate.map { it.value }) + ), + RecoveryStateCardItem( + icon = Res.drawable.ic_state_oxygen, + title = "血氧", + tip = "血氧水平正常", + value = buildStyledText { + append("${oximetry.lastOrNull()?.value}", 20.sp) + append("%", 11.sp) + }, + type = RecoveryStateCardType.Oximetry(oximetry.map { it.value } + ) + ), + RecoveryStateCardItem( + icon = Res.drawable.ic_state_temperature, + title = "体温", + tip = "体温水平正常", + value = buildStyledText { + append("${temperature.lastOrNull()?.value}", 20.sp) + append("°C", 11.sp) + }, + type = RecoveryStateCardType.Temperature(temperature.map { it.value }) + ) + ) + + ) + } + } + } + } +} -} \ No newline at end of file +data class RecoveryUiState( + val stateCards: List +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/utils/StyledTextBuilder.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/utils/StyledTextBuilder.kt new file mode 100644 index 0000000..c9fb1d8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/utils/StyledTextBuilder.kt @@ -0,0 +1,129 @@ +package com.whitefish.ring.utils + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + +/** + * 多样式文本构建器 + * 用于方便地构建包含不同样式文本段落的AnnotatedString + */ +class StyledTextBuilder { + private val builder = AnnotatedString.Builder() + + /** + * 添加普通文本 + */ + fun append(text: String): StyledTextBuilder { + builder.append(text) + return this + } + + /** + * 添加指定大小的文本 + */ + fun append( + text: String, + fontSize: TextUnit + ): StyledTextBuilder { + builder.withStyle( + style = SpanStyle(fontSize = fontSize) + ) { + append(text) + } + return this + } + + /** + * 添加指定大小和颜色的文本 + */ + fun append( + text: String, + fontSize: TextUnit, + color: Color + ): StyledTextBuilder { + builder.withStyle( + style = SpanStyle( + fontSize = fontSize, + color = color + ) + ) { + append(text) + } + return this + } + + /** + * 添加完全自定义样式的文本 + */ + fun append( + text: String, + fontSize: TextUnit = TextUnit.Unspecified, + color: Color = Color.Unspecified, + fontWeight: FontWeight? = null, + fontStyle: FontStyle? = null + ): StyledTextBuilder { + builder.withStyle( + style = SpanStyle( + fontSize = fontSize, + color = color, + fontWeight = fontWeight, + fontStyle = fontStyle + ) + ) { + append(text) + } + return this + } + + /** + * 添加大号文本 + */ + fun appendLarge(text: String, color: Color = Color.Unspecified): StyledTextBuilder { + return append(text, 24.sp, color) + } + + /** + * 添加中号文本 + */ + fun appendMedium(text: String, color: Color = Color.Unspecified): StyledTextBuilder { + return append(text, 18.sp, color) + } + + /** + * 添加小号文本 + */ + fun appendSmall(text: String, color: Color = Color.Unspecified): StyledTextBuilder { + return append(text, 14.sp, color) + } + + /** + * 添加粗体文本 + */ + fun appendBold(text: String, fontSize: TextUnit = TextUnit.Unspecified): StyledTextBuilder { + return append(text, fontSize, Color.Unspecified, FontWeight.Bold) + } + + /** + * 添加斜体文本 + */ + fun appendItalic(text: String, fontSize: TextUnit = TextUnit.Unspecified): StyledTextBuilder { + return append(text, fontSize, Color.Unspecified, null, FontStyle.Italic) + } + + /** + * 构建最终的AnnotatedString + */ + fun build(): AnnotatedString { + return builder.toAnnotatedString() + } +} + +fun buildStyledText(block: StyledTextBuilder.() -> Unit): AnnotatedString { + return StyledTextBuilder().apply(block).build() +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.ios.kt b/shared/src/iosMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.ios.kt index fb426de..1c5e2bc 100644 --- a/shared/src/iosMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.ios.kt +++ b/shared/src/iosMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.ios.kt @@ -3,9 +3,13 @@ package com.whitefish.ring.data import com.whitefish.app.ui.chart.sleep.SleepSegment import com.whitefish.ring.DeviceManager import com.whitefish.ring.bean.ui.HeartRate +import com.whitefish.ring.bean.ui.Oximetry import com.whitefish.ring.bean.ui.SleepState +import com.whitefish.ring.bean.ui.Temperature import com.whitefish.ring.objc.DBHeartRate +import com.whitefish.ring.objc.DBOxygen import com.whitefish.ring.objc.DBSleepData +import com.whitefish.ring.objc.DBThermemoter import com.whitefish.ring.objc.StagingListObj import com.whitefish.ring.objc.StagingSubObj import com.whitefish.ring.obtainDeviceManager @@ -69,6 +73,8 @@ actual suspend fun getSleep( end.timestampToDate().timeIntervalSince1970 ) { result -> var totalSeconds = 0 + var startTime = 0L + var endTime = 0L result?.let { it.forEach { @@ -94,13 +100,17 @@ actual suspend fun getSleep( sleepDataList?.let { dataList -> dataList.forEach { sleepData -> if (!sleepData.isNap) { + startTime = (sleepData.stagingData.startTime * 1000).toLong() // 转换为毫秒 + endTime = (sleepData.stagingData.endTime * 1000).toLong() // 转换为毫秒 // 处理睡眠分期数据 sleepData.stagingData.ousideStagingList?.let { stagingList -> for (i in 0 until stagingList.size) { val stagingObj = stagingList[i] as StagingSubObj - val stageDuration = (stagingObj.list.last() as StagingListObj).time.doubleValue - (stagingObj.list.first() as StagingListObj).time.doubleValue / 60 - val mappedState = mapSleepStageToState(stagingObj.type.value.toInt()) + val stageDuration = + (stagingObj.list.last() as StagingListObj).time.doubleValue - (stagingObj.list.first() as StagingListObj).time.doubleValue / 60 + val mappedState = + mapSleepStageToState(stagingObj.type.value.toInt()) segments.add(SleepSegment(mappedState, stageDuration.toFloat())) } } @@ -108,7 +118,14 @@ actual suspend fun getSleep( } } - coroutine.resume(SleepState(totalSeconds =totalSeconds,segments = segments)) + coroutine.resume( + SleepState( + totalSeconds = totalSeconds, + segments = segments, + start = startTime, + end = endTime + ) + ) } } } @@ -130,3 +147,63 @@ private fun mapSleepStageToState(originalType: Int): Int { else -> -1 } } + +@OptIn(ExperimentalForeignApi::class) +actual suspend fun getOximetry( + start: Long, + end: Long +): List = withContext(Dispatchers.IO) { + val macAddress = mac() + return@withContext suspendCoroutine { coroutine -> + macAddress?.let { + DBOxygen.query( + it, + start.timestampToDate().timeIntervalSince1970, + end.timestampToDate().timeIntervalSince1970, + false + ) { result -> + if (!result.isNullOrEmpty()) { + val oximetry = result.map { it as DBOxygen } + Napier.i { "oximetry -> ${oximetry}" } + coroutine.resume(oximetry.map { + Oximetry( + it.time.getCurrentTimestamp(), + it.value.floatValue + ) + }) + }else{ + coroutine.resume(emptyList()) + } + } + } + } + +} + +@OptIn(ExperimentalForeignApi::class) +actual suspend fun getTemperature( + start: Long, + end: Long +): List = withContext(Dispatchers.IO) { + val macAddress = mac() + return@withContext suspendCoroutine { coroutine -> + macAddress?.let { + DBThermemoter.queryBy( + it, + start.timestampToDate(), + end.timestampToDate(), + false + ) { result, max, min, avg -> + if (!result.isNullOrEmpty()){ + val temperatures = result.map { it as DBThermemoter }.map { + Temperature(it.time.getCurrentTimestamp(), it.value.floatValue) + } + coroutine.resume(temperatures) + }else{ + coroutine.resume(emptyList()) + } + } + } + } + +} \ No newline at end of file