Browse Source

feat: sleep data

dev_ios
AnranYus 4 weeks ago
parent
commit
35da66b6b8
  1. 3
      iosApp/iosApp/Libs/Modules/DeviceCenter.m
  2. 1
      shared/src/commonMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.kt
  3. 64
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/sleep/SleepChartPreview.kt
  4. 41
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/state/StateViewModel.kt
  5. 7
      shared/src/commonMain/kotlin/com/whitefish/ring/utils/Utils.kt
  6. 71
      shared/src/iosMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.ios.kt

3
iosApp/iosApp/Libs/Modules/DeviceCenter.m

@ -292,8 +292,6 @@ NSString * const CP_NAME = @"BlackShark";
#pragma mark -- 睡眠相关
-(void)querySleep:(NSDate *)date
{
self.calcSleepDate = date;
// 0 通知日期变更,其他界面各自查询
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:NOTI_NAME_SLEEP_DATE_CHANGE object:self.calcSleepDate];
@ -331,6 +329,7 @@ NSString * const CP_NAME = @"BlackShark";
// [results addObjectsFromArray:@[napData, napData2]];
#endif
if (results.count) { //
strongSelf.currentSelectDataSleepArray = [NSMutableArray new];
strongSelf.currentSelectNapArray = [NSMutableArray new];

1
shared/src/commonMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.kt

@ -3,7 +3,6 @@ package com.whitefish.ring.data
import com.whitefish.ring.bean.ui.HeartRate
import com.whitefish.ring.bean.ui.SleepState
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.map
suspend fun mac() = obtainDataStore().data.map { it[address] }.first()

64
shared/src/commonMain/kotlin/com/whitefish/ring/ui/chart/sleep/SleepChartPreview.kt

@ -22,7 +22,7 @@ fun SleepChartPreview(
modifier: Modifier = Modifier
) {
// 使用示例睡眠数据
val sampleData = remember { createSampleSleepData() }
// val sampleData = remember { createSampleSleepData() }
Card(
modifier = modifier.padding(16.dp),
@ -40,16 +40,16 @@ fun SleepChartPreview(
modifier = Modifier.padding(bottom = 16.dp)
)
// 睡眠图表
SleepChart(
sleepData = sampleData,
modifier = Modifier
.fillMaxWidth()
.height(120.dp),
barWidthMultiplier = 1f,
barHeight = 10f
)
// // 睡眠图表
// SleepChart(
// sleepData = sampleData,
// modifier = Modifier
// .fillMaxWidth()
// .height(120.dp),
// barWidthMultiplier = 1f,
// barHeight = 10f
// )
//
// 图例
SleepChartLegend(
modifier = Modifier
@ -57,13 +57,13 @@ fun SleepChartPreview(
.padding(top = 16.dp)
)
// 睡眠统计信息
SleepStatistics(
sleepData = sampleData,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
)
// // 睡眠统计信息
// SleepStatistics(
// sleepData = sampleData,
// modifier = Modifier
// .fillMaxWidth()
// .padding(top = 16.dp)
// )
}
}
}
@ -71,17 +71,17 @@ fun SleepChartPreview(
/**
*
*/
fun createSampleSleepData(): List<SleepSegment> {
return listOf(
SleepSegment(state = 0, durationMinutes = 30f), // 清醒
SleepSegment(state = 2, durationMinutes = 120f), // 深睡
SleepSegment(state = 1, durationMinutes = 90f), // 浅睡
SleepSegment(state = 3, durationMinutes = 60f), // REM
SleepSegment(state = 2, durationMinutes = 100f), // 深睡
SleepSegment(state = 1, durationMinutes = 80f), // 浅睡
SleepSegment(state = 0, durationMinutes = 20f) // 清醒
)
}
//fun createSampleSleepData(): List<SleepSegment> {
// return listOf(
// SleepSegment(state = 0, durationMinutes = 30f), // 清醒
// SleepSegment(state = 2, durationMinutes = 120f), // 深睡
// SleepSegment(state = 1, durationMinutes = 90f), // 浅睡
// SleepSegment(state = 3, durationMinutes = 60f), // REM
// SleepSegment(state = 2, durationMinutes = 100f), // 深睡
// SleepSegment(state = 1, durationMinutes = 80f), // 浅睡
// SleepSegment(state = 0, durationMinutes = 20f) // 清醒
// )
//}
/**
*
@ -150,9 +150,9 @@ private fun SleepStatistics(
// 计算各状态总时长
val stateTime = remember(sleepData) {
val times = mutableMapOf<Int, Float>()
sleepData.forEach { segment ->
times[segment.state] = (times[segment.state] ?: 0f) + segment.durationMinutes
}
// sleepData.forEach { segment ->
// times[segment.state] = (times[segment.state] ?: 0f) + segment.durationMinutes
// }
times
}

41
shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/state/StateViewModel.kt

@ -2,8 +2,6 @@ package com.whitefish.ring.ui.home.state
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.whitefish.app.ui.chart.sleep.createSampleSleepData
import com.whitefish.ring.data.MockDataProvider
import com.whitefish.ring.data.getHeartRate
import com.whitefish.ring.data.getSleep
import com.whitefish.ring.obtainDeviceManager
@ -12,6 +10,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.whitefish.ring.ui.home.state.components.ExerciseGoalData
import com.whitefish.ring.ui.home.state.components.RecoveryScoreData
import com.whitefish.ring.utils.getPastDayStartMillis
import com.whitefish.ring.utils.getTodayEndMillis
import com.whitefish.ring.utils.getTodayStartMillis
import com.whitefish.ring.utils.nowMilliseconds
@ -36,7 +35,7 @@ class StateViewModel : ViewModel() {
val uiState: StateFlow<StateUiState> = _uiState.asStateFlow()
init {
loadStateData()
loadInitData()
viewModelScope.launch {
obtainDeviceManager().bleReadyStateFlow.collectLatest {
if (it){
@ -44,37 +43,45 @@ class StateViewModel : ViewModel() {
today().atTime(0, 0).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds(),
nowMilliseconds()
)
Napier.i { "heart rates:${heartRates}" }
_uiState.value.copy(stateCards = _uiState.value.stateCards.map { card ->
if (card.type is StateCardType.HeartRate) {
card.copy(type = StateCardType.HeartRate(heartRates.map { it.value }))
} else {
card
}
}).also { newState ->
val sleepState = getSleep(getPastDayStartMillis(1), getTodayEndMillis())
_uiState.value.copy(stateCards = arrayListOf(
StateCardData(
title = "心率",
date = "${today().month}/${today().dayOfMonth}",
subtitle = "bpm",
isFullWidth = false,
type = StateCardType.HeartRate(heartRates.map { it.value })
),
StateCardData(
title = "睡眠",
date = "${today().month}/${today().dayOfMonth}",
subtitle = "昨晚",
isFullWidth = false,
type = StateCardType.SleepState(sleepState.segments)
)
)).let { newState ->
_uiState.value = newState
}
getSleep(getTodayStartMillis(), getTodayEndMillis())
}
}
}
}
private fun loadStateData() {
private fun loadInitData() {
val mockData = listOf(
StateCardData(
title = "心率",
date = "8/9",
date = "${today().month}/${today().dayOfMonth}",
subtitle = "bpm",
isFullWidth = false,
type = StateCardType.HeartRate(emptyList())
),
StateCardData(
title = "睡眠",
date = "8/9",
date = "${today().month}/${today().dayOfMonth}",
subtitle = "昨晚",
isFullWidth = false,
type = StateCardType.SleepState(createSampleSleepData())
type = StateCardType.SleepState(emptyList())
)
)

7
shared/src/commonMain/kotlin/com/whitefish/ring/utils/Utils.kt

@ -1,5 +1,6 @@
package com.whitefish.ring.utils
import androidx.compose.ui.geometry.Offset
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
@ -24,3 +25,9 @@ fun getTodayStartMillis(): Long {
fun getTodayEndMillis(): Long {
return today().atTime(23, 59, 59, 999999999).toInstant(UtcOffset.ZERO).toEpochMilliseconds()
}
fun pastDay(offset: Int) = today().minus(offset, DateTimeUnit.DAY)
fun getPastDayStartMillis(offset: Int): Long {
return pastDay(offset).atTime(0,0).toInstant(UtcOffset.ZERO).toEpochMilliseconds()
}

71
shared/src/iosMain/kotlin/com/whitefish/ring/data/DeviceDataProvider.ios.kt

@ -1,18 +1,31 @@
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.SleepState
import com.whitefish.ring.objc.DBHeartRate
import com.whitefish.ring.objc.DBSleepData
import com.whitefish.ring.objc.StagingListObj
import com.whitefish.ring.objc.StagingSubObj
import com.whitefish.ring.obtainDeviceManager
import com.whitefish.ring.utils.getCurrentTimestamp
import com.whitefish.ring.utils.timestampToDate
import com.whitefish.ring.utils.today
import io.github.aakira.napier.Napier
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.UtcOffset
import kotlinx.datetime.atStartOfDayIn
import kotlinx.datetime.atTime
import kotlinx.datetime.toInstant
import kotlinx.datetime.toNSDateComponents
import platform.Foundation.NSAllocateMemoryPages
import platform.Foundation.timeIntervalSince1970
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -50,56 +63,52 @@ actual suspend fun getSleep(
return@withContext suspendCoroutine { coroutine ->
macAddress?.let {
val segments = mutableListOf<SleepSegment>()
Napier.i { "${start.timestampToDate()} --> ${end.timestampToDate()}" }
DBSleepData.queryDbSleepBy(
it, start.timestampToDate().timeIntervalSince1970,
end.timestampToDate().timeIntervalSince1970
) { result ->
val sleepDataList = result?.map { it as DBSleepData }
var totalSeconds = 0
result?.let {
it.forEach {
(it as DBSleepData).apply {
totalSeconds += it.duration.intValue
Napier.i { "sleep isNap:${isNap},start:${it.sleepStart},end:${it.sleepEnd},duration:${duration}" }
stagingData.ousideStagingList?.forEach {
(it as StagingSubObj).apply {
Napier.i { "staging type:${type.value}, list size:${list.size} ========>" }
list.forEach { item ->
(item as StagingListObj).apply {
Napier.i { "staging item time:${time.doubleValue}" }
}
}
}
}
}
}
}
val sleepDataList = result?.map { it as DBSleepData }
sleepDataList?.let { dataList ->
dataList.forEach { sleepData ->
if (!sleepData.isNap) {
totalSeconds += sleepData.duration.intValue
// 处理睡眠分期数据
sleepData.stagingData?.ousideStagingList?.let { stagingList ->
sleepData.stagingData.ousideStagingList?.let { stagingList ->
for (i in 0 until stagingList.size) {
val stagingObj = stagingList[i] as StagingSubObj
// 计算该阶段的持续时间
val duration = if (i == 0) {
// 第一个阶段:从开始到结束
stagingObj.list.let { list ->
val firstTime = (list.first() as? StagingListObj)?.time?.doubleValue ?: 0.0
val lastTime = (list.last() as? StagingListObj)?.time?.doubleValue ?: 0.0
lastTime - firstTime
}
} else {
// 后续阶段:从前一个阶段结束到当前阶段结束
val prevStagingObj = stagingList[i - 1] as StagingSubObj
val currentEndTime = (stagingObj.list.first() as? StagingListObj)?.time?.doubleValue ?: 0.0
val prevEndTime = (prevStagingObj.list.last() as? StagingListObj)?.time?.doubleValue ?: 0.0
currentEndTime - prevEndTime
}
// 转换睡眠状态类型到用户定义的状态
val stageDuration = (stagingObj.list.last() as StagingListObj).time.doubleValue - (stagingObj.list.first() as StagingListObj).time.doubleValue / 60
val mappedState = mapSleepStageToState(stagingObj.type.value.toInt())
// 只添加有效的睡眠状态(跳过 NONE 状态)
if (mappedState >= 0) {
val durationMinutes = (duration / 60.0).toFloat()
if (durationMinutes > 0) {
segments.add(SleepSegment(mappedState, durationMinutes))
}
segments.add(SleepSegment(mappedState, stageDuration.toFloat()))
}
}
}
}
}
}
coroutine.resume(SleepState(totalSeconds =totalSeconds, segments = segments ))
coroutine.resume(SleepState(totalSeconds =totalSeconds,segments = segments))
}
}
}

Loading…
Cancel
Save