Showing
9 changed files
with
234 additions
and
81 deletions
| @@ -209,14 +209,14 @@ final class HealthKitQueryReader { | @@ -209,14 +209,14 @@ final class HealthKitQueryReader { | ||
| 209 | healthValueTimestamp: timestamp, | 209 | healthValueTimestamp: timestamp, |
| 210 | move: Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())), | 210 | move: Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())), |
| 211 | stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count())), | 211 | stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count())), |
| 212 | - activityMoveMode: Self.activityMoveModeValue(from: summary), | 212 | +// activityMoveMode: Self.activityMoveModeValue(from: summary), |
| 213 | activeEnergyBurned: summary.activeEnergyBurned.doubleValue(for: .kilocalorie()), | 213 | activeEnergyBurned: summary.activeEnergyBurned.doubleValue(for: .kilocalorie()), |
| 214 | activeEnergyBurnedGoal: summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie()), | 214 | activeEnergyBurnedGoal: summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie()), |
| 215 | appleMoveTime: Self.appleMoveTimeValue(from: summary), | 215 | appleMoveTime: Self.appleMoveTimeValue(from: summary), |
| 216 | appleMoveTimeGoal: Self.appleMoveTimeGoalValue(from: summary), | 216 | appleMoveTimeGoal: Self.appleMoveTimeGoalValue(from: summary), |
| 217 | - appleExerciseTime: summary.appleExerciseTime.doubleValue(for: .minute()), | 217 | + appleExerciseTime: summary.appleExerciseTime.doubleValue(for: .second()), |
| 218 | exerciseTimeGoal: Self.exerciseTimeGoalValue(from: summary), | 218 | exerciseTimeGoal: Self.exerciseTimeGoalValue(from: summary), |
| 219 | - appleStandHours: summary.appleStandHours.doubleValue(for: .count()), | 219 | + appleStandHours: Self.standSeconds(from: summary.appleStandHours), |
| 220 | standHoursGoal: Self.standHoursGoalValue(from: summary) | 220 | standHoursGoal: Self.standHoursGoalValue(from: summary) |
| 221 | ) | 221 | ) |
| 222 | } | 222 | } |
| @@ -244,18 +244,22 @@ final class HealthKitQueryReader { | @@ -244,18 +244,22 @@ final class HealthKitQueryReader { | ||
| 244 | 244 | ||
| 245 | private static func exerciseTimeGoalValue(from summary: HKActivitySummary) -> Double? { | 245 | private static func exerciseTimeGoalValue(from summary: HKActivitySummary) -> Double? { |
| 246 | if #available(iOS 16.0, *) { | 246 | if #available(iOS 16.0, *) { |
| 247 | - return summary.exerciseTimeGoal?.doubleValue(for: .minute()) | 247 | + return summary.exerciseTimeGoal?.doubleValue(for: .second()) |
| 248 | } | 248 | } |
| 249 | return nil | 249 | return nil |
| 250 | } | 250 | } |
| 251 | 251 | ||
| 252 | private static func standHoursGoalValue(from summary: HKActivitySummary) -> Double? { | 252 | private static func standHoursGoalValue(from summary: HKActivitySummary) -> Double? { |
| 253 | if #available(iOS 16.0, *) { | 253 | if #available(iOS 16.0, *) { |
| 254 | - return summary.standHoursGoal?.doubleValue(for: .count()) | 254 | + return summary.standHoursGoal.map { Self.standSeconds(from: $0) } |
| 255 | } | 255 | } |
| 256 | return nil | 256 | return nil |
| 257 | } | 257 | } |
| 258 | 258 | ||
| 259 | + private static func standSeconds(from quantity: HKQuantity) -> Double { | ||
| 260 | + quantity.doubleValue(for: .count()) * 60 * 60 | ||
| 261 | + } | ||
| 262 | + | ||
| 259 | private func fetchHeartRateFamily(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | 263 | private func fetchHeartRateFamily(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { |
| 260 | async let heartRate = fetchQuantitySamples( | 264 | async let heartRate = fetchQuantitySamples( |
| 261 | identifier: .heartRate, | 265 | identifier: .heartRate, |
| @@ -332,7 +336,7 @@ final class HealthKitQueryReader { | @@ -332,7 +336,7 @@ final class HealthKitQueryReader { | ||
| 332 | return samples.map { sample in | 336 | return samples.map { sample in |
| 333 | var ismotion: Bool? | 337 | var ismotion: Bool? |
| 334 | if identifier == .heartRate, let rawValue = sample.metadata?[ HKMetadataKeyHeartRateMotionContext] as? NSNumber, let context = HKHeartRateMotionContext(rawValue: rawValue.intValue) { | 338 | if identifier == .heartRate, let rawValue = sample.metadata?[ HKMetadataKeyHeartRateMotionContext] as? NSNumber, let context = HKHeartRateMotionContext(rawValue: rawValue.intValue) { |
| 335 | - ismotion = context == .active || context == .sedentary | 339 | + ismotion = context == .active |
| 336 | } | 340 | } |
| 337 | 341 | ||
| 338 | return NativeHealthRawDataPoint( | 342 | return NativeHealthRawDataPoint( |
| @@ -132,7 +132,7 @@ struct NativeActivityTarget { | @@ -132,7 +132,7 @@ struct NativeActivityTarget { | ||
| 132 | let healthValueTimestamp: TimeInterval? | 132 | let healthValueTimestamp: TimeInterval? |
| 133 | let move: Int? | 133 | let move: Int? |
| 134 | let stand: Int? | 134 | let stand: Int? |
| 135 | - let activityMoveMode: Int? | 135 | +// let activityMoveMode: Int? |
| 136 | // 千卡 | 136 | // 千卡 |
| 137 | let activeEnergyBurned: Double? | 137 | let activeEnergyBurned: Double? |
| 138 | let activeEnergyBurnedGoal: Double? | 138 | let activeEnergyBurnedGoal: Double? |
| @@ -370,14 +370,18 @@ protocol HealthKitRawDataHostApi { | @@ -370,14 +370,18 @@ protocol HealthKitRawDataHostApi { | ||
| 370 | func getHealthKitRawData(dataType: Int64, startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawDataPoint], Error>) -> Void) | 370 | func getHealthKitRawData(dataType: Int64, startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawDataPoint], Error>) -> Void) |
| 371 | /// 原生数据上传 | 371 | /// 原生数据上传 |
| 372 | func performHealthDataUpload(completion: @escaping (Result<Bool, Error>) -> Void) | 372 | func performHealthDataUpload(completion: @escaping (Result<Bool, Error>) -> Void) |
| 373 | + /// 上传心率 - 实时压力数据, 返回上传截止的时间戳, 方便flutter修改数据库 | ||
| 374 | + func performHRDataUpload(sqliteFilePath: String, completion: @escaping (Result<Int64, Error>) -> Void) | ||
| 375 | + /// 上传HRV - HRV压力数据, 返回上传截止的时间戳,方便flutter 修改数据库 | ||
| 376 | + func performHRVDataUpload(sqliteFilePath: String, completion: @escaping (Result<Int64, Error>) -> Void) | ||
| 377 | + /// 上传当前实时压力(统计维度是当天) | ||
| 378 | + func performAvgRealtimeStressDataUpload(sqliteFilePath: String, completion: @escaping (Result<Bool, Error>) -> Void) | ||
| 373 | /// 从从AppleHealth中读取睡眠数据 | 379 | /// 从从AppleHealth中读取睡眠数据 |
| 374 | func getHealthKitRawSleepData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawSleepDataPoint], Error>) -> Void) | 380 | func getHealthKitRawSleepData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawSleepDataPoint], Error>) -> Void) |
| 375 | /// 从从AppleHealth中读取活动统计数据 | 381 | /// 从从AppleHealth中读取活动统计数据 |
| 376 | func getHealthKitRawActivityData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawActivityDataPoint], Error>) -> Void) | 382 | func getHealthKitRawActivityData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawActivityDataPoint], Error>) -> Void) |
| 377 | /// 从从AppleHealth中读取锻炼数据 | 383 | /// 从从AppleHealth中读取锻炼数据 |
| 378 | func getHealthKitRawWorkoutData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawWorkoutDataPoint], Error>) -> Void) | 384 | func getHealthKitRawWorkoutData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawWorkoutDataPoint], Error>) -> Void) |
| 379 | - /// 从从AppleHealth中读取锻炼数据 | ||
| 380 | - func getHealthKitRawMotionData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawMotionDataPoint], Error>) -> Void) | ||
| 381 | } | 385 | } |
| 382 | 386 | ||
| 383 | /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. | 387 | /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. |
| @@ -438,6 +442,60 @@ class HealthKitRawDataHostApiSetup { | @@ -438,6 +442,60 @@ class HealthKitRawDataHostApiSetup { | ||
| 438 | } else { | 442 | } else { |
| 439 | performHealthDataUploadChannel.setMessageHandler(nil) | 443 | performHealthDataUploadChannel.setMessageHandler(nil) |
| 440 | } | 444 | } |
| 445 | + /// 上传心率 - 实时压力数据, 返回上传截止的时间戳, 方便flutter修改数据库 | ||
| 446 | + let performHRDataUploadChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.performHRDataUpload\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 447 | + if let api = api { | ||
| 448 | + performHRDataUploadChannel.setMessageHandler { message, reply in | ||
| 449 | + let args = message as! [Any?] | ||
| 450 | + let sqliteFilePathArg = args[0] as! String | ||
| 451 | + api.performHRDataUpload(sqliteFilePath: sqliteFilePathArg) { result in | ||
| 452 | + switch result { | ||
| 453 | + case .success(let res): | ||
| 454 | + reply(wrapResult(res)) | ||
| 455 | + case .failure(let error): | ||
| 456 | + reply(wrapError(error)) | ||
| 457 | + } | ||
| 458 | + } | ||
| 459 | + } | ||
| 460 | + } else { | ||
| 461 | + performHRDataUploadChannel.setMessageHandler(nil) | ||
| 462 | + } | ||
| 463 | + /// 上传HRV - HRV压力数据, 返回上传截止的时间戳,方便flutter 修改数据库 | ||
| 464 | + let performHRVDataUploadChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.performHRVDataUpload\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 465 | + if let api = api { | ||
| 466 | + performHRVDataUploadChannel.setMessageHandler { message, reply in | ||
| 467 | + let args = message as! [Any?] | ||
| 468 | + let sqliteFilePathArg = args[0] as! String | ||
| 469 | + api.performHRVDataUpload(sqliteFilePath: sqliteFilePathArg) { result in | ||
| 470 | + switch result { | ||
| 471 | + case .success(let res): | ||
| 472 | + reply(wrapResult(res)) | ||
| 473 | + case .failure(let error): | ||
| 474 | + reply(wrapError(error)) | ||
| 475 | + } | ||
| 476 | + } | ||
| 477 | + } | ||
| 478 | + } else { | ||
| 479 | + performHRVDataUploadChannel.setMessageHandler(nil) | ||
| 480 | + } | ||
| 481 | + /// 上传当前实时压力(统计维度是当天) | ||
| 482 | + let performAvgRealtimeStressDataUploadChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.performAvgRealtimeStressDataUpload\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 483 | + if let api = api { | ||
| 484 | + performAvgRealtimeStressDataUploadChannel.setMessageHandler { message, reply in | ||
| 485 | + let args = message as! [Any?] | ||
| 486 | + let sqliteFilePathArg = args[0] as! String | ||
| 487 | + api.performAvgRealtimeStressDataUpload(sqliteFilePath: sqliteFilePathArg) { result in | ||
| 488 | + switch result { | ||
| 489 | + case .success(let res): | ||
| 490 | + reply(wrapResult(res)) | ||
| 491 | + case .failure(let error): | ||
| 492 | + reply(wrapError(error)) | ||
| 493 | + } | ||
| 494 | + } | ||
| 495 | + } | ||
| 496 | + } else { | ||
| 497 | + performAvgRealtimeStressDataUploadChannel.setMessageHandler(nil) | ||
| 498 | + } | ||
| 441 | /// 从从AppleHealth中读取睡眠数据 | 499 | /// 从从AppleHealth中读取睡眠数据 |
| 442 | let getHealthKitRawSleepDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawSleepData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | 500 | let getHealthKitRawSleepDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawSleepData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) |
| 443 | if let api = api { | 501 | if let api = api { |
| @@ -495,24 +553,5 @@ class HealthKitRawDataHostApiSetup { | @@ -495,24 +553,5 @@ class HealthKitRawDataHostApiSetup { | ||
| 495 | } else { | 553 | } else { |
| 496 | getHealthKitRawWorkoutDataChannel.setMessageHandler(nil) | 554 | getHealthKitRawWorkoutDataChannel.setMessageHandler(nil) |
| 497 | } | 555 | } |
| 498 | - /// 从从AppleHealth中读取锻炼数据 | ||
| 499 | - let getHealthKitRawMotionDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawMotionData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 500 | - if let api = api { | ||
| 501 | - getHealthKitRawMotionDataChannel.setMessageHandler { message, reply in | ||
| 502 | - let args = message as! [Any?] | ||
| 503 | - let startTimeArg = args[0] as! Int64 | ||
| 504 | - let endTimeArg = args[1] as! Int64 | ||
| 505 | - api.getHealthKitRawMotionData(startTime: startTimeArg, endTime: endTimeArg) { result in | ||
| 506 | - switch result { | ||
| 507 | - case .success(let res): | ||
| 508 | - reply(wrapResult(res)) | ||
| 509 | - case .failure(let error): | ||
| 510 | - reply(wrapError(error)) | ||
| 511 | - } | ||
| 512 | - } | ||
| 513 | - } | ||
| 514 | - } else { | ||
| 515 | - getHealthKitRawMotionDataChannel.setMessageHandler(nil) | ||
| 516 | - } | ||
| 517 | } | 556 | } |
| 518 | } | 557 | } |
| @@ -20,6 +20,19 @@ final class HealthKitRawDataHostApiImpl: HealthKitRawDataHostApi { | @@ -20,6 +20,19 @@ final class HealthKitRawDataHostApiImpl: HealthKitRawDataHostApi { | ||
| 20 | completion(.success(true)) | 20 | completion(.success(true)) |
| 21 | } | 21 | } |
| 22 | } | 22 | } |
| 23 | + | ||
| 24 | + func performHRDataUpload(sqliteFilePath: String, completion: @escaping (Result<Int64, any Error>) -> Void) { | ||
| 25 | + | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + func performHRVDataUpload(sqliteFilePath: String, completion: @escaping (Result<Int64, any Error>) -> Void) { | ||
| 29 | + | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + func performAvgRealtimeStressDataUpload(sqliteFilePath: String, completion: @escaping (Result<Bool, any Error>) -> Void) { | ||
| 33 | + | ||
| 34 | + } | ||
| 35 | + | ||
| 23 | 36 | ||
| 24 | func getHealthKitRawData( | 37 | func getHealthKitRawData( |
| 25 | dataType: Int64, | 38 | dataType: Int64, |
| @@ -133,10 +146,6 @@ final class HealthKitRawDataHostApiImpl: HealthKitRawDataHostApi { | @@ -133,10 +146,6 @@ final class HealthKitRawDataHostApiImpl: HealthKitRawDataHostApi { | ||
| 133 | } | 146 | } |
| 134 | } | 147 | } |
| 135 | } | 148 | } |
| 136 | - | ||
| 137 | - func getHealthKitRawMotionData(startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawMotionDataPoint], any Error>) -> Void) { | ||
| 138 | - | ||
| 139 | - } | ||
| 140 | 149 | ||
| 141 | func performSleepDataUpload(completion: @escaping (Result<Bool, Error>) -> Void) { | 150 | func performSleepDataUpload(completion: @escaping (Result<Bool, Error>) -> Void) { |
| 142 | Task { | 151 | Task { |
| @@ -107,7 +107,6 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource { | @@ -107,7 +107,6 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource { | ||
| 107 | ActivityBurnStatisticsDataV2 data, | 107 | ActivityBurnStatisticsDataV2 data, |
| 108 | ) { | 108 | ) { |
| 109 | final target = data.activityTargetInfo; | 109 | final target = data.activityTargetInfo; |
| 110 | - final overallValues = _dailyOverallValues(date, data.overallList); | ||
| 111 | final points = <ActivityBurnHeartRatePoint>[ | 110 | final points = <ActivityBurnHeartRatePoint>[ |
| 112 | for (final item in data.hrList ?? const <HrList>[]) | 111 | for (final item in data.hrList ?? const <HrList>[]) |
| 113 | if (_parseDateTime(item.dataTime) case final time?) | 112 | if (_parseDateTime(item.dataTime) case final time?) |
| @@ -120,13 +119,13 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource { | @@ -120,13 +119,13 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource { | ||
| 120 | return ActivityBurnReport( | 119 | return ActivityBurnReport( |
| 121 | date: date, | 120 | date: date, |
| 122 | activeEnergy: | 121 | activeEnergy: |
| 123 | - _metric(overallValues?.move ?? data.totalMove, target?.move), | 122 | + _metric(data.totalMove, target?.move), |
| 124 | exerciseMinutes: _exerciseDurationMetric( | 123 | exerciseMinutes: _exerciseDurationMetric( |
| 125 | - overallValues?.exercise ?? data.totalExercise, | 124 | + data.totalExercise, |
| 126 | target?.exercise, | 125 | target?.exercise, |
| 127 | ), | 126 | ), |
| 128 | standHours: _standDurationMetric( | 127 | standHours: _standDurationMetric( |
| 129 | - overallValues?.stand ?? data.totalStand, | 128 | + data.totalStand, |
| 130 | target?.stand, | 129 | target?.stand, |
| 131 | ), | 130 | ), |
| 132 | heartRate: ActivityBurnHeartRateSummary( | 131 | heartRate: ActivityBurnHeartRateSummary( |
| @@ -137,19 +136,6 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource { | @@ -137,19 +136,6 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource { | ||
| 137 | ); | 136 | ); |
| 138 | } | 137 | } |
| 139 | 138 | ||
| 140 | - Value? _dailyOverallValues( | ||
| 141 | - DateTime date, | ||
| 142 | - List<OverallList>? overallList, | ||
| 143 | - ) { | ||
| 144 | - final items = overallList ?? const <OverallList>[]; | ||
| 145 | - for (final item in items) { | ||
| 146 | - final time = _parseDateTime(item.timeKey); | ||
| 147 | - if (time != null && _normalizeDate(time) == date) return item.value; | ||
| 148 | - } | ||
| 149 | - if (items.length == 1) return items.first.value; | ||
| 150 | - return null; | ||
| 151 | - } | ||
| 152 | - | ||
| 153 | List<ActivityBurnReport> _periodReports( | 139 | List<ActivityBurnReport> _periodReports( |
| 154 | DateTime start, | 140 | DateTime start, |
| 155 | int dayCount, | 141 | int dayCount, |
| @@ -230,7 +230,7 @@ class LocalHealthDataConvert { | @@ -230,7 +230,7 @@ class LocalHealthDataConvert { | ||
| 230 | final data = ActivityBurnStatisticsDataV2( | 230 | final data = ActivityBurnStatisticsDataV2( |
| 231 | totalMove: _sum(daily.map((e) => e.move).toList()), | 231 | totalMove: _sum(daily.map((e) => e.move).toList()), |
| 232 | totalSteps: _sum(daily.map((e) => e.steps).toList()), | 232 | totalSteps: _sum(daily.map((e) => e.steps).toList()), |
| 233 | - totalStand: _sum(daily.map((e) => e.standHours).toList()), | 233 | + totalStand: _sum(daily.map((e) => e.standSeconds).toList()), |
| 234 | totalExercise: _sum(daily.map((e) => e.exerciseSeconds).toList()), | 234 | totalExercise: _sum(daily.map((e) => e.exerciseSeconds).toList()), |
| 235 | avgMove: _averageOrNull(daily.map((e) => e.move).toList()), | 235 | avgMove: _averageOrNull(daily.map((e) => e.move).toList()), |
| 236 | qoqAvgMove: _averageOrNull(previousDaily.map((e) => e.move).toList()), | 236 | qoqAvgMove: _averageOrNull(previousDaily.map((e) => e.move).toList()), |
| @@ -248,7 +248,7 @@ class LocalHealthDataConvert { | @@ -248,7 +248,7 @@ class LocalHealthDataConvert { | ||
| 248 | timeKey: dateKey(item.day), | 248 | timeKey: dateKey(item.day), |
| 249 | value: Value( | 249 | value: Value( |
| 250 | move: item.move, | 250 | move: item.move, |
| 251 | - stand: item.standHours, | 251 | + stand: item.standSeconds, |
| 252 | exercise: item.exerciseSeconds, | 252 | exercise: item.exerciseSeconds, |
| 253 | ), | 253 | ), |
| 254 | ), | 254 | ), |
| @@ -370,7 +370,7 @@ class LocalHealthDataConvert { | @@ -370,7 +370,7 @@ class LocalHealthDataConvert { | ||
| 370 | stand: latestWithGoal?.standHoursGoal?.round(), | 370 | stand: latestWithGoal?.standHoursGoal?.round(), |
| 371 | exercise: latestWithGoal?.exerciseTimeGoal == null | 371 | exercise: latestWithGoal?.exerciseTimeGoal == null |
| 372 | ? null | 372 | ? null |
| 373 | - : (latestWithGoal!.exerciseTimeGoal! * 60).round(), | 373 | + : latestWithGoal!.exerciseTimeGoal!.round(), |
| 374 | updateTime: latestWithGoal?.endTime, | 374 | updateTime: latestWithGoal?.endTime, |
| 375 | sleepTargetDuration: (60 * _sleepGoalMinutes).round(), | 375 | sleepTargetDuration: (60 * _sleepGoalMinutes).round(), |
| 376 | ); | 376 | ); |
| @@ -414,7 +414,7 @@ class LocalHealthDataConvert { | @@ -414,7 +414,7 @@ class LocalHealthDataConvert { | ||
| 414 | )?.round(), | 414 | )?.round(), |
| 415 | move: activitySummary.move.round(), | 415 | move: activitySummary.move.round(), |
| 416 | exercise: activitySummary.exerciseSeconds.round(), | 416 | exercise: activitySummary.exerciseSeconds.round(), |
| 417 | - stand: activitySummary.standHours.round(), | 417 | + stand: activitySummary.standSeconds.round(), |
| 418 | steps: activitySummary.steps.round(), | 418 | steps: activitySummary.steps.round(), |
| 419 | sleepDuration: sleepSummary.durationSeconds.round(), | 419 | sleepDuration: sleepSummary.durationSeconds.round(), |
| 420 | sleepScore: sleepSummary.score?.toDouble(), | 420 | sleepScore: sleepSummary.score?.toDouble(), |
| @@ -598,14 +598,12 @@ class LocalHealthDataConvert { | @@ -598,14 +598,12 @@ class LocalHealthDataConvert { | ||
| 598 | day: day, | 598 | day: day, |
| 599 | move: latest?.activeEnergyBurned ?? 0, | 599 | move: latest?.activeEnergyBurned ?? 0, |
| 600 | steps: latest?.appleMoveTime ?? 0, | 600 | steps: latest?.appleMoveTime ?? 0, |
| 601 | - standHours: latest?.appleStandHours ?? 0, | ||
| 602 | - exerciseSeconds: (latest?.appleExerciseTime ?? 0) * 60, | 601 | + standSeconds: latest?.appleStandHours ?? 0, |
| 602 | + exerciseSeconds: latest?.appleExerciseTime ?? 0, | ||
| 603 | moveGoal: latest?.activeEnergyBurnedGoal, | 603 | moveGoal: latest?.activeEnergyBurnedGoal, |
| 604 | stepGoal: latest?.appleMoveTimeGoal, | 604 | stepGoal: latest?.appleMoveTimeGoal, |
| 605 | standGoal: latest?.standHoursGoal, | 605 | standGoal: latest?.standHoursGoal, |
| 606 | - exerciseGoalSeconds: latest?.exerciseTimeGoal == null | ||
| 607 | - ? null | ||
| 608 | - : latest!.exerciseTimeGoal! * 60, | 606 | + exerciseGoalSeconds: latest?.exerciseTimeGoal, |
| 609 | ); | 607 | ); |
| 610 | } | 608 | } |
| 611 | 609 | ||
| @@ -1024,7 +1022,7 @@ class _ActivityDaySummary { | @@ -1024,7 +1022,7 @@ class _ActivityDaySummary { | ||
| 1024 | required this.day, | 1022 | required this.day, |
| 1025 | required this.move, | 1023 | required this.move, |
| 1026 | required this.steps, | 1024 | required this.steps, |
| 1027 | - required this.standHours, | 1025 | + required this.standSeconds, |
| 1028 | required this.exerciseSeconds, | 1026 | required this.exerciseSeconds, |
| 1029 | required this.moveGoal, | 1027 | required this.moveGoal, |
| 1030 | required this.stepGoal, | 1028 | required this.stepGoal, |
| @@ -1035,7 +1033,7 @@ class _ActivityDaySummary { | @@ -1035,7 +1033,7 @@ class _ActivityDaySummary { | ||
| 1035 | final DateTime day; | 1033 | final DateTime day; |
| 1036 | final num move; | 1034 | final num move; |
| 1037 | final num steps; | 1035 | final num steps; |
| 1038 | - final num standHours; | 1036 | + final num standSeconds; |
| 1039 | final num exerciseSeconds; | 1037 | final num exerciseSeconds; |
| 1040 | final num? moveGoal; | 1038 | final num? moveGoal; |
| 1041 | final num? stepGoal; | 1039 | final num? stepGoal; |
| @@ -467,15 +467,15 @@ class HealthKitRawDataHostApi { | @@ -467,15 +467,15 @@ class HealthKitRawDataHostApi { | ||
| 467 | } | 467 | } |
| 468 | } | 468 | } |
| 469 | 469 | ||
| 470 | - /// 从从AppleHealth中读取睡眠数据 | ||
| 471 | - Future<List<HealthKitRawSleepDataPoint>> getHealthKitRawSleepData(int startTime, int endTime) async { | ||
| 472 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawSleepData$pigeonVar_messageChannelSuffix'; | 470 | + /// 上传心率 - 实时压力数据, 返回上传截止的时间戳, 方便flutter修改数据库 |
| 471 | + Future<int> performHRDataUpload({required String sqliteFilePath}) async { | ||
| 472 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.performHRDataUpload$pigeonVar_messageChannelSuffix'; | ||
| 473 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 473 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( |
| 474 | pigeonVar_channelName, | 474 | pigeonVar_channelName, |
| 475 | pigeonChannelCodec, | 475 | pigeonChannelCodec, |
| 476 | binaryMessenger: pigeonVar_binaryMessenger, | 476 | binaryMessenger: pigeonVar_binaryMessenger, |
| 477 | ); | 477 | ); |
| 478 | - final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[startTime, endTime]); | 478 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[sqliteFilePath]); |
| 479 | final List<Object?>? pigeonVar_replyList = | 479 | final List<Object?>? pigeonVar_replyList = |
| 480 | await pigeonVar_sendFuture as List<Object?>?; | 480 | await pigeonVar_sendFuture as List<Object?>?; |
| 481 | if (pigeonVar_replyList == null) { | 481 | if (pigeonVar_replyList == null) { |
| @@ -492,13 +492,71 @@ class HealthKitRawDataHostApi { | @@ -492,13 +492,71 @@ class HealthKitRawDataHostApi { | ||
| 492 | message: 'Host platform returned null value for non-null return value.', | 492 | message: 'Host platform returned null value for non-null return value.', |
| 493 | ); | 493 | ); |
| 494 | } else { | 494 | } else { |
| 495 | - return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawSleepDataPoint>(); | 495 | + return (pigeonVar_replyList[0] as int?)!; |
| 496 | } | 496 | } |
| 497 | } | 497 | } |
| 498 | 498 | ||
| 499 | - /// 从从AppleHealth中读取活动统计数据 | ||
| 500 | - Future<List<HealthKitRawActivityDataPoint>> getHealthKitRawActivityData(int startTime, int endTime) async { | ||
| 501 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawActivityData$pigeonVar_messageChannelSuffix'; | 499 | + /// 上传HRV - HRV压力数据, 返回上传截止的时间戳,方便flutter 修改数据库 |
| 500 | + Future<int> performHRVDataUpload({required String sqliteFilePath}) async { | ||
| 501 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.performHRVDataUpload$pigeonVar_messageChannelSuffix'; | ||
| 502 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 503 | + pigeonVar_channelName, | ||
| 504 | + pigeonChannelCodec, | ||
| 505 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 506 | + ); | ||
| 507 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[sqliteFilePath]); | ||
| 508 | + final List<Object?>? pigeonVar_replyList = | ||
| 509 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 510 | + if (pigeonVar_replyList == null) { | ||
| 511 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 512 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 513 | + throw PlatformException( | ||
| 514 | + code: pigeonVar_replyList[0]! as String, | ||
| 515 | + message: pigeonVar_replyList[1] as String?, | ||
| 516 | + details: pigeonVar_replyList[2], | ||
| 517 | + ); | ||
| 518 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 519 | + throw PlatformException( | ||
| 520 | + code: 'null-error', | ||
| 521 | + message: 'Host platform returned null value for non-null return value.', | ||
| 522 | + ); | ||
| 523 | + } else { | ||
| 524 | + return (pigeonVar_replyList[0] as int?)!; | ||
| 525 | + } | ||
| 526 | + } | ||
| 527 | + | ||
| 528 | + /// 上传当前实时压力(统计维度是当天) | ||
| 529 | + Future<bool> performAvgRealtimeStressDataUpload({required String sqliteFilePath}) async { | ||
| 530 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.performAvgRealtimeStressDataUpload$pigeonVar_messageChannelSuffix'; | ||
| 531 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 532 | + pigeonVar_channelName, | ||
| 533 | + pigeonChannelCodec, | ||
| 534 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 535 | + ); | ||
| 536 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[sqliteFilePath]); | ||
| 537 | + final List<Object?>? pigeonVar_replyList = | ||
| 538 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 539 | + if (pigeonVar_replyList == null) { | ||
| 540 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 541 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 542 | + throw PlatformException( | ||
| 543 | + code: pigeonVar_replyList[0]! as String, | ||
| 544 | + message: pigeonVar_replyList[1] as String?, | ||
| 545 | + details: pigeonVar_replyList[2], | ||
| 546 | + ); | ||
| 547 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 548 | + throw PlatformException( | ||
| 549 | + code: 'null-error', | ||
| 550 | + message: 'Host platform returned null value for non-null return value.', | ||
| 551 | + ); | ||
| 552 | + } else { | ||
| 553 | + return (pigeonVar_replyList[0] as bool?)!; | ||
| 554 | + } | ||
| 555 | + } | ||
| 556 | + | ||
| 557 | + /// 从从AppleHealth中读取睡眠数据 | ||
| 558 | + Future<List<HealthKitRawSleepDataPoint>> getHealthKitRawSleepData(int startTime, int endTime) async { | ||
| 559 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawSleepData$pigeonVar_messageChannelSuffix'; | ||
| 502 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 560 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( |
| 503 | pigeonVar_channelName, | 561 | pigeonVar_channelName, |
| 504 | pigeonChannelCodec, | 562 | pigeonChannelCodec, |
| @@ -521,13 +579,13 @@ class HealthKitRawDataHostApi { | @@ -521,13 +579,13 @@ class HealthKitRawDataHostApi { | ||
| 521 | message: 'Host platform returned null value for non-null return value.', | 579 | message: 'Host platform returned null value for non-null return value.', |
| 522 | ); | 580 | ); |
| 523 | } else { | 581 | } else { |
| 524 | - return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawActivityDataPoint>(); | 582 | + return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawSleepDataPoint>(); |
| 525 | } | 583 | } |
| 526 | } | 584 | } |
| 527 | 585 | ||
| 528 | - /// 从从AppleHealth中读取锻炼数据 | ||
| 529 | - Future<List<HealthKitRawWorkoutDataPoint>> getHealthKitRawWorkoutData(int startTime, int endTime) async { | ||
| 530 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawWorkoutData$pigeonVar_messageChannelSuffix'; | 586 | + /// 从从AppleHealth中读取活动统计数据 |
| 587 | + Future<List<HealthKitRawActivityDataPoint>> getHealthKitRawActivityData(int startTime, int endTime) async { | ||
| 588 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawActivityData$pigeonVar_messageChannelSuffix'; | ||
| 531 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 589 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( |
| 532 | pigeonVar_channelName, | 590 | pigeonVar_channelName, |
| 533 | pigeonChannelCodec, | 591 | pigeonChannelCodec, |
| @@ -550,13 +608,13 @@ class HealthKitRawDataHostApi { | @@ -550,13 +608,13 @@ class HealthKitRawDataHostApi { | ||
| 550 | message: 'Host platform returned null value for non-null return value.', | 608 | message: 'Host platform returned null value for non-null return value.', |
| 551 | ); | 609 | ); |
| 552 | } else { | 610 | } else { |
| 553 | - return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawWorkoutDataPoint>(); | 611 | + return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawActivityDataPoint>(); |
| 554 | } | 612 | } |
| 555 | } | 613 | } |
| 556 | 614 | ||
| 557 | /// 从从AppleHealth中读取锻炼数据 | 615 | /// 从从AppleHealth中读取锻炼数据 |
| 558 | - Future<List<HealthKitRawMotionDataPoint>> getHealthKitRawMotionData(int startTime, int endTime) async { | ||
| 559 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawMotionData$pigeonVar_messageChannelSuffix'; | 616 | + Future<List<HealthKitRawWorkoutDataPoint>> getHealthKitRawWorkoutData(int startTime, int endTime) async { |
| 617 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawWorkoutData$pigeonVar_messageChannelSuffix'; | ||
| 560 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 618 | final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( |
| 561 | pigeonVar_channelName, | 619 | pigeonVar_channelName, |
| 562 | pigeonChannelCodec, | 620 | pigeonChannelCodec, |
| @@ -579,7 +637,7 @@ class HealthKitRawDataHostApi { | @@ -579,7 +637,7 @@ class HealthKitRawDataHostApi { | ||
| 579 | message: 'Host platform returned null value for non-null return value.', | 637 | message: 'Host platform returned null value for non-null return value.', |
| 580 | ); | 638 | ); |
| 581 | } else { | 639 | } else { |
| 582 | - return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawMotionDataPoint>(); | 640 | + return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HealthKitRawWorkoutDataPoint>(); |
| 583 | } | 641 | } |
| 584 | } | 642 | } |
| 585 | } | 643 | } |
| @@ -102,6 +102,18 @@ abstract class HealthKitRawDataHostApi { | @@ -102,6 +102,18 @@ abstract class HealthKitRawDataHostApi { | ||
| 102 | @async | 102 | @async |
| 103 | bool performHealthDataUpload(); | 103 | bool performHealthDataUpload(); |
| 104 | 104 | ||
| 105 | + /// 上传心率 - 实时压力数据, 返回上传截止的时间戳, 方便flutter修改数据库 | ||
| 106 | + @async | ||
| 107 | + int performHRDataUpload({required String sqliteFilePath}); | ||
| 108 | + | ||
| 109 | + /// 上传HRV - HRV压力数据, 返回上传截止的时间戳,方便flutter 修改数据库 | ||
| 110 | + @async | ||
| 111 | + int performHRVDataUpload({required String sqliteFilePath}); | ||
| 112 | + | ||
| 113 | + /// 上传当前实时压力(统计维度是当天) | ||
| 114 | + @async | ||
| 115 | + bool performAvgRealtimeStressDataUpload({required String sqliteFilePath}); | ||
| 116 | + | ||
| 105 | /// 从从AppleHealth中读取睡眠数据 | 117 | /// 从从AppleHealth中读取睡眠数据 |
| 106 | @async | 118 | @async |
| 107 | List<HealthKitRawSleepDataPoint> getHealthKitRawSleepData( | 119 | List<HealthKitRawSleepDataPoint> getHealthKitRawSleepData( |
| @@ -116,9 +128,4 @@ abstract class HealthKitRawDataHostApi { | @@ -116,9 +128,4 @@ abstract class HealthKitRawDataHostApi { | ||
| 116 | @async | 128 | @async |
| 117 | List<HealthKitRawWorkoutDataPoint> getHealthKitRawWorkoutData( | 129 | List<HealthKitRawWorkoutDataPoint> getHealthKitRawWorkoutData( |
| 118 | int startTime, int endTime); | 130 | int startTime, int endTime); |
| 119 | - | ||
| 120 | - /// 从从AppleHealth中读取锻炼数据 | ||
| 121 | - @async | ||
| 122 | - List<HealthKitRawMotionDataPoint> getHealthKitRawMotionData( | ||
| 123 | - int startTime, int endTime); | ||
| 124 | } | 131 | } |
| @@ -287,4 +287,56 @@ void main() { | @@ -287,4 +287,56 @@ void main() { | ||
| 287 | HealthRawStressState.attention.value); | 287 | HealthRawStressState.attention.value); |
| 288 | }); | 288 | }); |
| 289 | }); | 289 | }); |
| 290 | + | ||
| 291 | + group('LocalHealthDataConvert activity unit conversion', () { | ||
| 292 | + test('passes through exercise and stand seconds from raw activity data', | ||
| 293 | + () { | ||
| 294 | + final day = DateTime(2026, 7, 16); | ||
| 295 | + final endTime = LocalHealthDataConvert.unixSeconds( | ||
| 296 | + day.add(const Duration(hours: 12)), | ||
| 297 | + ); | ||
| 298 | + final activity = [ | ||
| 299 | + HealthKitRawActivityDataPoint( | ||
| 300 | + endTime: endTime, | ||
| 301 | + activeEnergyBurned: 300, | ||
| 302 | + activeEnergyBurnedGoal: 500, | ||
| 303 | + appleMoveTime: 8000, | ||
| 304 | + appleMoveTimeGoal: 10000, | ||
| 305 | + appleExerciseTime: 1800, | ||
| 306 | + exerciseTimeGoal: 3600, | ||
| 307 | + appleStandHours: 21600, | ||
| 308 | + standHoursGoal: 43200, | ||
| 309 | + ), | ||
| 310 | + ]; | ||
| 311 | + | ||
| 312 | + final healthData = LocalHealthDataConvert.v2HealthData( | ||
| 313 | + day: day, | ||
| 314 | + hrvPoints: const [], | ||
| 315 | + activity: activity, | ||
| 316 | + sleepingHeartRate: const [], | ||
| 317 | + restingHeartRate: const [], | ||
| 318 | + sleepIntervals: const [], | ||
| 319 | + ); | ||
| 320 | + final target = LocalHealthDataConvert.v2ActivityTarget( | ||
| 321 | + userId: 1, | ||
| 322 | + activity: activity, | ||
| 323 | + ); | ||
| 324 | + final statistics = LocalHealthDataConvert.activityBurnStatistics( | ||
| 325 | + days: [day], | ||
| 326 | + previousDays: const [], | ||
| 327 | + activity: activity, | ||
| 328 | + heartRate: const [], | ||
| 329 | + sleepIntervals: const [], | ||
| 330 | + ); | ||
| 331 | + | ||
| 332 | + expect(healthData.exercise, 1800); | ||
| 333 | + expect(healthData.stand, 21600); | ||
| 334 | + expect(target.exercise, 3600); | ||
| 335 | + expect(target.stand, 43200); | ||
| 336 | + expect(statistics.totalExercise, 1800); | ||
| 337 | + expect(statistics.totalStand, 21600); | ||
| 338 | + expect(statistics.activityTargetInfo?.exercise, 3600); | ||
| 339 | + expect(statistics.activityTargetInfo?.stand, 43200); | ||
| 340 | + }); | ||
| 341 | + }); | ||
| 290 | } | 342 | } |
-
Please register or login to post a comment