Commit a388346ad0159ecf70fadc7a5498832aaa2c6dd4
1 parent
bcaf2779
feat(ui):初步添加上传数据的api和添加测试AppleHealth数据页面
Showing
46 changed files
with
3042 additions
and
162 deletions
| 1 | package com.doublefeel.app.native | 1 | package com.doublefeel.app.native |
| 2 | 2 | ||
| 3 | import com.doublefeel.app.MainActivity | 3 | import com.doublefeel.app.MainActivity |
| 4 | +import com.doublefeel.app.pigeon.HealthActivityTargetData | ||
| 4 | import com.doublefeel.app.pigeon.HealthKitHostApi | 5 | import com.doublefeel.app.pigeon.HealthKitHostApi |
| 6 | +import com.doublefeel.app.pigeon.HealthSleepUploadDataPoint | ||
| 7 | +import com.doublefeel.app.pigeon.HealthUploadDataPoint | ||
| 5 | import com.doublefeel.app.pigeon.HealthUploadResult | 8 | import com.doublefeel.app.pigeon.HealthUploadResult |
| 6 | 9 | ||
| 7 | class HealthKitHostApiImpl( | 10 | class HealthKitHostApiImpl( |
| @@ -31,4 +34,34 @@ class HealthKitHostApiImpl( | @@ -31,4 +34,34 @@ class HealthKitHostApiImpl( | ||
| 31 | errorMessage = "Native bulk upload runs via Dart HealthKitUploadService; HMS read bridge pending", | 34 | errorMessage = "Native bulk upload runs via Dart HealthKitUploadService; HMS read bridge pending", |
| 32 | ) | 35 | ) |
| 33 | } | 36 | } |
| 37 | + | ||
| 38 | + override fun fetchHrvData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 39 | + | ||
| 40 | + override fun fetchHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 41 | + | ||
| 42 | + override fun fetchWalkingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 43 | + | ||
| 44 | + override fun fetchRestingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 45 | + | ||
| 46 | + override fun fetchSleepingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 47 | + | ||
| 48 | + override fun fetchOxygenSaturationData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 49 | + | ||
| 50 | + override fun fetchActiveEnergyData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 51 | + | ||
| 52 | + override fun fetchExerciseData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 53 | + | ||
| 54 | + override fun fetchStandData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 55 | + | ||
| 56 | + override fun fetchStepCountData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 57 | + | ||
| 58 | + override fun fetchSleepData(startTime: Long, endTime: Long): List<HealthSleepUploadDataPoint> = emptyList() | ||
| 59 | + | ||
| 60 | + override fun fetchSleepingWristTemperatureData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 61 | + | ||
| 62 | + override fun fetchRespiratoryRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 63 | + | ||
| 64 | + override fun fetchIrregularHeartRhythmData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList() | ||
| 65 | + | ||
| 66 | + override fun fetchActivityTargetData(startTime: Long, endTime: Long): HealthActivityTargetData? = null | ||
| 34 | } | 67 | } |
| @@ -112,6 +112,105 @@ data class HealthUploadResult ( | @@ -112,6 +112,105 @@ data class HealthUploadResult ( | ||
| 112 | 112 | ||
| 113 | override fun hashCode(): Int = toList().hashCode() | 113 | override fun hashCode(): Int = toList().hashCode() |
| 114 | } | 114 | } |
| 115 | + | ||
| 116 | +/** Generated class from Pigeon that represents data sent in messages. */ | ||
| 117 | +data class HealthUploadDataPoint ( | ||
| 118 | + val dataType: Long, | ||
| 119 | + val time: Long, | ||
| 120 | + val value: Double | ||
| 121 | +) | ||
| 122 | + { | ||
| 123 | + companion object { | ||
| 124 | + fun fromList(pigeonVar_list: List<Any?>): HealthUploadDataPoint { | ||
| 125 | + val dataType = pigeonVar_list[0] as Long | ||
| 126 | + val time = pigeonVar_list[1] as Long | ||
| 127 | + val value = pigeonVar_list[2] as Double | ||
| 128 | + return HealthUploadDataPoint(dataType, time, value) | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + fun toList(): List<Any?> { | ||
| 132 | + return listOf( | ||
| 133 | + dataType, | ||
| 134 | + time, | ||
| 135 | + value, | ||
| 136 | + ) | ||
| 137 | + } | ||
| 138 | + override fun equals(other: Any?): Boolean { | ||
| 139 | + if (other !is HealthUploadDataPoint) { | ||
| 140 | + return false | ||
| 141 | + } | ||
| 142 | + if (this === other) { | ||
| 143 | + return true | ||
| 144 | + } | ||
| 145 | + return HealthKitApiPigeonUtils.deepEquals(toList(), other.toList()) } | ||
| 146 | + | ||
| 147 | + override fun hashCode(): Int = toList().hashCode() | ||
| 148 | +} | ||
| 149 | + | ||
| 150 | +/** Generated class from Pigeon that represents data sent in messages. */ | ||
| 151 | +data class HealthSleepUploadDataPoint ( | ||
| 152 | + val dataType: Long, | ||
| 153 | + val fromTime: Long, | ||
| 154 | + val toTime: Long | ||
| 155 | +) | ||
| 156 | + { | ||
| 157 | + companion object { | ||
| 158 | + fun fromList(pigeonVar_list: List<Any?>): HealthSleepUploadDataPoint { | ||
| 159 | + val dataType = pigeonVar_list[0] as Long | ||
| 160 | + val fromTime = pigeonVar_list[1] as Long | ||
| 161 | + val toTime = pigeonVar_list[2] as Long | ||
| 162 | + return HealthSleepUploadDataPoint(dataType, fromTime, toTime) | ||
| 163 | + } | ||
| 164 | + } | ||
| 165 | + fun toList(): List<Any?> { | ||
| 166 | + return listOf( | ||
| 167 | + dataType, | ||
| 168 | + fromTime, | ||
| 169 | + toTime, | ||
| 170 | + ) | ||
| 171 | + } | ||
| 172 | + override fun equals(other: Any?): Boolean { | ||
| 173 | + if (other !is HealthSleepUploadDataPoint) { | ||
| 174 | + return false | ||
| 175 | + } | ||
| 176 | + if (this === other) { | ||
| 177 | + return true | ||
| 178 | + } | ||
| 179 | + return HealthKitApiPigeonUtils.deepEquals(toList(), other.toList()) } | ||
| 180 | + | ||
| 181 | + override fun hashCode(): Int = toList().hashCode() | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +/** Generated class from Pigeon that represents data sent in messages. */ | ||
| 185 | +data class HealthActivityTargetData ( | ||
| 186 | + val move: Long? = null, | ||
| 187 | + val stand: Long? = null | ||
| 188 | +) | ||
| 189 | + { | ||
| 190 | + companion object { | ||
| 191 | + fun fromList(pigeonVar_list: List<Any?>): HealthActivityTargetData { | ||
| 192 | + val move = pigeonVar_list[0] as Long? | ||
| 193 | + val stand = pigeonVar_list[1] as Long? | ||
| 194 | + return HealthActivityTargetData(move, stand) | ||
| 195 | + } | ||
| 196 | + } | ||
| 197 | + fun toList(): List<Any?> { | ||
| 198 | + return listOf( | ||
| 199 | + move, | ||
| 200 | + stand, | ||
| 201 | + ) | ||
| 202 | + } | ||
| 203 | + override fun equals(other: Any?): Boolean { | ||
| 204 | + if (other !is HealthActivityTargetData) { | ||
| 205 | + return false | ||
| 206 | + } | ||
| 207 | + if (this === other) { | ||
| 208 | + return true | ||
| 209 | + } | ||
| 210 | + return HealthKitApiPigeonUtils.deepEquals(toList(), other.toList()) } | ||
| 211 | + | ||
| 212 | + override fun hashCode(): Int = toList().hashCode() | ||
| 213 | +} | ||
| 115 | private open class HealthKitApiPigeonCodec : StandardMessageCodec() { | 214 | private open class HealthKitApiPigeonCodec : StandardMessageCodec() { |
| 116 | override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { | 215 | override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { |
| 117 | return when (type) { | 216 | return when (type) { |
| @@ -120,6 +219,21 @@ private open class HealthKitApiPigeonCodec : StandardMessageCodec() { | @@ -120,6 +219,21 @@ private open class HealthKitApiPigeonCodec : StandardMessageCodec() { | ||
| 120 | HealthUploadResult.fromList(it) | 219 | HealthUploadResult.fromList(it) |
| 121 | } | 220 | } |
| 122 | } | 221 | } |
| 222 | + 130.toByte() -> { | ||
| 223 | + return (readValue(buffer) as? List<Any?>)?.let { | ||
| 224 | + HealthUploadDataPoint.fromList(it) | ||
| 225 | + } | ||
| 226 | + } | ||
| 227 | + 131.toByte() -> { | ||
| 228 | + return (readValue(buffer) as? List<Any?>)?.let { | ||
| 229 | + HealthSleepUploadDataPoint.fromList(it) | ||
| 230 | + } | ||
| 231 | + } | ||
| 232 | + 132.toByte() -> { | ||
| 233 | + return (readValue(buffer) as? List<Any?>)?.let { | ||
| 234 | + HealthActivityTargetData.fromList(it) | ||
| 235 | + } | ||
| 236 | + } | ||
| 123 | else -> super.readValueOfType(type, buffer) | 237 | else -> super.readValueOfType(type, buffer) |
| 124 | } | 238 | } |
| 125 | } | 239 | } |
| @@ -129,6 +243,18 @@ private open class HealthKitApiPigeonCodec : StandardMessageCodec() { | @@ -129,6 +243,18 @@ private open class HealthKitApiPigeonCodec : StandardMessageCodec() { | ||
| 129 | stream.write(129) | 243 | stream.write(129) |
| 130 | writeValue(stream, value.toList()) | 244 | writeValue(stream, value.toList()) |
| 131 | } | 245 | } |
| 246 | + is HealthUploadDataPoint -> { | ||
| 247 | + stream.write(130) | ||
| 248 | + writeValue(stream, value.toList()) | ||
| 249 | + } | ||
| 250 | + is HealthSleepUploadDataPoint -> { | ||
| 251 | + stream.write(131) | ||
| 252 | + writeValue(stream, value.toList()) | ||
| 253 | + } | ||
| 254 | + is HealthActivityTargetData -> { | ||
| 255 | + stream.write(132) | ||
| 256 | + writeValue(stream, value.toList()) | ||
| 257 | + } | ||
| 132 | else -> super.writeValue(stream, value) | 258 | else -> super.writeValue(stream, value) |
| 133 | } | 259 | } |
| 134 | } | 260 | } |
| @@ -143,6 +269,21 @@ interface HealthKitHostApi { | @@ -143,6 +269,21 @@ interface HealthKitHostApi { | ||
| 143 | fun cancelHealthAppAuthorization(): Boolean | 269 | fun cancelHealthAppAuthorization(): Boolean |
| 144 | /** Runs native health read and server upload pipeline. */ | 270 | /** Runs native health read and server upload pipeline. */ |
| 145 | fun performHealthUpload(): HealthUploadResult | 271 | fun performHealthUpload(): HealthUploadResult |
| 272 | + fun fetchHrvData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 273 | + fun fetchHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 274 | + fun fetchWalkingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 275 | + fun fetchRestingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 276 | + fun fetchSleepingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 277 | + fun fetchOxygenSaturationData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 278 | + fun fetchActiveEnergyData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 279 | + fun fetchExerciseData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 280 | + fun fetchStandData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 281 | + fun fetchStepCountData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 282 | + fun fetchSleepData(startTime: Long, endTime: Long): List<HealthSleepUploadDataPoint> | ||
| 283 | + fun fetchSleepingWristTemperatureData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 284 | + fun fetchRespiratoryRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 285 | + fun fetchIrregularHeartRhythmData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> | ||
| 286 | + fun fetchActivityTargetData(startTime: Long, endTime: Long): HealthActivityTargetData? | ||
| 146 | 287 | ||
| 147 | companion object { | 288 | companion object { |
| 148 | /** The codec used by HealthKitHostApi. */ | 289 | /** The codec used by HealthKitHostApi. */ |
| @@ -228,6 +369,276 @@ interface HealthKitHostApi { | @@ -228,6 +369,276 @@ interface HealthKitHostApi { | ||
| 228 | channel.setMessageHandler(null) | 369 | channel.setMessageHandler(null) |
| 229 | } | 370 | } |
| 230 | } | 371 | } |
| 372 | + run { | ||
| 373 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHrvData$separatedMessageChannelSuffix", codec) | ||
| 374 | + if (api != null) { | ||
| 375 | + channel.setMessageHandler { message, reply -> | ||
| 376 | + val args = message as List<Any?> | ||
| 377 | + val startTimeArg = args[0] as Long | ||
| 378 | + val endTimeArg = args[1] as Long | ||
| 379 | + val wrapped: List<Any?> = try { | ||
| 380 | + listOf(api.fetchHrvData(startTimeArg, endTimeArg)) | ||
| 381 | + } catch (exception: Throwable) { | ||
| 382 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 383 | + } | ||
| 384 | + reply.reply(wrapped) | ||
| 385 | + } | ||
| 386 | + } else { | ||
| 387 | + channel.setMessageHandler(null) | ||
| 388 | + } | ||
| 389 | + } | ||
| 390 | + run { | ||
| 391 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHeartRateData$separatedMessageChannelSuffix", codec) | ||
| 392 | + if (api != null) { | ||
| 393 | + channel.setMessageHandler { message, reply -> | ||
| 394 | + val args = message as List<Any?> | ||
| 395 | + val startTimeArg = args[0] as Long | ||
| 396 | + val endTimeArg = args[1] as Long | ||
| 397 | + val wrapped: List<Any?> = try { | ||
| 398 | + listOf(api.fetchHeartRateData(startTimeArg, endTimeArg)) | ||
| 399 | + } catch (exception: Throwable) { | ||
| 400 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 401 | + } | ||
| 402 | + reply.reply(wrapped) | ||
| 403 | + } | ||
| 404 | + } else { | ||
| 405 | + channel.setMessageHandler(null) | ||
| 406 | + } | ||
| 407 | + } | ||
| 408 | + run { | ||
| 409 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchWalkingHeartRateData$separatedMessageChannelSuffix", codec) | ||
| 410 | + if (api != null) { | ||
| 411 | + channel.setMessageHandler { message, reply -> | ||
| 412 | + val args = message as List<Any?> | ||
| 413 | + val startTimeArg = args[0] as Long | ||
| 414 | + val endTimeArg = args[1] as Long | ||
| 415 | + val wrapped: List<Any?> = try { | ||
| 416 | + listOf(api.fetchWalkingHeartRateData(startTimeArg, endTimeArg)) | ||
| 417 | + } catch (exception: Throwable) { | ||
| 418 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 419 | + } | ||
| 420 | + reply.reply(wrapped) | ||
| 421 | + } | ||
| 422 | + } else { | ||
| 423 | + channel.setMessageHandler(null) | ||
| 424 | + } | ||
| 425 | + } | ||
| 426 | + run { | ||
| 427 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRestingHeartRateData$separatedMessageChannelSuffix", codec) | ||
| 428 | + if (api != null) { | ||
| 429 | + channel.setMessageHandler { message, reply -> | ||
| 430 | + val args = message as List<Any?> | ||
| 431 | + val startTimeArg = args[0] as Long | ||
| 432 | + val endTimeArg = args[1] as Long | ||
| 433 | + val wrapped: List<Any?> = try { | ||
| 434 | + listOf(api.fetchRestingHeartRateData(startTimeArg, endTimeArg)) | ||
| 435 | + } catch (exception: Throwable) { | ||
| 436 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 437 | + } | ||
| 438 | + reply.reply(wrapped) | ||
| 439 | + } | ||
| 440 | + } else { | ||
| 441 | + channel.setMessageHandler(null) | ||
| 442 | + } | ||
| 443 | + } | ||
| 444 | + run { | ||
| 445 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingHeartRateData$separatedMessageChannelSuffix", codec) | ||
| 446 | + if (api != null) { | ||
| 447 | + channel.setMessageHandler { message, reply -> | ||
| 448 | + val args = message as List<Any?> | ||
| 449 | + val startTimeArg = args[0] as Long | ||
| 450 | + val endTimeArg = args[1] as Long | ||
| 451 | + val wrapped: List<Any?> = try { | ||
| 452 | + listOf(api.fetchSleepingHeartRateData(startTimeArg, endTimeArg)) | ||
| 453 | + } catch (exception: Throwable) { | ||
| 454 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 455 | + } | ||
| 456 | + reply.reply(wrapped) | ||
| 457 | + } | ||
| 458 | + } else { | ||
| 459 | + channel.setMessageHandler(null) | ||
| 460 | + } | ||
| 461 | + } | ||
| 462 | + run { | ||
| 463 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchOxygenSaturationData$separatedMessageChannelSuffix", codec) | ||
| 464 | + if (api != null) { | ||
| 465 | + channel.setMessageHandler { message, reply -> | ||
| 466 | + val args = message as List<Any?> | ||
| 467 | + val startTimeArg = args[0] as Long | ||
| 468 | + val endTimeArg = args[1] as Long | ||
| 469 | + val wrapped: List<Any?> = try { | ||
| 470 | + listOf(api.fetchOxygenSaturationData(startTimeArg, endTimeArg)) | ||
| 471 | + } catch (exception: Throwable) { | ||
| 472 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 473 | + } | ||
| 474 | + reply.reply(wrapped) | ||
| 475 | + } | ||
| 476 | + } else { | ||
| 477 | + channel.setMessageHandler(null) | ||
| 478 | + } | ||
| 479 | + } | ||
| 480 | + run { | ||
| 481 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActiveEnergyData$separatedMessageChannelSuffix", codec) | ||
| 482 | + if (api != null) { | ||
| 483 | + channel.setMessageHandler { message, reply -> | ||
| 484 | + val args = message as List<Any?> | ||
| 485 | + val startTimeArg = args[0] as Long | ||
| 486 | + val endTimeArg = args[1] as Long | ||
| 487 | + val wrapped: List<Any?> = try { | ||
| 488 | + listOf(api.fetchActiveEnergyData(startTimeArg, endTimeArg)) | ||
| 489 | + } catch (exception: Throwable) { | ||
| 490 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 491 | + } | ||
| 492 | + reply.reply(wrapped) | ||
| 493 | + } | ||
| 494 | + } else { | ||
| 495 | + channel.setMessageHandler(null) | ||
| 496 | + } | ||
| 497 | + } | ||
| 498 | + run { | ||
| 499 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchExerciseData$separatedMessageChannelSuffix", codec) | ||
| 500 | + if (api != null) { | ||
| 501 | + channel.setMessageHandler { message, reply -> | ||
| 502 | + val args = message as List<Any?> | ||
| 503 | + val startTimeArg = args[0] as Long | ||
| 504 | + val endTimeArg = args[1] as Long | ||
| 505 | + val wrapped: List<Any?> = try { | ||
| 506 | + listOf(api.fetchExerciseData(startTimeArg, endTimeArg)) | ||
| 507 | + } catch (exception: Throwable) { | ||
| 508 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 509 | + } | ||
| 510 | + reply.reply(wrapped) | ||
| 511 | + } | ||
| 512 | + } else { | ||
| 513 | + channel.setMessageHandler(null) | ||
| 514 | + } | ||
| 515 | + } | ||
| 516 | + run { | ||
| 517 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStandData$separatedMessageChannelSuffix", codec) | ||
| 518 | + if (api != null) { | ||
| 519 | + channel.setMessageHandler { message, reply -> | ||
| 520 | + val args = message as List<Any?> | ||
| 521 | + val startTimeArg = args[0] as Long | ||
| 522 | + val endTimeArg = args[1] as Long | ||
| 523 | + val wrapped: List<Any?> = try { | ||
| 524 | + listOf(api.fetchStandData(startTimeArg, endTimeArg)) | ||
| 525 | + } catch (exception: Throwable) { | ||
| 526 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 527 | + } | ||
| 528 | + reply.reply(wrapped) | ||
| 529 | + } | ||
| 530 | + } else { | ||
| 531 | + channel.setMessageHandler(null) | ||
| 532 | + } | ||
| 533 | + } | ||
| 534 | + run { | ||
| 535 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStepCountData$separatedMessageChannelSuffix", codec) | ||
| 536 | + if (api != null) { | ||
| 537 | + channel.setMessageHandler { message, reply -> | ||
| 538 | + val args = message as List<Any?> | ||
| 539 | + val startTimeArg = args[0] as Long | ||
| 540 | + val endTimeArg = args[1] as Long | ||
| 541 | + val wrapped: List<Any?> = try { | ||
| 542 | + listOf(api.fetchStepCountData(startTimeArg, endTimeArg)) | ||
| 543 | + } catch (exception: Throwable) { | ||
| 544 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 545 | + } | ||
| 546 | + reply.reply(wrapped) | ||
| 547 | + } | ||
| 548 | + } else { | ||
| 549 | + channel.setMessageHandler(null) | ||
| 550 | + } | ||
| 551 | + } | ||
| 552 | + run { | ||
| 553 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepData$separatedMessageChannelSuffix", codec) | ||
| 554 | + if (api != null) { | ||
| 555 | + channel.setMessageHandler { message, reply -> | ||
| 556 | + val args = message as List<Any?> | ||
| 557 | + val startTimeArg = args[0] as Long | ||
| 558 | + val endTimeArg = args[1] as Long | ||
| 559 | + val wrapped: List<Any?> = try { | ||
| 560 | + listOf(api.fetchSleepData(startTimeArg, endTimeArg)) | ||
| 561 | + } catch (exception: Throwable) { | ||
| 562 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 563 | + } | ||
| 564 | + reply.reply(wrapped) | ||
| 565 | + } | ||
| 566 | + } else { | ||
| 567 | + channel.setMessageHandler(null) | ||
| 568 | + } | ||
| 569 | + } | ||
| 570 | + run { | ||
| 571 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingWristTemperatureData$separatedMessageChannelSuffix", codec) | ||
| 572 | + if (api != null) { | ||
| 573 | + channel.setMessageHandler { message, reply -> | ||
| 574 | + val args = message as List<Any?> | ||
| 575 | + val startTimeArg = args[0] as Long | ||
| 576 | + val endTimeArg = args[1] as Long | ||
| 577 | + val wrapped: List<Any?> = try { | ||
| 578 | + listOf(api.fetchSleepingWristTemperatureData(startTimeArg, endTimeArg)) | ||
| 579 | + } catch (exception: Throwable) { | ||
| 580 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 581 | + } | ||
| 582 | + reply.reply(wrapped) | ||
| 583 | + } | ||
| 584 | + } else { | ||
| 585 | + channel.setMessageHandler(null) | ||
| 586 | + } | ||
| 587 | + } | ||
| 588 | + run { | ||
| 589 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRespiratoryRateData$separatedMessageChannelSuffix", codec) | ||
| 590 | + if (api != null) { | ||
| 591 | + channel.setMessageHandler { message, reply -> | ||
| 592 | + val args = message as List<Any?> | ||
| 593 | + val startTimeArg = args[0] as Long | ||
| 594 | + val endTimeArg = args[1] as Long | ||
| 595 | + val wrapped: List<Any?> = try { | ||
| 596 | + listOf(api.fetchRespiratoryRateData(startTimeArg, endTimeArg)) | ||
| 597 | + } catch (exception: Throwable) { | ||
| 598 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 599 | + } | ||
| 600 | + reply.reply(wrapped) | ||
| 601 | + } | ||
| 602 | + } else { | ||
| 603 | + channel.setMessageHandler(null) | ||
| 604 | + } | ||
| 605 | + } | ||
| 606 | + run { | ||
| 607 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchIrregularHeartRhythmData$separatedMessageChannelSuffix", codec) | ||
| 608 | + if (api != null) { | ||
| 609 | + channel.setMessageHandler { message, reply -> | ||
| 610 | + val args = message as List<Any?> | ||
| 611 | + val startTimeArg = args[0] as Long | ||
| 612 | + val endTimeArg = args[1] as Long | ||
| 613 | + val wrapped: List<Any?> = try { | ||
| 614 | + listOf(api.fetchIrregularHeartRhythmData(startTimeArg, endTimeArg)) | ||
| 615 | + } catch (exception: Throwable) { | ||
| 616 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 617 | + } | ||
| 618 | + reply.reply(wrapped) | ||
| 619 | + } | ||
| 620 | + } else { | ||
| 621 | + channel.setMessageHandler(null) | ||
| 622 | + } | ||
| 623 | + } | ||
| 624 | + run { | ||
| 625 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActivityTargetData$separatedMessageChannelSuffix", codec) | ||
| 626 | + if (api != null) { | ||
| 627 | + channel.setMessageHandler { message, reply -> | ||
| 628 | + val args = message as List<Any?> | ||
| 629 | + val startTimeArg = args[0] as Long | ||
| 630 | + val endTimeArg = args[1] as Long | ||
| 631 | + val wrapped: List<Any?> = try { | ||
| 632 | + listOf(api.fetchActivityTargetData(startTimeArg, endTimeArg)) | ||
| 633 | + } catch (exception: Throwable) { | ||
| 634 | + HealthKitApiPigeonUtils.wrapError(exception) | ||
| 635 | + } | ||
| 636 | + reply.reply(wrapped) | ||
| 637 | + } | ||
| 638 | + } else { | ||
| 639 | + channel.setMessageHandler(null) | ||
| 640 | + } | ||
| 641 | + } | ||
| 231 | } | 642 | } |
| 232 | } | 643 | } |
| 233 | } | 644 | } |
| @@ -141,6 +141,11 @@ interface WearEngineHostApi { | @@ -141,6 +141,11 @@ interface WearEngineHostApi { | ||
| 141 | fun registerMessageReceiver(): Boolean | 141 | fun registerMessageReceiver(): Boolean |
| 142 | fun sendTextMessage(message: String): Boolean | 142 | fun sendTextMessage(message: String): Boolean |
| 143 | fun sendWatchSyncPayload(jsonPayload: String): Boolean | 143 | fun sendWatchSyncPayload(jsonPayload: String): Boolean |
| 144 | + /** | ||
| 145 | + * Opens the system photo picker and returns a local PNG file path after | ||
| 146 | + * removing the image background on the host platform. | ||
| 147 | + */ | ||
| 148 | + fun pickImageAndRemoveBackground(): String? | ||
| 144 | 149 | ||
| 145 | companion object { | 150 | companion object { |
| 146 | /** The codec used by WearEngineHostApi. */ | 151 | /** The codec used by WearEngineHostApi. */ |
| @@ -230,6 +235,21 @@ interface WearEngineHostApi { | @@ -230,6 +235,21 @@ interface WearEngineHostApi { | ||
| 230 | channel.setMessageHandler(null) | 235 | channel.setMessageHandler(null) |
| 231 | } | 236 | } |
| 232 | } | 237 | } |
| 238 | + run { | ||
| 239 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground$separatedMessageChannelSuffix", codec) | ||
| 240 | + if (api != null) { | ||
| 241 | + channel.setMessageHandler { _, reply -> | ||
| 242 | + val wrapped: List<Any?> = try { | ||
| 243 | + listOf(api.pickImageAndRemoveBackground()) | ||
| 244 | + } catch (exception: Throwable) { | ||
| 245 | + WearEngineApiPigeonUtils.wrapError(exception) | ||
| 246 | + } | ||
| 247 | + reply.reply(wrapped) | ||
| 248 | + } | ||
| 249 | + } else { | ||
| 250 | + channel.setMessageHandler(null) | ||
| 251 | + } | ||
| 252 | + } | ||
| 233 | } | 253 | } |
| 234 | } | 254 | } |
| 235 | } | 255 | } |
| 1 | import SwiftUI | 1 | import SwiftUI |
| 2 | -import UIKit | ||
| 3 | 2 | ||
| 4 | struct DFDefaultWatchHomeView: View { | 3 | struct DFDefaultWatchHomeView: View { |
| 5 | - @State private var batteryLevel: Float = UIDevice.current.batteryLevel | ||
| 6 | - @State private var batteryState: UIDevice.BatteryState = UIDevice.current.batteryState | ||
| 7 | - | ||
| 8 | @State private var weekday: String = "" | 4 | @State private var weekday: String = "" |
| 9 | @State private var day: String = "" | 5 | @State private var day: String = "" |
| 10 | 6 | ||
| 11 | var hasData: Bool = false | 7 | var hasData: Bool = false |
| 12 | var hrvValue: String = "38ms" | 8 | var hrvValue: String = "38ms" |
| 13 | var hrvTime: String = "19:04" | 9 | var hrvTime: String = "19:04" |
| 14 | - | ||
| 15 | - var batteryColor: Color{ | ||
| 16 | - if batteryLevel < 0.1{ | ||
| 17 | - // 0 | ||
| 18 | - return Color(hex: "#FF0000") | ||
| 19 | - }else if batteryLevel < 40{ | ||
| 20 | - // 25 | ||
| 21 | - return Color(hex: "#FF9A6E") | ||
| 22 | - }else if batteryLevel < 60{ | ||
| 23 | - // 50 | ||
| 24 | - return Color(hex: "#7B9BFB") | ||
| 25 | - }else if batteryLevel < 80{ | ||
| 26 | - // 75 | ||
| 27 | - return Color(hex: "#3BD49D") | ||
| 28 | - }else{ | ||
| 29 | - // 100 | ||
| 30 | - return Color(hex: "#3BD49D") | ||
| 31 | - } | ||
| 32 | - } | ||
| 33 | - var batteryImageName: String{ | ||
| 34 | - if batteryLevel < 0.1{ | ||
| 35 | - // 0 | ||
| 36 | - return "battery.0percent" | ||
| 37 | - }else if batteryLevel < 40{ | ||
| 38 | - // 25 | ||
| 39 | - return "battery.25percent" | ||
| 40 | - }else if batteryLevel < 60{ | ||
| 41 | - // 50 | ||
| 42 | - return "battery.50percent" | ||
| 43 | - }else if batteryLevel < 80{ | ||
| 44 | - // 75 | ||
| 45 | - return "battery.75percent" | ||
| 46 | - }else{ | ||
| 47 | - // 100 | ||
| 48 | - return "battery.100percent" | ||
| 49 | - } | ||
| 50 | - } | ||
| 51 | 10 | ||
| 52 | var body: some View { | 11 | var body: some View { |
| 53 | VStack{ | 12 | VStack{ |
| 54 | HStack{ | 13 | HStack{ |
| 55 | - Image(systemName: batteryImageName) | ||
| 56 | - .frame(height: 20) | ||
| 57 | - .tint(batteryColor) | ||
| 58 | Spacer() | 14 | Spacer() |
| 59 | Text(weekday) | 15 | Text(weekday) |
| 60 | .font(.system(size: 16, weight: .medium)) | 16 | .font(.system(size: 16, weight: .medium)) |
| @@ -98,30 +54,9 @@ struct DFDefaultWatchHomeView: View { | @@ -98,30 +54,9 @@ struct DFDefaultWatchHomeView: View { | ||
| 98 | .padding(.all, 15) | 54 | .padding(.all, 15) |
| 99 | .onAppear { | 55 | .onAppear { |
| 100 | updateDate() | 56 | updateDate() |
| 101 | - updateBattery() | ||
| 102 | - NotificationCenter.default.addObserver( | ||
| 103 | - forName: UIDevice.batteryLevelDidChangeNotification, | ||
| 104 | - object: nil, | ||
| 105 | - queue: .main | ||
| 106 | - ) { _ in | ||
| 107 | - updateBattery() | ||
| 108 | - } | ||
| 109 | - | ||
| 110 | - NotificationCenter.default.addObserver( | ||
| 111 | - forName: UIDevice.batteryStateDidChangeNotification, | ||
| 112 | - object: nil, | ||
| 113 | - queue: .main | ||
| 114 | - ) { _ in | ||
| 115 | - updateBattery() | ||
| 116 | - } | ||
| 117 | } | 57 | } |
| 118 | } | 58 | } |
| 119 | - | ||
| 120 | - private func updateBattery() { | ||
| 121 | - UIDevice.current.isBatteryMonitoringEnabled = true | ||
| 122 | - batteryLevel = UIDevice.current.batteryLevel | ||
| 123 | - batteryState = UIDevice.current.batteryState | ||
| 124 | - } | 59 | + |
| 125 | private func updateDate(){ | 60 | private func updateDate(){ |
| 126 | let calendar = Calendar.current | 61 | let calendar = Calendar.current |
| 127 | let component = calendar.component(.weekday, from: .now) | 62 | let component = calendar.component(.weekday, from: .now) |
| @@ -3,15 +3,15 @@ | @@ -3,15 +3,15 @@ | ||
| 3 | archiveVersion = 1; | 3 | archiveVersion = 1; |
| 4 | classes = { | 4 | classes = { |
| 5 | }; | 5 | }; |
| 6 | - objectVersion = 77; | 6 | + objectVersion = 54; |
| 7 | objects = { | 7 | objects = { |
| 8 | 8 | ||
| 9 | /* Begin PBXBuildFile section */ | 9 | /* Begin PBXBuildFile section */ |
| 10 | 0798E27DB95F2A823881D75A /* Pods_Runner_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A488EFC0BD642ED604C4E43 /* Pods_Runner_Watch_App.framework */; }; | 10 | 0798E27DB95F2A823881D75A /* Pods_Runner_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A488EFC0BD642ED604C4E43 /* Pods_Runner_Watch_App.framework */; }; |
| 11 | 20A3C4994C9B0F60D1F3BCBE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8DC48B3736B7FEB548D0E65 /* Pods_Runner.framework */; }; | 11 | 20A3C4994C9B0F60D1F3BCBE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8DC48B3736B7FEB548D0E65 /* Pods_Runner.framework */; }; |
| 12 | - 66FB19002FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */; }; | ||
| 13 | - 66FB19012FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */; }; | ||
| 14 | - 66FB19022FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */; }; | 12 | + 66FB19002FDBC80100F515B4 /* DFWatchFigmaTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */; }; |
| 13 | + 66FB19012FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */; }; | ||
| 14 | + 66FB19022FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */; }; | ||
| 15 | 66FBE58C2FDA4F0F00F515B4 /* Runner Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; | 15 | 66FBE58C2FDA4F0F00F515B4 /* Runner Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; |
| 16 | /* End PBXBuildFile section */ | 16 | /* End PBXBuildFile section */ |
| 17 | 17 | ||
| @@ -54,9 +54,9 @@ | @@ -54,9 +54,9 @@ | ||
| 54 | 27E4D5A8995AB1EC91E7330E /* Pods-Runner Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner Watch App.debug.xcconfig"; path = "Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App.debug.xcconfig"; sourceTree = "<group>"; }; | 54 | 27E4D5A8995AB1EC91E7330E /* Pods-Runner Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner Watch App.debug.xcconfig"; path = "Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App.debug.xcconfig"; sourceTree = "<group>"; }; |
| 55 | 4A9B7C102FDB0A1200F515B4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; | 55 | 4A9B7C102FDB0A1200F515B4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; |
| 56 | 4A9B7C112FDB0A1200F515B4 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; | 56 | 4A9B7C112FDB0A1200F515B4 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; |
| 57 | - 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift"; sourceTree = "<group>"; }; | ||
| 58 | - 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift"; sourceTree = "<group>"; }; | ||
| 59 | - 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift"; sourceTree = "<group>"; }; | 57 | + 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift"; sourceTree = "<group>"; }; |
| 58 | + 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift"; sourceTree = "<group>"; }; | ||
| 59 | + 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift"; sourceTree = "<group>"; }; | ||
| 60 | 66FBE57E2FDA4F0E00F515B4 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; | 60 | 66FBE57E2FDA4F0E00F515B4 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 61 | 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Runner Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; | 61 | 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Runner Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 62 | 66FBE5A72FDA518C00F515B4 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; | 62 | 66FBE5A72FDA518C00F515B4 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; |
| @@ -97,6 +97,8 @@ | @@ -97,6 +97,8 @@ | ||
| 97 | }; | 97 | }; |
| 98 | 66FBE58F2FDA4F0F00F515B4 /* Runner Watch App */ = { | 98 | 66FBE58F2FDA4F0F00F515B4 /* Runner Watch App */ = { |
| 99 | isa = PBXFileSystemSynchronizedRootGroup; | 99 | isa = PBXFileSystemSynchronizedRootGroup; |
| 100 | + exceptions = ( | ||
| 101 | + ); | ||
| 100 | path = "Runner Watch App"; | 102 | path = "Runner Watch App"; |
| 101 | sourceTree = "<group>"; | 103 | sourceTree = "<group>"; |
| 102 | }; | 104 | }; |
| @@ -134,9 +136,9 @@ | @@ -134,9 +136,9 @@ | ||
| 134 | 66FB19072FDBC80100F515B4 /* FigmaHome Preview Sources */ = { | 136 | 66FB19072FDBC80100F515B4 /* FigmaHome Preview Sources */ = { |
| 135 | isa = PBXGroup; | 137 | isa = PBXGroup; |
| 136 | children = ( | 138 | children = ( |
| 137 | - 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */, | ||
| 138 | - 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */, | ||
| 139 | - 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */, | 139 | + 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */, |
| 140 | + 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */, | ||
| 141 | + 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */, | ||
| 140 | ); | 142 | ); |
| 141 | name = "FigmaHome Preview Sources"; | 143 | name = "FigmaHome Preview Sources"; |
| 142 | sourceTree = "<group>"; | 144 | sourceTree = "<group>"; |
| @@ -303,14 +305,10 @@ | @@ -303,14 +305,10 @@ | ||
| 303 | inputFileListPaths = ( | 305 | inputFileListPaths = ( |
| 304 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", | 306 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", |
| 305 | ); | 307 | ); |
| 306 | - inputPaths = ( | ||
| 307 | - ); | ||
| 308 | name = "[CP] Copy Pods Resources"; | 308 | name = "[CP] Copy Pods Resources"; |
| 309 | outputFileListPaths = ( | 309 | outputFileListPaths = ( |
| 310 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", | 310 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", |
| 311 | ); | 311 | ); |
| 312 | - outputPaths = ( | ||
| 313 | - ); | ||
| 314 | runOnlyForDeploymentPostprocessing = 0; | 312 | runOnlyForDeploymentPostprocessing = 0; |
| 315 | shellPath = /bin/sh; | 313 | shellPath = /bin/sh; |
| 316 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; | 314 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; |
| @@ -381,14 +379,10 @@ | @@ -381,14 +379,10 @@ | ||
| 381 | inputFileListPaths = ( | 379 | inputFileListPaths = ( |
| 382 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", | 380 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", |
| 383 | ); | 381 | ); |
| 384 | - inputPaths = ( | ||
| 385 | - ); | ||
| 386 | name = "[CP] Embed Pods Frameworks"; | 382 | name = "[CP] Embed Pods Frameworks"; |
| 387 | outputFileListPaths = ( | 383 | outputFileListPaths = ( |
| 388 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", | 384 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", |
| 389 | ); | 385 | ); |
| 390 | - outputPaths = ( | ||
| 391 | - ); | ||
| 392 | runOnlyForDeploymentPostprocessing = 0; | 386 | runOnlyForDeploymentPostprocessing = 0; |
| 393 | shellPath = /bin/sh; | 387 | shellPath = /bin/sh; |
| 394 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; | 388 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; |
| @@ -402,14 +396,10 @@ | @@ -402,14 +396,10 @@ | ||
| 402 | inputFileListPaths = ( | 396 | inputFileListPaths = ( |
| 403 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist", | 397 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist", |
| 404 | ); | 398 | ); |
| 405 | - inputPaths = ( | ||
| 406 | - ); | ||
| 407 | name = "[CP] Embed Pods Frameworks"; | 399 | name = "[CP] Embed Pods Frameworks"; |
| 408 | outputFileListPaths = ( | 400 | outputFileListPaths = ( |
| 409 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist", | 401 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist", |
| 410 | ); | 402 | ); |
| 411 | - outputPaths = ( | ||
| 412 | - ); | ||
| 413 | runOnlyForDeploymentPostprocessing = 0; | 403 | runOnlyForDeploymentPostprocessing = 0; |
| 414 | shellPath = /bin/sh; | 404 | shellPath = /bin/sh; |
| 415 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks.sh\"\n"; | 405 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks.sh\"\n"; |
| @@ -451,9 +441,9 @@ | @@ -451,9 +441,9 @@ | ||
| 451 | isa = PBXSourcesBuildPhase; | 441 | isa = PBXSourcesBuildPhase; |
| 452 | buildActionMask = 2147483647; | 442 | buildActionMask = 2147483647; |
| 453 | files = ( | 443 | files = ( |
| 454 | - 66FB19022FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift in Sources */, | ||
| 455 | - 66FB19012FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift in Sources */, | ||
| 456 | - 66FB19002FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift in Sources */, | 444 | + 66FB19022FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift in Sources */, |
| 445 | + 66FB19012FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift in Sources */, | ||
| 446 | + 66FB19002FDBC80100F515B4 /* DFWatchFigmaTokens.swift in Sources */, | ||
| 457 | ); | 447 | ); |
| 458 | runOnlyForDeploymentPostprocessing = 0; | 448 | runOnlyForDeploymentPostprocessing = 0; |
| 459 | }; | 449 | }; |
| @@ -133,6 +133,170 @@ final class HealthDataReader { | @@ -133,6 +133,170 @@ final class HealthDataReader { | ||
| 133 | } | 133 | } |
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | + func fetchHrvData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 137 | + try await fetchQuantitySamples( | ||
| 138 | + identifier: .heartRateVariabilitySDNN, | ||
| 139 | + dataType: .hrv, | ||
| 140 | + unit: .secondUnit(with: .milli), | ||
| 141 | + startDate: startDate, | ||
| 142 | + endDate: endDate | ||
| 143 | + ) | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + func fetchHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 147 | + try await fetchQuantitySamples( | ||
| 148 | + identifier: .heartRate, | ||
| 149 | + dataType: .heartRate, | ||
| 150 | + unit: HKUnit.count().unitDivided(by: .minute()), | ||
| 151 | + startDate: startDate, | ||
| 152 | + endDate: endDate | ||
| 153 | + ) | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + func fetchWalkingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 157 | + try await fetchQuantitySamples( | ||
| 158 | + identifier: .walkingHeartRateAverage, | ||
| 159 | + dataType: .walkingHeartRate, | ||
| 160 | + unit: HKUnit.count().unitDivided(by: .minute()), | ||
| 161 | + startDate: startDate, | ||
| 162 | + endDate: endDate | ||
| 163 | + ) | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + func fetchRestingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 167 | + try await fetchQuantitySamples( | ||
| 168 | + identifier: .restingHeartRate, | ||
| 169 | + dataType: .restingHeartRate, | ||
| 170 | + unit: HKUnit.count().unitDivided(by: .minute()), | ||
| 171 | + startDate: startDate, | ||
| 172 | + endDate: endDate | ||
| 173 | + ) | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + func fetchSleepingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 177 | + let sleepIntervals = try await fetchSleepIntervals(startDate: startDate, endDate: endDate) | ||
| 178 | + var points: [NativeHealthDataPoint] = [] | ||
| 179 | + for interval in sleepIntervals where NativeSleepStage.isAsleep(interval.dataType) { | ||
| 180 | + let samples = try await fetchQuantitySamples( | ||
| 181 | + identifier: .heartRate, | ||
| 182 | + dataType: .sleepingHeartRate, | ||
| 183 | + unit: HKUnit.count().unitDivided(by: .minute()), | ||
| 184 | + startDate: Date(timeIntervalSince1970: interval.fromTime), | ||
| 185 | + endDate: Date(timeIntervalSince1970: interval.toTime) | ||
| 186 | + ) | ||
| 187 | + points.append(contentsOf: samples) | ||
| 188 | + } | ||
| 189 | + return points | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + func fetchOxygenSaturationData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 193 | + let points = try await fetchQuantitySamples( | ||
| 194 | + identifier: .oxygenSaturation, | ||
| 195 | + dataType: .oxygenSaturation, | ||
| 196 | + unit: .percent(), | ||
| 197 | + startDate: startDate, | ||
| 198 | + endDate: endDate | ||
| 199 | + ) | ||
| 200 | + return points.map { point in | ||
| 201 | + NativeHealthDataPoint(dataType: point.dataType, time: point.time, value: point.value * 100) | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + func fetchActiveEnergyData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 206 | + try await fetchQuantitySamples( | ||
| 207 | + identifier: .activeEnergyBurned, | ||
| 208 | + dataType: .activeEnergy, | ||
| 209 | + unit: .kilocalorie(), | ||
| 210 | + startDate: startDate, | ||
| 211 | + endDate: endDate | ||
| 212 | + ) | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + func fetchExerciseData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 216 | + try await fetchQuantitySamples( | ||
| 217 | + identifier: .appleExerciseTime, | ||
| 218 | + dataType: .exercise, | ||
| 219 | + unit: .minute(), | ||
| 220 | + startDate: startDate, | ||
| 221 | + endDate: endDate | ||
| 222 | + ) | ||
| 223 | + } | ||
| 224 | + | ||
| 225 | + func fetchStandData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 226 | + try await fetchQuantitySamples( | ||
| 227 | + identifier: .appleStandTime, | ||
| 228 | + dataType: .stand, | ||
| 229 | + unit: .minute(), | ||
| 230 | + startDate: startDate, | ||
| 231 | + endDate: endDate | ||
| 232 | + ) | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + func fetchStepCountData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 236 | + try await fetchDailyCumulativeSamples( | ||
| 237 | + identifier: .stepCount, | ||
| 238 | + dataType: .steps, | ||
| 239 | + unit: .count(), | ||
| 240 | + startDate: startDate, | ||
| 241 | + endDate: endDate | ||
| 242 | + ) | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + func fetchSleepData(startDate: Date, endDate: Date) async throws -> [NativeSleepInterval] { | ||
| 246 | + try await fetchSleepIntervals(startDate: startDate, endDate: endDate) | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + func fetchSleepingWristTemperatureData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 250 | + try await fetchQuantitySamples( | ||
| 251 | + identifier: .appleSleepingWristTemperature, | ||
| 252 | + dataType: .sleepingWristTemperature, | ||
| 253 | + unit: .degreeCelsius(), | ||
| 254 | + startDate: startDate, | ||
| 255 | + endDate: endDate | ||
| 256 | + ) | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + func fetchRespiratoryRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 260 | + try await fetchQuantitySamples( | ||
| 261 | + identifier: .respiratoryRate, | ||
| 262 | + dataType: .respiratoryRate, | ||
| 263 | + unit: HKUnit.count().unitDivided(by: .minute()), | ||
| 264 | + startDate: startDate, | ||
| 265 | + endDate: endDate | ||
| 266 | + ) | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + func fetchIrregularHeartRhythmData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 270 | + try await fetchIrregularHeartRhythmEvents(startDate: startDate, endDate: endDate) | ||
| 271 | + } | ||
| 272 | + | ||
| 273 | + func fetchActivityTargetData(startDate: Date, endDate: Date) async throws -> NativeActivityTarget? { | ||
| 274 | + let calendar = Calendar.current | ||
| 275 | + let start = calendar.dateComponents([.era, .year, .month, .day], from: startDate) | ||
| 276 | + let end = calendar.dateComponents([.era, .year, .month, .day], from: endDate) | ||
| 277 | + let predicate = HKQuery.predicate(forActivitySummariesBetweenStart: start, end: end) | ||
| 278 | + | ||
| 279 | + return try await withCheckedThrowingContinuation { continuation in | ||
| 280 | + let query = HKActivitySummaryQuery(predicate: predicate) { _, summaries, error in | ||
| 281 | + if let error { | ||
| 282 | + continuation.resume(throwing: error) | ||
| 283 | + return | ||
| 284 | + } | ||
| 285 | + guard let summary = summaries?.last else { | ||
| 286 | + continuation.resume(returning: nil) | ||
| 287 | + return | ||
| 288 | + } | ||
| 289 | + continuation.resume( | ||
| 290 | + returning: NativeActivityTarget( | ||
| 291 | + move: Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())), | ||
| 292 | + stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count())) | ||
| 293 | + ) | ||
| 294 | + ) | ||
| 295 | + } | ||
| 296 | + healthStore.execute(query) | ||
| 297 | + } | ||
| 298 | + } | ||
| 299 | + | ||
| 136 | private func fetchHeartRateFamily(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | 300 | private func fetchHeartRateFamily(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { |
| 137 | async let heartRate = fetchQuantitySamples( | 301 | async let heartRate = fetchQuantitySamples( |
| 138 | identifier: .heartRate, | 302 | identifier: .heartRate, |
| @@ -108,6 +108,66 @@ final class HealthKitService { | @@ -108,6 +108,66 @@ final class HealthKitService { | ||
| 108 | } | 108 | } |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | + func fetchHrvData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 112 | + try await reader.fetchHrvData(startDate: startDate, endDate: endDate) | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + func fetchHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 116 | + try await reader.fetchHeartRateData(startDate: startDate, endDate: endDate) | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + func fetchWalkingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 120 | + try await reader.fetchWalkingHeartRateData(startDate: startDate, endDate: endDate) | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + func fetchRestingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 124 | + try await reader.fetchRestingHeartRateData(startDate: startDate, endDate: endDate) | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + func fetchSleepingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 128 | + try await reader.fetchSleepingHeartRateData(startDate: startDate, endDate: endDate) | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + func fetchOxygenSaturationData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 132 | + try await reader.fetchOxygenSaturationData(startDate: startDate, endDate: endDate) | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + func fetchActiveEnergyData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 136 | + try await reader.fetchActiveEnergyData(startDate: startDate, endDate: endDate) | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + func fetchExerciseData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 140 | + try await reader.fetchExerciseData(startDate: startDate, endDate: endDate) | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + func fetchStandData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 144 | + try await reader.fetchStandData(startDate: startDate, endDate: endDate) | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + func fetchStepCountData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 148 | + try await reader.fetchStepCountData(startDate: startDate, endDate: endDate) | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + func fetchSleepData(startDate: Date, endDate: Date) async throws -> [NativeSleepInterval] { | ||
| 152 | + try await reader.fetchSleepData(startDate: startDate, endDate: endDate) | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + func fetchSleepingWristTemperatureData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 156 | + try await reader.fetchSleepingWristTemperatureData(startDate: startDate, endDate: endDate) | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + func fetchRespiratoryRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 160 | + try await reader.fetchRespiratoryRateData(startDate: startDate, endDate: endDate) | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + func fetchIrregularHeartRhythmData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] { | ||
| 164 | + try await reader.fetchIrregularHeartRhythmData(startDate: startDate, endDate: endDate) | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + func fetchActivityTargetData(startDate: Date, endDate: Date) async throws -> NativeActivityTarget? { | ||
| 168 | + try await reader.fetchActivityTargetData(startDate: startDate, endDate: endDate) | ||
| 169 | + } | ||
| 170 | + | ||
| 111 | private func earliestStartDate() -> Date { | 171 | private func earliestStartDate() -> Date { |
| 112 | NativeHealthDataType.allCases | 172 | NativeHealthDataType.allCases |
| 113 | .filter { $0 != .unknown } | 173 | .filter { $0 != .unknown } |
| @@ -174,4 +234,3 @@ private extension NativeHealthDataType { | @@ -174,4 +234,3 @@ private extension NativeHealthDataType { | ||
| 174 | } | 234 | } |
| 175 | } | 235 | } |
| 176 | } | 236 | } |
| 177 | - |
| @@ -49,6 +49,25 @@ struct NativeSleepInterval: Codable { | @@ -49,6 +49,25 @@ struct NativeSleepInterval: Codable { | ||
| 49 | let toTime: TimeInterval | 49 | let toTime: TimeInterval |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | +struct NativeActivityTarget { | ||
| 53 | + let move: Int? | ||
| 54 | + let stand: Int? | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +enum NativeSleepStage { | ||
| 58 | + static func isAsleep(_ rawValue: Int) -> Bool { | ||
| 59 | + guard let value = HKCategoryValueSleepAnalysis(rawValue: rawValue) else { | ||
| 60 | + return false | ||
| 61 | + } | ||
| 62 | + switch value { | ||
| 63 | + case .asleepUnspecified, .asleepCore, .asleepDeep, .asleepREM: | ||
| 64 | + return true | ||
| 65 | + default: | ||
| 66 | + return false | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | +} | ||
| 70 | + | ||
| 52 | struct NativeHealthSyncSummary { | 71 | struct NativeHealthSyncSummary { |
| 53 | var commonCount = 0 | 72 | var commonCount = 0 |
| 54 | var sleepCount = 0 | 73 | var sleepCount = 0 |
| @@ -145,11 +145,112 @@ struct HealthUploadResult: Hashable { | @@ -145,11 +145,112 @@ struct HealthUploadResult: Hashable { | ||
| 145 | } | 145 | } |
| 146 | } | 146 | } |
| 147 | 147 | ||
| 148 | +/// Generated class from Pigeon that represents data sent in messages. | ||
| 149 | +struct HealthUploadDataPoint: Hashable { | ||
| 150 | + var dataType: Int64 | ||
| 151 | + var time: Int64 | ||
| 152 | + var value: Double | ||
| 153 | + | ||
| 154 | + | ||
| 155 | + // swift-format-ignore: AlwaysUseLowerCamelCase | ||
| 156 | + static func fromList(_ pigeonVar_list: [Any?]) -> HealthUploadDataPoint? { | ||
| 157 | + let dataType = pigeonVar_list[0] as! Int64 | ||
| 158 | + let time = pigeonVar_list[1] as! Int64 | ||
| 159 | + let value = pigeonVar_list[2] as! Double | ||
| 160 | + | ||
| 161 | + return HealthUploadDataPoint( | ||
| 162 | + dataType: dataType, | ||
| 163 | + time: time, | ||
| 164 | + value: value | ||
| 165 | + ) | ||
| 166 | + } | ||
| 167 | + func toList() -> [Any?] { | ||
| 168 | + return [ | ||
| 169 | + dataType, | ||
| 170 | + time, | ||
| 171 | + value, | ||
| 172 | + ] | ||
| 173 | + } | ||
| 174 | + static func == (lhs: HealthUploadDataPoint, rhs: HealthUploadDataPoint) -> Bool { | ||
| 175 | + return deepEqualsHealthKitApi(lhs.toList(), rhs.toList()) } | ||
| 176 | + func hash(into hasher: inout Hasher) { | ||
| 177 | + deepHashHealthKitApi(value: toList(), hasher: &hasher) | ||
| 178 | + } | ||
| 179 | +} | ||
| 180 | + | ||
| 181 | +/// Generated class from Pigeon that represents data sent in messages. | ||
| 182 | +struct HealthSleepUploadDataPoint: Hashable { | ||
| 183 | + var dataType: Int64 | ||
| 184 | + var fromTime: Int64 | ||
| 185 | + var toTime: Int64 | ||
| 186 | + | ||
| 187 | + | ||
| 188 | + // swift-format-ignore: AlwaysUseLowerCamelCase | ||
| 189 | + static func fromList(_ pigeonVar_list: [Any?]) -> HealthSleepUploadDataPoint? { | ||
| 190 | + let dataType = pigeonVar_list[0] as! Int64 | ||
| 191 | + let fromTime = pigeonVar_list[1] as! Int64 | ||
| 192 | + let toTime = pigeonVar_list[2] as! Int64 | ||
| 193 | + | ||
| 194 | + return HealthSleepUploadDataPoint( | ||
| 195 | + dataType: dataType, | ||
| 196 | + fromTime: fromTime, | ||
| 197 | + toTime: toTime | ||
| 198 | + ) | ||
| 199 | + } | ||
| 200 | + func toList() -> [Any?] { | ||
| 201 | + return [ | ||
| 202 | + dataType, | ||
| 203 | + fromTime, | ||
| 204 | + toTime, | ||
| 205 | + ] | ||
| 206 | + } | ||
| 207 | + static func == (lhs: HealthSleepUploadDataPoint, rhs: HealthSleepUploadDataPoint) -> Bool { | ||
| 208 | + return deepEqualsHealthKitApi(lhs.toList(), rhs.toList()) } | ||
| 209 | + func hash(into hasher: inout Hasher) { | ||
| 210 | + deepHashHealthKitApi(value: toList(), hasher: &hasher) | ||
| 211 | + } | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +/// Generated class from Pigeon that represents data sent in messages. | ||
| 215 | +struct HealthActivityTargetData: Hashable { | ||
| 216 | + var move: Int64? = nil | ||
| 217 | + var stand: Int64? = nil | ||
| 218 | + | ||
| 219 | + | ||
| 220 | + // swift-format-ignore: AlwaysUseLowerCamelCase | ||
| 221 | + static func fromList(_ pigeonVar_list: [Any?]) -> HealthActivityTargetData? { | ||
| 222 | + let move: Int64? = nilOrValue(pigeonVar_list[0]) | ||
| 223 | + let stand: Int64? = nilOrValue(pigeonVar_list[1]) | ||
| 224 | + | ||
| 225 | + return HealthActivityTargetData( | ||
| 226 | + move: move, | ||
| 227 | + stand: stand | ||
| 228 | + ) | ||
| 229 | + } | ||
| 230 | + func toList() -> [Any?] { | ||
| 231 | + return [ | ||
| 232 | + move, | ||
| 233 | + stand, | ||
| 234 | + ] | ||
| 235 | + } | ||
| 236 | + static func == (lhs: HealthActivityTargetData, rhs: HealthActivityTargetData) -> Bool { | ||
| 237 | + return deepEqualsHealthKitApi(lhs.toList(), rhs.toList()) } | ||
| 238 | + func hash(into hasher: inout Hasher) { | ||
| 239 | + deepHashHealthKitApi(value: toList(), hasher: &hasher) | ||
| 240 | + } | ||
| 241 | +} | ||
| 242 | + | ||
| 148 | private class HealthKitApiPigeonCodecReader: FlutterStandardReader { | 243 | private class HealthKitApiPigeonCodecReader: FlutterStandardReader { |
| 149 | override func readValue(ofType type: UInt8) -> Any? { | 244 | override func readValue(ofType type: UInt8) -> Any? { |
| 150 | switch type { | 245 | switch type { |
| 151 | case 129: | 246 | case 129: |
| 152 | return HealthUploadResult.fromList(self.readValue() as! [Any?]) | 247 | return HealthUploadResult.fromList(self.readValue() as! [Any?]) |
| 248 | + case 130: | ||
| 249 | + return HealthUploadDataPoint.fromList(self.readValue() as! [Any?]) | ||
| 250 | + case 131: | ||
| 251 | + return HealthSleepUploadDataPoint.fromList(self.readValue() as! [Any?]) | ||
| 252 | + case 132: | ||
| 253 | + return HealthActivityTargetData.fromList(self.readValue() as! [Any?]) | ||
| 153 | default: | 254 | default: |
| 154 | return super.readValue(ofType: type) | 255 | return super.readValue(ofType: type) |
| 155 | } | 256 | } |
| @@ -161,6 +262,15 @@ private class HealthKitApiPigeonCodecWriter: FlutterStandardWriter { | @@ -161,6 +262,15 @@ private class HealthKitApiPigeonCodecWriter: FlutterStandardWriter { | ||
| 161 | if let value = value as? HealthUploadResult { | 262 | if let value = value as? HealthUploadResult { |
| 162 | super.writeByte(129) | 263 | super.writeByte(129) |
| 163 | super.writeValue(value.toList()) | 264 | super.writeValue(value.toList()) |
| 265 | + } else if let value = value as? HealthUploadDataPoint { | ||
| 266 | + super.writeByte(130) | ||
| 267 | + super.writeValue(value.toList()) | ||
| 268 | + } else if let value = value as? HealthSleepUploadDataPoint { | ||
| 269 | + super.writeByte(131) | ||
| 270 | + super.writeValue(value.toList()) | ||
| 271 | + } else if let value = value as? HealthActivityTargetData { | ||
| 272 | + super.writeByte(132) | ||
| 273 | + super.writeValue(value.toList()) | ||
| 164 | } else { | 274 | } else { |
| 165 | super.writeValue(value) | 275 | super.writeValue(value) |
| 166 | } | 276 | } |
| @@ -190,6 +300,21 @@ protocol HealthKitHostApi { | @@ -190,6 +300,21 @@ protocol HealthKitHostApi { | ||
| 190 | func cancelHealthAppAuthorization() throws -> Bool | 300 | func cancelHealthAppAuthorization() throws -> Bool |
| 191 | /// Runs native health read and server upload pipeline. | 301 | /// Runs native health read and server upload pipeline. |
| 192 | func performHealthUpload() throws -> HealthUploadResult | 302 | func performHealthUpload() throws -> HealthUploadResult |
| 303 | + func fetchHrvData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 304 | + func fetchHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 305 | + func fetchWalkingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 306 | + func fetchRestingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 307 | + func fetchSleepingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 308 | + func fetchOxygenSaturationData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 309 | + func fetchActiveEnergyData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 310 | + func fetchExerciseData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 311 | + func fetchStandData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 312 | + func fetchStepCountData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 313 | + func fetchSleepData(startTime: Int64, endTime: Int64) throws -> [HealthSleepUploadDataPoint] | ||
| 314 | + func fetchSleepingWristTemperatureData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 315 | + func fetchRespiratoryRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 316 | + func fetchIrregularHeartRhythmData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] | ||
| 317 | + func fetchActivityTargetData(startTime: Int64, endTime: Int64) throws -> HealthActivityTargetData? | ||
| 193 | } | 318 | } |
| 194 | 319 | ||
| 195 | /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. | 320 | /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. |
| @@ -265,5 +390,245 @@ class HealthKitHostApiSetup { | @@ -265,5 +390,245 @@ class HealthKitHostApiSetup { | ||
| 265 | } else { | 390 | } else { |
| 266 | performHealthUploadChannel.setMessageHandler(nil) | 391 | performHealthUploadChannel.setMessageHandler(nil) |
| 267 | } | 392 | } |
| 393 | + let fetchHrvDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHrvData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 394 | + if let api = api { | ||
| 395 | + fetchHrvDataChannel.setMessageHandler { message, reply in | ||
| 396 | + let args = message as! [Any?] | ||
| 397 | + let startTimeArg = args[0] as! Int64 | ||
| 398 | + let endTimeArg = args[1] as! Int64 | ||
| 399 | + do { | ||
| 400 | + let result = try api.fetchHrvData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 401 | + reply(wrapResult(result)) | ||
| 402 | + } catch { | ||
| 403 | + reply(wrapError(error)) | ||
| 404 | + } | ||
| 405 | + } | ||
| 406 | + } else { | ||
| 407 | + fetchHrvDataChannel.setMessageHandler(nil) | ||
| 408 | + } | ||
| 409 | + let fetchHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 410 | + if let api = api { | ||
| 411 | + fetchHeartRateDataChannel.setMessageHandler { message, reply in | ||
| 412 | + let args = message as! [Any?] | ||
| 413 | + let startTimeArg = args[0] as! Int64 | ||
| 414 | + let endTimeArg = args[1] as! Int64 | ||
| 415 | + do { | ||
| 416 | + let result = try api.fetchHeartRateData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 417 | + reply(wrapResult(result)) | ||
| 418 | + } catch { | ||
| 419 | + reply(wrapError(error)) | ||
| 420 | + } | ||
| 421 | + } | ||
| 422 | + } else { | ||
| 423 | + fetchHeartRateDataChannel.setMessageHandler(nil) | ||
| 424 | + } | ||
| 425 | + let fetchWalkingHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchWalkingHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 426 | + if let api = api { | ||
| 427 | + fetchWalkingHeartRateDataChannel.setMessageHandler { message, reply in | ||
| 428 | + let args = message as! [Any?] | ||
| 429 | + let startTimeArg = args[0] as! Int64 | ||
| 430 | + let endTimeArg = args[1] as! Int64 | ||
| 431 | + do { | ||
| 432 | + let result = try api.fetchWalkingHeartRateData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 433 | + reply(wrapResult(result)) | ||
| 434 | + } catch { | ||
| 435 | + reply(wrapError(error)) | ||
| 436 | + } | ||
| 437 | + } | ||
| 438 | + } else { | ||
| 439 | + fetchWalkingHeartRateDataChannel.setMessageHandler(nil) | ||
| 440 | + } | ||
| 441 | + let fetchRestingHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRestingHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 442 | + if let api = api { | ||
| 443 | + fetchRestingHeartRateDataChannel.setMessageHandler { message, reply in | ||
| 444 | + let args = message as! [Any?] | ||
| 445 | + let startTimeArg = args[0] as! Int64 | ||
| 446 | + let endTimeArg = args[1] as! Int64 | ||
| 447 | + do { | ||
| 448 | + let result = try api.fetchRestingHeartRateData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 449 | + reply(wrapResult(result)) | ||
| 450 | + } catch { | ||
| 451 | + reply(wrapError(error)) | ||
| 452 | + } | ||
| 453 | + } | ||
| 454 | + } else { | ||
| 455 | + fetchRestingHeartRateDataChannel.setMessageHandler(nil) | ||
| 456 | + } | ||
| 457 | + let fetchSleepingHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 458 | + if let api = api { | ||
| 459 | + fetchSleepingHeartRateDataChannel.setMessageHandler { message, reply in | ||
| 460 | + let args = message as! [Any?] | ||
| 461 | + let startTimeArg = args[0] as! Int64 | ||
| 462 | + let endTimeArg = args[1] as! Int64 | ||
| 463 | + do { | ||
| 464 | + let result = try api.fetchSleepingHeartRateData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 465 | + reply(wrapResult(result)) | ||
| 466 | + } catch { | ||
| 467 | + reply(wrapError(error)) | ||
| 468 | + } | ||
| 469 | + } | ||
| 470 | + } else { | ||
| 471 | + fetchSleepingHeartRateDataChannel.setMessageHandler(nil) | ||
| 472 | + } | ||
| 473 | + let fetchOxygenSaturationDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchOxygenSaturationData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 474 | + if let api = api { | ||
| 475 | + fetchOxygenSaturationDataChannel.setMessageHandler { message, reply in | ||
| 476 | + let args = message as! [Any?] | ||
| 477 | + let startTimeArg = args[0] as! Int64 | ||
| 478 | + let endTimeArg = args[1] as! Int64 | ||
| 479 | + do { | ||
| 480 | + let result = try api.fetchOxygenSaturationData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 481 | + reply(wrapResult(result)) | ||
| 482 | + } catch { | ||
| 483 | + reply(wrapError(error)) | ||
| 484 | + } | ||
| 485 | + } | ||
| 486 | + } else { | ||
| 487 | + fetchOxygenSaturationDataChannel.setMessageHandler(nil) | ||
| 488 | + } | ||
| 489 | + let fetchActiveEnergyDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActiveEnergyData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 490 | + if let api = api { | ||
| 491 | + fetchActiveEnergyDataChannel.setMessageHandler { message, reply in | ||
| 492 | + let args = message as! [Any?] | ||
| 493 | + let startTimeArg = args[0] as! Int64 | ||
| 494 | + let endTimeArg = args[1] as! Int64 | ||
| 495 | + do { | ||
| 496 | + let result = try api.fetchActiveEnergyData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 497 | + reply(wrapResult(result)) | ||
| 498 | + } catch { | ||
| 499 | + reply(wrapError(error)) | ||
| 500 | + } | ||
| 501 | + } | ||
| 502 | + } else { | ||
| 503 | + fetchActiveEnergyDataChannel.setMessageHandler(nil) | ||
| 504 | + } | ||
| 505 | + let fetchExerciseDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchExerciseData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 506 | + if let api = api { | ||
| 507 | + fetchExerciseDataChannel.setMessageHandler { message, reply in | ||
| 508 | + let args = message as! [Any?] | ||
| 509 | + let startTimeArg = args[0] as! Int64 | ||
| 510 | + let endTimeArg = args[1] as! Int64 | ||
| 511 | + do { | ||
| 512 | + let result = try api.fetchExerciseData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 513 | + reply(wrapResult(result)) | ||
| 514 | + } catch { | ||
| 515 | + reply(wrapError(error)) | ||
| 516 | + } | ||
| 517 | + } | ||
| 518 | + } else { | ||
| 519 | + fetchExerciseDataChannel.setMessageHandler(nil) | ||
| 520 | + } | ||
| 521 | + let fetchStandDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStandData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 522 | + if let api = api { | ||
| 523 | + fetchStandDataChannel.setMessageHandler { message, reply in | ||
| 524 | + let args = message as! [Any?] | ||
| 525 | + let startTimeArg = args[0] as! Int64 | ||
| 526 | + let endTimeArg = args[1] as! Int64 | ||
| 527 | + do { | ||
| 528 | + let result = try api.fetchStandData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 529 | + reply(wrapResult(result)) | ||
| 530 | + } catch { | ||
| 531 | + reply(wrapError(error)) | ||
| 532 | + } | ||
| 533 | + } | ||
| 534 | + } else { | ||
| 535 | + fetchStandDataChannel.setMessageHandler(nil) | ||
| 536 | + } | ||
| 537 | + let fetchStepCountDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStepCountData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 538 | + if let api = api { | ||
| 539 | + fetchStepCountDataChannel.setMessageHandler { message, reply in | ||
| 540 | + let args = message as! [Any?] | ||
| 541 | + let startTimeArg = args[0] as! Int64 | ||
| 542 | + let endTimeArg = args[1] as! Int64 | ||
| 543 | + do { | ||
| 544 | + let result = try api.fetchStepCountData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 545 | + reply(wrapResult(result)) | ||
| 546 | + } catch { | ||
| 547 | + reply(wrapError(error)) | ||
| 548 | + } | ||
| 549 | + } | ||
| 550 | + } else { | ||
| 551 | + fetchStepCountDataChannel.setMessageHandler(nil) | ||
| 552 | + } | ||
| 553 | + let fetchSleepDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 554 | + if let api = api { | ||
| 555 | + fetchSleepDataChannel.setMessageHandler { message, reply in | ||
| 556 | + let args = message as! [Any?] | ||
| 557 | + let startTimeArg = args[0] as! Int64 | ||
| 558 | + let endTimeArg = args[1] as! Int64 | ||
| 559 | + do { | ||
| 560 | + let result = try api.fetchSleepData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 561 | + reply(wrapResult(result)) | ||
| 562 | + } catch { | ||
| 563 | + reply(wrapError(error)) | ||
| 564 | + } | ||
| 565 | + } | ||
| 566 | + } else { | ||
| 567 | + fetchSleepDataChannel.setMessageHandler(nil) | ||
| 568 | + } | ||
| 569 | + let fetchSleepingWristTemperatureDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingWristTemperatureData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 570 | + if let api = api { | ||
| 571 | + fetchSleepingWristTemperatureDataChannel.setMessageHandler { message, reply in | ||
| 572 | + let args = message as! [Any?] | ||
| 573 | + let startTimeArg = args[0] as! Int64 | ||
| 574 | + let endTimeArg = args[1] as! Int64 | ||
| 575 | + do { | ||
| 576 | + let result = try api.fetchSleepingWristTemperatureData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 577 | + reply(wrapResult(result)) | ||
| 578 | + } catch { | ||
| 579 | + reply(wrapError(error)) | ||
| 580 | + } | ||
| 581 | + } | ||
| 582 | + } else { | ||
| 583 | + fetchSleepingWristTemperatureDataChannel.setMessageHandler(nil) | ||
| 584 | + } | ||
| 585 | + let fetchRespiratoryRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRespiratoryRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 586 | + if let api = api { | ||
| 587 | + fetchRespiratoryRateDataChannel.setMessageHandler { message, reply in | ||
| 588 | + let args = message as! [Any?] | ||
| 589 | + let startTimeArg = args[0] as! Int64 | ||
| 590 | + let endTimeArg = args[1] as! Int64 | ||
| 591 | + do { | ||
| 592 | + let result = try api.fetchRespiratoryRateData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 593 | + reply(wrapResult(result)) | ||
| 594 | + } catch { | ||
| 595 | + reply(wrapError(error)) | ||
| 596 | + } | ||
| 597 | + } | ||
| 598 | + } else { | ||
| 599 | + fetchRespiratoryRateDataChannel.setMessageHandler(nil) | ||
| 600 | + } | ||
| 601 | + let fetchIrregularHeartRhythmDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchIrregularHeartRhythmData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 602 | + if let api = api { | ||
| 603 | + fetchIrregularHeartRhythmDataChannel.setMessageHandler { message, reply in | ||
| 604 | + let args = message as! [Any?] | ||
| 605 | + let startTimeArg = args[0] as! Int64 | ||
| 606 | + let endTimeArg = args[1] as! Int64 | ||
| 607 | + do { | ||
| 608 | + let result = try api.fetchIrregularHeartRhythmData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 609 | + reply(wrapResult(result)) | ||
| 610 | + } catch { | ||
| 611 | + reply(wrapError(error)) | ||
| 612 | + } | ||
| 613 | + } | ||
| 614 | + } else { | ||
| 615 | + fetchIrregularHeartRhythmDataChannel.setMessageHandler(nil) | ||
| 616 | + } | ||
| 617 | + let fetchActivityTargetDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActivityTargetData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 618 | + if let api = api { | ||
| 619 | + fetchActivityTargetDataChannel.setMessageHandler { message, reply in | ||
| 620 | + let args = message as! [Any?] | ||
| 621 | + let startTimeArg = args[0] as! Int64 | ||
| 622 | + let endTimeArg = args[1] as! Int64 | ||
| 623 | + do { | ||
| 624 | + let result = try api.fetchActivityTargetData(startTime: startTimeArg, endTime: endTimeArg) | ||
| 625 | + reply(wrapResult(result)) | ||
| 626 | + } catch { | ||
| 627 | + reply(wrapError(error)) | ||
| 628 | + } | ||
| 629 | + } | ||
| 630 | + } else { | ||
| 631 | + fetchActivityTargetDataChannel.setMessageHandler(nil) | ||
| 632 | + } | ||
| 268 | } | 633 | } |
| 269 | } | 634 | } |
| @@ -50,6 +50,109 @@ final class HealthKitHostApiImpl: HealthKitHostApi { | @@ -50,6 +50,109 @@ final class HealthKitHostApiImpl: HealthKitHostApi { | ||
| 50 | ) | 50 | ) |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | + func fetchHrvData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 54 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchHrvData) | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + func fetchHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 58 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchHeartRateData) | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + func fetchWalkingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 62 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchWalkingHeartRateData) | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + func fetchRestingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 66 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchRestingHeartRateData) | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + func fetchSleepingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 70 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchSleepingHeartRateData) | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + func fetchOxygenSaturationData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 74 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchOxygenSaturationData) | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + func fetchActiveEnergyData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 78 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchActiveEnergyData) | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + func fetchExerciseData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 82 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchExerciseData) | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + func fetchStandData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 86 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchStandData) | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + func fetchStepCountData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 90 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchStepCountData) | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + func fetchSleepData(startTime: Int64, endTime: Int64) throws -> [HealthSleepUploadDataPoint] { | ||
| 94 | + let range = makeDateRange(startTime: startTime, endTime: endTime) | ||
| 95 | + let intervals = try runBlockingThrows { | ||
| 96 | + try await self.service.fetchSleepData(startDate: range.startDate, endDate: range.endDate) | ||
| 97 | + } | ||
| 98 | + return intervals.map { interval in | ||
| 99 | + HealthSleepUploadDataPoint( | ||
| 100 | + dataType: Int64(interval.dataType), | ||
| 101 | + fromTime: Int64(interval.fromTime.rounded()), | ||
| 102 | + toTime: Int64(interval.toTime.rounded()) | ||
| 103 | + ) | ||
| 104 | + } | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + func fetchSleepingWristTemperatureData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 108 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchSleepingWristTemperatureData) | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + func fetchRespiratoryRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 112 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchRespiratoryRateData) | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + func fetchIrregularHeartRhythmData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] { | ||
| 116 | + try fetchCommon(startTime: startTime, endTime: endTime, service.fetchIrregularHeartRhythmData) | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + func fetchActivityTargetData(startTime: Int64, endTime: Int64) throws -> HealthActivityTargetData? { | ||
| 120 | + let range = makeDateRange(startTime: startTime, endTime: endTime) | ||
| 121 | + let target = try runBlockingThrows { | ||
| 122 | + try await self.service.fetchActivityTargetData(startDate: range.startDate, endDate: range.endDate) | ||
| 123 | + } | ||
| 124 | + guard let target else { return nil } | ||
| 125 | + return HealthActivityTargetData( | ||
| 126 | + move: target.move.map(Int64.init), | ||
| 127 | + stand: target.stand.map(Int64.init) | ||
| 128 | + ) | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + private func fetchCommon( | ||
| 132 | + startTime: Int64, | ||
| 133 | + endTime: Int64, | ||
| 134 | + _ fetch: @escaping (Date, Date) async throws -> [NativeHealthDataPoint] | ||
| 135 | + ) throws -> [HealthUploadDataPoint] { | ||
| 136 | + let range = makeDateRange(startTime: startTime, endTime: endTime) | ||
| 137 | + let points = try runBlockingThrows { | ||
| 138 | + try await fetch(range.startDate, range.endDate) | ||
| 139 | + } | ||
| 140 | + return points.map { point in | ||
| 141 | + HealthUploadDataPoint( | ||
| 142 | + dataType: Int64(point.dataType.rawValue), | ||
| 143 | + time: Int64(point.time.rounded()), | ||
| 144 | + value: point.value | ||
| 145 | + ) | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + private func makeDateRange(startTime: Int64, endTime: Int64) -> (startDate: Date, endDate: Date) { | ||
| 150 | + ( | ||
| 151 | + Date(timeIntervalSince1970: TimeInterval(startTime)), | ||
| 152 | + Date(timeIntervalSince1970: TimeInterval(endTime)) | ||
| 153 | + ) | ||
| 154 | + } | ||
| 155 | + | ||
| 53 | private func runAuthorizationRequest() -> (success: Bool, error: Error?) { | 156 | private func runAuthorizationRequest() -> (success: Bool, error: Error?) { |
| 54 | var result: (Bool, Error?) = (false, nil) | 157 | var result: (Bool, Error?) = (false, nil) |
| 55 | let semaphore = DispatchSemaphore(value: 0) | 158 | let semaphore = DispatchSemaphore(value: 0) |
| @@ -73,6 +176,21 @@ private func runBlocking<T>(_ operation: @escaping () async -> T) -> T { | @@ -73,6 +176,21 @@ private func runBlocking<T>(_ operation: @escaping () async -> T) -> T { | ||
| 73 | return result! | 176 | return result! |
| 74 | } | 177 | } |
| 75 | 178 | ||
| 179 | +private func runBlockingThrows<T>(_ operation: @escaping () async throws -> T) throws -> T { | ||
| 180 | + let semaphore = DispatchSemaphore(value: 0) | ||
| 181 | + var result: Result<T, Error>? | ||
| 182 | + Task { | ||
| 183 | + do { | ||
| 184 | + result = .success(try await operation()) | ||
| 185 | + } catch { | ||
| 186 | + result = .failure(error) | ||
| 187 | + } | ||
| 188 | + semaphore.signal() | ||
| 189 | + } | ||
| 190 | + waitForSemaphore(semaphore) | ||
| 191 | + return try result!.get() | ||
| 192 | +} | ||
| 193 | + | ||
| 76 | private func waitForSemaphore(_ semaphore: DispatchSemaphore) { | 194 | private func waitForSemaphore(_ semaphore: DispatchSemaphore) { |
| 77 | if Thread.isMainThread { | 195 | if Thread.isMainThread { |
| 78 | while semaphore.wait(timeout: .now() + 0.05) == .timedOut { | 196 | while semaphore.wait(timeout: .now() + 0.05) == .timedOut { |
| @@ -188,6 +188,8 @@ protocol WearEngineHostApi { | @@ -188,6 +188,8 @@ protocol WearEngineHostApi { | ||
| 188 | func registerMessageReceiver() throws -> Bool | 188 | func registerMessageReceiver() throws -> Bool |
| 189 | func sendTextMessage(message: String) throws -> Bool | 189 | func sendTextMessage(message: String) throws -> Bool |
| 190 | func sendWatchSyncPayload(jsonPayload: String) throws -> Bool | 190 | func sendWatchSyncPayload(jsonPayload: String) throws -> Bool |
| 191 | + /// Opens the system photo picker and returns a local PNG file path after | ||
| 192 | + /// removing the image background on the host platform. | ||
| 191 | func pickImageAndRemoveBackground() throws -> String? | 193 | func pickImageAndRemoveBackground() throws -> String? |
| 192 | } | 194 | } |
| 193 | 195 | ||
| @@ -266,6 +268,8 @@ class WearEngineHostApiSetup { | @@ -266,6 +268,8 @@ class WearEngineHostApiSetup { | ||
| 266 | } else { | 268 | } else { |
| 267 | sendWatchSyncPayloadChannel.setMessageHandler(nil) | 269 | sendWatchSyncPayloadChannel.setMessageHandler(nil) |
| 268 | } | 270 | } |
| 271 | + /// Opens the system photo picker and returns a local PNG file path after | ||
| 272 | + /// removing the image background on the host platform. | ||
| 269 | let pickImageAndRemoveBackgroundChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | 273 | let pickImageAndRemoveBackgroundChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) |
| 270 | if let api = api { | 274 | if let api = api { |
| 271 | pickImageAndRemoveBackgroundChannel.setMessageHandler { _, reply in | 275 | pickImageAndRemoveBackgroundChannel.setMessageHandler { _, reply in |
| 1 | +import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart'; | ||
| 2 | +import 'package:doublefeel_flutter/core/network/api_paths.dart'; | ||
| 3 | +import 'package:doublefeel_flutter/core/network/dio_client.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/core/result/app_result.dart'; | ||
| 5 | +import 'package:doublefeel_flutter/core/result/safe_call.dart'; | ||
| 6 | + | ||
| 7 | +import 'models/apple_health_upload_record.dart'; | ||
| 8 | +import 'models/apple_health_upload_request.dart'; | ||
| 9 | +import 'models/upload_activity_target.dart'; | ||
| 10 | + | ||
| 11 | +class AppleHealthUploadApi { | ||
| 12 | + AppleHealthUploadApi(this._dioClient); | ||
| 13 | + | ||
| 14 | + final DioClient _dioClient; | ||
| 15 | + | ||
| 16 | + Future<AppResult<void>> uploadCommonData( | ||
| 17 | + AppleHealthCommonUploadRequest request, | ||
| 18 | + ) { | ||
| 19 | + return safeCall( | ||
| 20 | + call: () async { | ||
| 21 | + await _dioClient.dio.post( | ||
| 22 | + ApiPaths.healthUploadCommon, | ||
| 23 | + data: request.toJson(), | ||
| 24 | + ); | ||
| 25 | + }, | ||
| 26 | + ); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + Future<AppResult<AppleHealthLatestUploadRecordList>> | ||
| 30 | + getLatestCommonUploadRecordList({ | ||
| 31 | + HttpErrorHandlingPolicy? errorHandlingPolicy = | ||
| 32 | + HttpErrorHandlingPolicy.defaultPolicy, | ||
| 33 | + }) { | ||
| 34 | + return safeCall( | ||
| 35 | + call: () async { | ||
| 36 | + final response = await _dioClient.dio.get(ApiPaths.healthUploadCommon); | ||
| 37 | + return AppleHealthLatestUploadRecordList.fromJson( | ||
| 38 | + response.data as Map<String, dynamic>, | ||
| 39 | + ); | ||
| 40 | + }, | ||
| 41 | + errorHandlingPolicy: errorHandlingPolicy, | ||
| 42 | + ); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + Future<AppResult<void>> uploadSleepData( | ||
| 46 | + AppleHealthSleepUploadRequest request, | ||
| 47 | + ) { | ||
| 48 | + return safeCall( | ||
| 49 | + call: () async { | ||
| 50 | + await _dioClient.dio.post( | ||
| 51 | + ApiPaths.healthUploadSleep, | ||
| 52 | + data: request.toJson(), | ||
| 53 | + ); | ||
| 54 | + }, | ||
| 55 | + ); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + Future<AppResult<AppleHealthLatestSleepUploadRecord>> | ||
| 59 | + getLatestSleepUploadRecord({ | ||
| 60 | + HttpErrorHandlingPolicy? errorHandlingPolicy = | ||
| 61 | + HttpErrorHandlingPolicy.defaultPolicy, | ||
| 62 | + }) { | ||
| 63 | + return safeCall( | ||
| 64 | + call: () async { | ||
| 65 | + final response = await _dioClient.dio.get(ApiPaths.healthUploadSleep); | ||
| 66 | + return AppleHealthLatestSleepUploadRecord.fromJson( | ||
| 67 | + response.data as Map<String, dynamic>, | ||
| 68 | + ); | ||
| 69 | + }, | ||
| 70 | + errorHandlingPolicy: errorHandlingPolicy, | ||
| 71 | + ); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + Future<AppResult<void>> uploadActivityTarget( | ||
| 75 | + HealthActivityTargetUploadData target, | ||
| 76 | + ) { | ||
| 77 | + return safeCall( | ||
| 78 | + call: () async { | ||
| 79 | + await _dioClient.dio.post( | ||
| 80 | + ApiPaths.healthActivityTarget, | ||
| 81 | + data: target.toJson(), | ||
| 82 | + ); | ||
| 83 | + }, | ||
| 84 | + ); | ||
| 85 | + } | ||
| 86 | +} |
| 1 | +import 'package:doublefeel_flutter/core/result/app_result.dart'; | ||
| 2 | + | ||
| 3 | +import 'apple_health_upload_api.dart'; | ||
| 4 | +import 'models/apple_health_upload_record.dart'; | ||
| 5 | +import 'models/apple_health_upload_request.dart'; | ||
| 6 | +import 'models/apple_health_upload_sample.dart'; | ||
| 7 | +import 'models/upload_activity_target.dart'; | ||
| 8 | +import 'models/upload_sleep.dart'; | ||
| 9 | + | ||
| 10 | +class AppleHealthUploadTool { | ||
| 11 | + AppleHealthUploadTool(this._api); | ||
| 12 | + | ||
| 13 | + final AppleHealthUploadApi _api; | ||
| 14 | + | ||
| 15 | + Future<AppleHealthUploadResult> upload({ | ||
| 16 | + List<AppleHealthUploadSample> commonData = const [], | ||
| 17 | + List<HealthSleepUploadData> sleepData = const [], | ||
| 18 | + HealthActivityTargetUploadData? activityTarget, | ||
| 19 | + }) async { | ||
| 20 | + AppResult<void>? commonResult; | ||
| 21 | + AppResult<void>? sleepResult; | ||
| 22 | + AppResult<void>? activityTargetResult; | ||
| 23 | + | ||
| 24 | + if (commonData.isNotEmpty) { | ||
| 25 | + commonResult = await _api.uploadCommonData( | ||
| 26 | + AppleHealthCommonUploadRequest(dataList: commonData), | ||
| 27 | + ); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + if (sleepData.isNotEmpty) { | ||
| 31 | + sleepResult = await _api.uploadSleepData( | ||
| 32 | + AppleHealthSleepUploadRequest(dataList: sleepData), | ||
| 33 | + ); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + if (activityTarget != null) { | ||
| 37 | + activityTargetResult = await _api.uploadActivityTarget(activityTarget); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + return AppleHealthUploadResult( | ||
| 41 | + commonUploadSuccess: | ||
| 42 | + commonResult == null || commonResult is AppSuccess<void>, | ||
| 43 | + sleepUploadSuccess: | ||
| 44 | + sleepResult == null || sleepResult is AppSuccess<void>, | ||
| 45 | + activityTargetUploadSuccess: activityTargetResult == null || | ||
| 46 | + activityTargetResult is AppSuccess<void>, | ||
| 47 | + ); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + Future<AppResult<AppleHealthLatestUploadRecordList>> | ||
| 51 | + getLatestCommonUploadRecordList() { | ||
| 52 | + return _api.getLatestCommonUploadRecordList(); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + Future<AppResult<AppleHealthLatestSleepUploadRecord>> | ||
| 56 | + getLatestSleepUploadRecord() { | ||
| 57 | + return _api.getLatestSleepUploadRecord(); | ||
| 58 | + } | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +class AppleHealthUploadResult { | ||
| 62 | + const AppleHealthUploadResult({ | ||
| 63 | + required this.commonUploadSuccess, | ||
| 64 | + required this.sleepUploadSuccess, | ||
| 65 | + required this.activityTargetUploadSuccess, | ||
| 66 | + }); | ||
| 67 | + | ||
| 68 | + final bool commonUploadSuccess; | ||
| 69 | + final bool sleepUploadSuccess; | ||
| 70 | + final bool activityTargetUploadSuccess; | ||
| 71 | + | ||
| 72 | + bool get isSuccess => | ||
| 73 | + commonUploadSuccess && sleepUploadSuccess && activityTargetUploadSuccess; | ||
| 74 | +} |
| 1 | +import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; | ||
| 2 | + | ||
| 3 | +import 'models/apple_health_upload_sample.dart'; | ||
| 4 | +import 'models/health_upload_data_type.dart'; | ||
| 5 | +import 'models/upload_active_energy.dart'; | ||
| 6 | +import 'models/upload_exercise.dart'; | ||
| 7 | +import 'models/upload_heart_rate.dart'; | ||
| 8 | +import 'models/upload_hrv.dart'; | ||
| 9 | +import 'models/upload_irregular_heart_rhythm.dart'; | ||
| 10 | +import 'models/upload_oxygen_saturation.dart'; | ||
| 11 | +import 'models/upload_respiratory_rate.dart'; | ||
| 12 | +import 'models/upload_resting_heart_rate.dart'; | ||
| 13 | +import 'models/upload_sleep.dart'; | ||
| 14 | +import 'models/upload_sleeping_heart_rate.dart'; | ||
| 15 | +import 'models/upload_sleeping_wrist_temperature.dart'; | ||
| 16 | +import 'models/upload_stand.dart'; | ||
| 17 | +import 'models/upload_step_count.dart'; | ||
| 18 | +import 'models/upload_walking_heart_rate.dart'; | ||
| 19 | + | ||
| 20 | +extension HealthUploadDataPointMapper on HealthUploadDataPoint { | ||
| 21 | + AppleHealthUploadSample? toAppleHealthUploadSample() { | ||
| 22 | + switch (HealthUploadDataType.fromValue(dataType)) { | ||
| 23 | + case HealthUploadDataType.hrv: | ||
| 24 | + return HealthHrvUploadData(time: time, value: value); | ||
| 25 | + case HealthUploadDataType.heartRate: | ||
| 26 | + return HealthHeartRateUploadData(time: time, value: value); | ||
| 27 | + case HealthUploadDataType.oxygenSaturation: | ||
| 28 | + return HealthOxygenSaturationUploadData(time: time, value: value); | ||
| 29 | + case HealthUploadDataType.activeEnergy: | ||
| 30 | + return HealthActiveEnergyUploadData(time: time, value: value); | ||
| 31 | + case HealthUploadDataType.exercise: | ||
| 32 | + return HealthExerciseUploadData(time: time, value: value); | ||
| 33 | + case HealthUploadDataType.stand: | ||
| 34 | + return HealthStandUploadData(time: time, value: value); | ||
| 35 | + case HealthUploadDataType.steps: | ||
| 36 | + return HealthStepCountUploadData(time: time, value: value); | ||
| 37 | + case HealthUploadDataType.walkingHeartRate: | ||
| 38 | + return HealthWalkingHeartRateUploadData(time: time, value: value); | ||
| 39 | + case HealthUploadDataType.restingHeartRate: | ||
| 40 | + return HealthRestingHeartRateUploadData(time: time, value: value); | ||
| 41 | + case HealthUploadDataType.sleepingHeartRate: | ||
| 42 | + return HealthSleepingHeartRateUploadData(time: time, value: value); | ||
| 43 | + case HealthUploadDataType.sleepingWristTemperature: | ||
| 44 | + return HealthSleepingWristTemperatureUploadData( | ||
| 45 | + time: time, | ||
| 46 | + value: value, | ||
| 47 | + ); | ||
| 48 | + case HealthUploadDataType.respiratoryRate: | ||
| 49 | + return HealthRespiratoryRateUploadData(time: time, value: value); | ||
| 50 | + case HealthUploadDataType.irregularHeartRhythm: | ||
| 51 | + return HealthIrregularHeartRhythmUploadData(time: time, value: value); | ||
| 52 | + case HealthUploadDataType.unknown: | ||
| 53 | + case HealthUploadDataType.sleep: | ||
| 54 | + return null; | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +extension HealthSleepUploadDataPointMapper on HealthSleepUploadDataPoint { | ||
| 60 | + HealthSleepUploadData toHealthSleepUploadData() { | ||
| 61 | + return HealthSleepUploadData( | ||
| 62 | + dataType: dataType, | ||
| 63 | + fromTime: fromTime, | ||
| 64 | + toTime: toTime, | ||
| 65 | + ); | ||
| 66 | + } | ||
| 67 | +} |
| 1 | +import 'health_upload_data_type.dart'; | ||
| 2 | + | ||
| 3 | +class AppleHealthLatestUploadRecord { | ||
| 4 | + const AppleHealthLatestUploadRecord({ | ||
| 5 | + this.dataType, | ||
| 6 | + this.latestDataTime, | ||
| 7 | + }); | ||
| 8 | + | ||
| 9 | + final HealthUploadDataType? dataType; | ||
| 10 | + final int? latestDataTime; | ||
| 11 | + | ||
| 12 | + factory AppleHealthLatestUploadRecord.fromJson(Map<String, dynamic> json) { | ||
| 13 | + final rawDataType = json['data_type']; | ||
| 14 | + return AppleHealthLatestUploadRecord( | ||
| 15 | + dataType: rawDataType is int | ||
| 16 | + ? HealthUploadDataType.fromValue(rawDataType) | ||
| 17 | + : null, | ||
| 18 | + latestDataTime: json['latest_data_time'] as int?, | ||
| 19 | + ); | ||
| 20 | + } | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +class AppleHealthLatestUploadRecordList { | ||
| 24 | + const AppleHealthLatestUploadRecordList({this.latestDataTimeList}); | ||
| 25 | + | ||
| 26 | + final List<AppleHealthLatestUploadRecord>? latestDataTimeList; | ||
| 27 | + | ||
| 28 | + factory AppleHealthLatestUploadRecordList.fromJson( | ||
| 29 | + Map<String, dynamic> json, | ||
| 30 | + ) { | ||
| 31 | + return AppleHealthLatestUploadRecordList( | ||
| 32 | + latestDataTimeList: (json['latest_data_time_list'] as List<dynamic>?) | ||
| 33 | + ?.map((item) => AppleHealthLatestUploadRecord.fromJson( | ||
| 34 | + item as Map<String, dynamic>, | ||
| 35 | + )) | ||
| 36 | + .toList(), | ||
| 37 | + ); | ||
| 38 | + } | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +class AppleHealthLatestSleepUploadRecord { | ||
| 42 | + const AppleHealthLatestSleepUploadRecord({this.latestDataTime}); | ||
| 43 | + | ||
| 44 | + final int? latestDataTime; | ||
| 45 | + | ||
| 46 | + factory AppleHealthLatestSleepUploadRecord.fromJson( | ||
| 47 | + Map<String, dynamic> json, | ||
| 48 | + ) { | ||
| 49 | + return AppleHealthLatestSleepUploadRecord( | ||
| 50 | + latestDataTime: json['latest_data_time'] as int?, | ||
| 51 | + ); | ||
| 52 | + } | ||
| 53 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'upload_sleep.dart'; | ||
| 3 | + | ||
| 4 | +class AppleHealthCommonUploadRequest { | ||
| 5 | + const AppleHealthCommonUploadRequest({required this.dataList}); | ||
| 6 | + | ||
| 7 | + final List<AppleHealthUploadSample> dataList; | ||
| 8 | + | ||
| 9 | + Map<String, dynamic> toJson() { | ||
| 10 | + return { | ||
| 11 | + 'data_list': dataList.map((item) => item.toJson()).toList(), | ||
| 12 | + }; | ||
| 13 | + } | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +class AppleHealthSleepUploadRequest { | ||
| 17 | + const AppleHealthSleepUploadRequest({required this.dataList}); | ||
| 18 | + | ||
| 19 | + final List<HealthSleepUploadData> dataList; | ||
| 20 | + | ||
| 21 | + Map<String, dynamic> toJson() { | ||
| 22 | + return { | ||
| 23 | + 'data_list': dataList.map((item) => item.toJson()).toList(), | ||
| 24 | + }; | ||
| 25 | + } | ||
| 26 | +} |
| 1 | +import 'health_upload_data_type.dart'; | ||
| 2 | + | ||
| 3 | +abstract class AppleHealthUploadSample { | ||
| 4 | + const AppleHealthUploadSample({ | ||
| 5 | + required this.dataType, | ||
| 6 | + required this.time, | ||
| 7 | + required this.value, | ||
| 8 | + }); | ||
| 9 | + | ||
| 10 | + final HealthUploadDataType dataType; | ||
| 11 | + final int time; | ||
| 12 | + final double value; | ||
| 13 | + | ||
| 14 | + Map<String, dynamic> toJson() => { | ||
| 15 | + 'data_type': dataType.value, | ||
| 16 | + 'time': time, | ||
| 17 | + 'value': value, | ||
| 18 | + }; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +int healthUploadTimeFromJson(Object? value) { | ||
| 22 | + if (value is int) return value; | ||
| 23 | + if (value is double) return value.round(); | ||
| 24 | + if (value is String) return int.tryParse(value) ?? 0; | ||
| 25 | + return 0; | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +double healthUploadDoubleFromJson(Object? value) { | ||
| 29 | + if (value is num) return value.toDouble(); | ||
| 30 | + if (value is String) return double.tryParse(value) ?? 0; | ||
| 31 | + return 0; | ||
| 32 | +} |
| 1 | +enum HealthUploadDataType { | ||
| 2 | + unknown(0), | ||
| 3 | + hrv(1), | ||
| 4 | + heartRate(2), | ||
| 5 | + oxygenSaturation(3), | ||
| 6 | + activeEnergy(4), | ||
| 7 | + exercise(5), | ||
| 8 | + stand(6), | ||
| 9 | + steps(7), | ||
| 10 | + walkingHeartRate(8), | ||
| 11 | + restingHeartRate(9), | ||
| 12 | + sleepingHeartRate(10), | ||
| 13 | + sleepingWristTemperature(11), | ||
| 14 | + respiratoryRate(12), | ||
| 15 | + irregularHeartRhythm(13), | ||
| 16 | + sleep(100); | ||
| 17 | + | ||
| 18 | + const HealthUploadDataType(this.value); | ||
| 19 | + | ||
| 20 | + final int value; | ||
| 21 | + | ||
| 22 | + static HealthUploadDataType fromValue(int value) { | ||
| 23 | + return HealthUploadDataType.values.firstWhere( | ||
| 24 | + (type) => type.value == value, | ||
| 25 | + orElse: () => HealthUploadDataType.unknown, | ||
| 26 | + ); | ||
| 27 | + } | ||
| 28 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthActiveEnergyUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthActiveEnergyUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.activeEnergy); | ||
| 9 | + | ||
| 10 | + factory HealthActiveEnergyUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthActiveEnergyUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +class HealthActivityTargetUploadData { | ||
| 2 | + const HealthActivityTargetUploadData({ | ||
| 3 | + this.move, | ||
| 4 | + this.stand, | ||
| 5 | + }); | ||
| 6 | + | ||
| 7 | + final int? move; | ||
| 8 | + final int? stand; | ||
| 9 | + | ||
| 10 | + factory HealthActivityTargetUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthActivityTargetUploadData( | ||
| 12 | + move: json['move'] as int?, | ||
| 13 | + stand: json['stand'] as int?, | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + Map<String, dynamic> toJson() { | ||
| 18 | + return { | ||
| 19 | + if (move != null) 'move': move, | ||
| 20 | + if (stand != null) 'stand': stand, | ||
| 21 | + }; | ||
| 22 | + } | ||
| 23 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthExerciseUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthExerciseUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.exercise); | ||
| 9 | + | ||
| 10 | + factory HealthExerciseUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthExerciseUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthHeartRateUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthHeartRateUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.heartRate); | ||
| 9 | + | ||
| 10 | + factory HealthHeartRateUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthHeartRateUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthHrvUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthHrvUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.hrv); | ||
| 9 | + | ||
| 10 | + factory HealthHrvUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthHrvUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthIrregularHeartRhythmUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthIrregularHeartRhythmUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.irregularHeartRhythm); | ||
| 9 | + | ||
| 10 | + factory HealthIrregularHeartRhythmUploadData.fromJson( | ||
| 11 | + Map<String, dynamic> json, | ||
| 12 | + ) { | ||
| 13 | + return HealthIrregularHeartRhythmUploadData( | ||
| 14 | + time: healthUploadTimeFromJson(json['time']), | ||
| 15 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 16 | + ); | ||
| 17 | + } | ||
| 18 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthOxygenSaturationUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthOxygenSaturationUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.oxygenSaturation); | ||
| 9 | + | ||
| 10 | + factory HealthOxygenSaturationUploadData.fromJson( | ||
| 11 | + Map<String, dynamic> json, | ||
| 12 | + ) { | ||
| 13 | + return HealthOxygenSaturationUploadData( | ||
| 14 | + time: healthUploadTimeFromJson(json['time']), | ||
| 15 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 16 | + ); | ||
| 17 | + } | ||
| 18 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthRespiratoryRateUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthRespiratoryRateUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.respiratoryRate); | ||
| 9 | + | ||
| 10 | + factory HealthRespiratoryRateUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthRespiratoryRateUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthRestingHeartRateUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthRestingHeartRateUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.restingHeartRate); | ||
| 9 | + | ||
| 10 | + factory HealthRestingHeartRateUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthRestingHeartRateUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | + | ||
| 3 | +class HealthSleepUploadData { | ||
| 4 | + const HealthSleepUploadData({ | ||
| 5 | + required this.dataType, | ||
| 6 | + required this.fromTime, | ||
| 7 | + required this.toTime, | ||
| 8 | + }); | ||
| 9 | + | ||
| 10 | + final int dataType; | ||
| 11 | + final int fromTime; | ||
| 12 | + final int toTime; | ||
| 13 | + | ||
| 14 | + Map<String, dynamic> toJson() { | ||
| 15 | + return { | ||
| 16 | + 'data_type': dataType, | ||
| 17 | + 'from_time': fromTime, | ||
| 18 | + 'to_time': toTime, | ||
| 19 | + }; | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + factory HealthSleepUploadData.fromJson(Map<String, dynamic> json) { | ||
| 23 | + return HealthSleepUploadData( | ||
| 24 | + dataType: healthUploadTimeFromJson(json['data_type']), | ||
| 25 | + fromTime: healthUploadTimeFromJson(json['from_time']), | ||
| 26 | + toTime: healthUploadTimeFromJson(json['to_time']), | ||
| 27 | + ); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + @override | ||
| 31 | + bool operator ==(Object other) { | ||
| 32 | + if (identical(this, other)) return true; | ||
| 33 | + return other is HealthSleepUploadData && | ||
| 34 | + other.fromTime == fromTime && | ||
| 35 | + other.toTime == toTime && | ||
| 36 | + other.dataType == dataType; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + @override | ||
| 40 | + int get hashCode => Object.hash(dataType, fromTime, toTime); | ||
| 41 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthSleepingHeartRateUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthSleepingHeartRateUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.sleepingHeartRate); | ||
| 9 | + | ||
| 10 | + factory HealthSleepingHeartRateUploadData.fromJson( | ||
| 11 | + Map<String, dynamic> json, | ||
| 12 | + ) { | ||
| 13 | + return HealthSleepingHeartRateUploadData( | ||
| 14 | + time: healthUploadTimeFromJson(json['time']), | ||
| 15 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 16 | + ); | ||
| 17 | + } | ||
| 18 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthSleepingWristTemperatureUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthSleepingWristTemperatureUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.sleepingWristTemperature); | ||
| 9 | + | ||
| 10 | + factory HealthSleepingWristTemperatureUploadData.fromJson( | ||
| 11 | + Map<String, dynamic> json, | ||
| 12 | + ) { | ||
| 13 | + return HealthSleepingWristTemperatureUploadData( | ||
| 14 | + time: healthUploadTimeFromJson(json['time']), | ||
| 15 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 16 | + ); | ||
| 17 | + } | ||
| 18 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthStandUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthStandUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.stand); | ||
| 9 | + | ||
| 10 | + factory HealthStandUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthStandUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthStepCountUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthStepCountUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.steps); | ||
| 9 | + | ||
| 10 | + factory HealthStepCountUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthStepCountUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | +import 'apple_health_upload_sample.dart'; | ||
| 2 | +import 'health_upload_data_type.dart'; | ||
| 3 | + | ||
| 4 | +class HealthWalkingHeartRateUploadData extends AppleHealthUploadSample { | ||
| 5 | + const HealthWalkingHeartRateUploadData({ | ||
| 6 | + required super.time, | ||
| 7 | + required super.value, | ||
| 8 | + }) : super(dataType: HealthUploadDataType.walkingHeartRate); | ||
| 9 | + | ||
| 10 | + factory HealthWalkingHeartRateUploadData.fromJson(Map<String, dynamic> json) { | ||
| 11 | + return HealthWalkingHeartRateUploadData( | ||
| 12 | + time: healthUploadTimeFromJson(json['time']), | ||
| 13 | + value: healthUploadDoubleFromJson(json['value']), | ||
| 14 | + ); | ||
| 15 | + } | ||
| 16 | +} |
| 1 | import 'package:get/get.dart'; | 1 | import 'package:get/get.dart'; |
| 2 | 2 | ||
| 3 | +import '../apple_health_upload/apple_health_upload_api.dart'; | ||
| 4 | +import '../apple_health_upload/apple_health_upload_tool.dart'; | ||
| 3 | import '../../core/config/app_environment_config.dart'; | 5 | import '../../core/config/app_environment_config.dart'; |
| 4 | import '../../core/error/app_error_handler.dart'; | 6 | import '../../core/error/app_error_handler.dart'; |
| 5 | import '../../core/network/api/config_api.dart'; | 7 | import '../../core/network/api/config_api.dart'; |
| @@ -71,6 +73,11 @@ void registerUserSessionDeps() { | @@ -71,6 +73,11 @@ void registerUserSessionDeps() { | ||
| 71 | /// Health data API and upload pipeline (lazy, recreated after dispose). | 73 | /// Health data API and upload pipeline (lazy, recreated after dispose). |
| 72 | void registerHealthDeps(DioClient dioClient) { | 74 | void registerHealthDeps(DioClient dioClient) { |
| 73 | Get.lazyPut(() => HealthApi(dioClient), fenix: true); | 75 | Get.lazyPut(() => HealthApi(dioClient), fenix: true); |
| 76 | + Get.lazyPut(() => AppleHealthUploadApi(dioClient), fenix: true); | ||
| 77 | + Get.lazyPut( | ||
| 78 | + () => AppleHealthUploadTool(Get.find<AppleHealthUploadApi>()), | ||
| 79 | + fenix: true, | ||
| 80 | + ); | ||
| 74 | Get.lazyPut( | 81 | Get.lazyPut( |
| 75 | () => HealthKitUploadService(Get.find<HealthApi>()), | 82 | () => HealthKitUploadService(Get.find<HealthApi>()), |
| 76 | fenix: true, | 83 | fenix: true, |
| 1 | +import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart'; | ||
| 2 | +import 'package:get/get.dart'; | ||
| 3 | + | ||
| 4 | +import '../controllers/apple_health_upload_test_controller.dart'; | ||
| 5 | + | ||
| 6 | +class AppleHealthUploadTestBinding extends Bindings { | ||
| 7 | + @override | ||
| 8 | + void dependencies() { | ||
| 9 | + Get.lazyPut<AppleHealthUploadTestController>( | ||
| 10 | + () => AppleHealthUploadTestController( | ||
| 11 | + Get.find<AppleHealthUploadTool>(), | ||
| 12 | + ), | ||
| 13 | + ); | ||
| 14 | + } | ||
| 15 | +} |
lib/app/modules/apple_health_upload_test/controllers/apple_health_upload_test_controller.dart
0 → 100644
| 1 | +import 'dart:convert'; | ||
| 2 | + | ||
| 3 | +import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/app/apple_health_upload/health_kit_upload_mapper.dart'; | ||
| 5 | +import 'package:doublefeel_flutter/app/apple_health_upload/models/apple_health_upload_sample.dart'; | ||
| 6 | +import 'package:doublefeel_flutter/app/apple_health_upload/models/upload_activity_target.dart'; | ||
| 7 | +import 'package:doublefeel_flutter/app/apple_health_upload/models/upload_sleep.dart'; | ||
| 8 | +import 'package:doublefeel_flutter/core/util/app_toast.dart'; | ||
| 9 | +import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; | ||
| 10 | +import 'package:get/get.dart'; | ||
| 11 | + | ||
| 12 | +class AppleHealthUploadTestController extends GetxController { | ||
| 13 | + AppleHealthUploadTestController(this._uploadTool); | ||
| 14 | + | ||
| 15 | + final AppleHealthUploadTool _uploadTool; | ||
| 16 | + final HealthKitHostApi _hostApi = HealthKitHostApi(); | ||
| 17 | + | ||
| 18 | + final logText = ''.obs; | ||
| 19 | + final permissionTitle = '检测 AppleHealth 权限'.obs; | ||
| 20 | + final isSyncing = false.obs; | ||
| 21 | + final isUploading = false.obs; | ||
| 22 | + | ||
| 23 | + final List<AppleHealthUploadSample> _commonData = []; | ||
| 24 | + final List<HealthSleepUploadData> _sleepData = []; | ||
| 25 | + HealthActivityTargetUploadData? _activityTarget; | ||
| 26 | + | ||
| 27 | + Future<void> checkPermission() async { | ||
| 28 | + _appendLog('开始检测 AppleHealth 权限'); | ||
| 29 | + try { | ||
| 30 | + final hasPermission = await _hostApi.checkHealthAppAuthorization(); | ||
| 31 | + if (hasPermission) { | ||
| 32 | + permissionTitle.value = '权限:已授权'; | ||
| 33 | + _appendLog('AppleHealth 权限已授权'); | ||
| 34 | + return; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + final granted = await _hostApi.requestHealthClientAuthorization(); | ||
| 38 | + permissionTitle.value = granted ? '权限:已授权' : '权限:未授权'; | ||
| 39 | + _appendLog('AppleHealth 授权请求结果:$granted'); | ||
| 40 | + } catch (error, stackTrace) { | ||
| 41 | + permissionTitle.value = '权限:检测失败'; | ||
| 42 | + _appendLog('权限检测失败:$error'); | ||
| 43 | + _appendLog(stackTrace.toString()); | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + Future<void> syncHealthData() async { | ||
| 48 | + if (isSyncing.value) return; | ||
| 49 | + isSyncing.value = true; | ||
| 50 | + _commonData.clear(); | ||
| 51 | + _sleepData.clear(); | ||
| 52 | + _activityTarget = null; | ||
| 53 | + _appendLog('开始同步 HealthKitHostApi 数据'); | ||
| 54 | + | ||
| 55 | + final endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; | ||
| 56 | + final startTime = DateTime.now() | ||
| 57 | + .subtract(const Duration(days: 7)) | ||
| 58 | + .millisecondsSinceEpoch ~/ | ||
| 59 | + 1000; | ||
| 60 | + _appendLog('同步范围:$startTime -> $endTime'); | ||
| 61 | + | ||
| 62 | + try { | ||
| 63 | + await _fetchCommon( | ||
| 64 | + label: 'hrv', | ||
| 65 | + fetch: () => _hostApi.fetchHrvData(startTime, endTime), | ||
| 66 | + ); | ||
| 67 | + await _fetchCommon( | ||
| 68 | + label: 'heartRate', | ||
| 69 | + fetch: () => _hostApi.fetchHeartRateData(startTime, endTime), | ||
| 70 | + ); | ||
| 71 | + await _fetchCommon( | ||
| 72 | + label: 'walkingHeartRate', | ||
| 73 | + fetch: () => _hostApi.fetchWalkingHeartRateData(startTime, endTime), | ||
| 74 | + ); | ||
| 75 | + await _fetchCommon( | ||
| 76 | + label: 'restingHeartRate', | ||
| 77 | + fetch: () => _hostApi.fetchRestingHeartRateData(startTime, endTime), | ||
| 78 | + ); | ||
| 79 | + await _fetchCommon( | ||
| 80 | + label: 'sleepingHeartRate', | ||
| 81 | + fetch: () => _hostApi.fetchSleepingHeartRateData(startTime, endTime), | ||
| 82 | + ); | ||
| 83 | + await _fetchCommon( | ||
| 84 | + label: 'oxygenSaturation', | ||
| 85 | + fetch: () => _hostApi.fetchOxygenSaturationData(startTime, endTime), | ||
| 86 | + ); | ||
| 87 | + await _fetchCommon( | ||
| 88 | + label: 'activeEnergy', | ||
| 89 | + fetch: () => _hostApi.fetchActiveEnergyData(startTime, endTime), | ||
| 90 | + ); | ||
| 91 | + await _fetchCommon( | ||
| 92 | + label: 'exercise', | ||
| 93 | + fetch: () => _hostApi.fetchExerciseData(startTime, endTime), | ||
| 94 | + ); | ||
| 95 | + await _fetchCommon( | ||
| 96 | + label: 'stand', | ||
| 97 | + fetch: () => _hostApi.fetchStandData(startTime, endTime), | ||
| 98 | + ); | ||
| 99 | + await _fetchCommon( | ||
| 100 | + label: 'stepCount', | ||
| 101 | + fetch: () => _hostApi.fetchStepCountData(startTime, endTime), | ||
| 102 | + ); | ||
| 103 | + await _fetchSleep(startTime, endTime); | ||
| 104 | + await _fetchCommon( | ||
| 105 | + label: 'sleepingWristTemperature', | ||
| 106 | + fetch: () => | ||
| 107 | + _hostApi.fetchSleepingWristTemperatureData(startTime, endTime), | ||
| 108 | + ); | ||
| 109 | + await _fetchCommon( | ||
| 110 | + label: 'respiratoryRate', | ||
| 111 | + fetch: () => _hostApi.fetchRespiratoryRateData(startTime, endTime), | ||
| 112 | + ); | ||
| 113 | + await _fetchCommon( | ||
| 114 | + label: 'irregularHeartRhythm', | ||
| 115 | + fetch: () => _hostApi.fetchIrregularHeartRhythmData(startTime, endTime), | ||
| 116 | + ); | ||
| 117 | + await _fetchActivityTarget(startTime, endTime); | ||
| 118 | + | ||
| 119 | + _appendLog( | ||
| 120 | + '同步完成:common=${_commonData.length}, sleep=${_sleepData.length}, ' | ||
| 121 | + 'activityTarget=${_activityTarget == null ? 0 : 1}', | ||
| 122 | + ); | ||
| 123 | + } catch (error, stackTrace) { | ||
| 124 | + _appendLog('同步失败:$error'); | ||
| 125 | + _appendLog(stackTrace.toString()); | ||
| 126 | + } finally { | ||
| 127 | + isSyncing.value = false; | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + Future<void> uploadHealthData() async { | ||
| 132 | + if (isUploading.value) return; | ||
| 133 | + if (_commonData.isEmpty && _sleepData.isEmpty && _activityTarget == null) { | ||
| 134 | + _appendLog('没有缓存数据,先执行一次同步'); | ||
| 135 | + await syncHealthData(); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + isUploading.value = true; | ||
| 139 | + _appendLog('开始上传到服务器'); | ||
| 140 | + try { | ||
| 141 | + final result = await _uploadTool.upload( | ||
| 142 | + commonData: _commonData, | ||
| 143 | + sleepData: _sleepData, | ||
| 144 | + activityTarget: _activityTarget, | ||
| 145 | + ); | ||
| 146 | + _appendLog( | ||
| 147 | + '上传结果:common=${result.commonUploadSuccess}, ' | ||
| 148 | + 'sleep=${result.sleepUploadSuccess}, ' | ||
| 149 | + 'activityTarget=${result.activityTargetUploadSuccess}, ' | ||
| 150 | + 'success=${result.isSuccess}', | ||
| 151 | + ); | ||
| 152 | + AppToast.show(result.isSuccess ? '上传完成' : '上传失败'); | ||
| 153 | + } catch (error, stackTrace) { | ||
| 154 | + _appendLog('上传异常:$error'); | ||
| 155 | + _appendLog(stackTrace.toString()); | ||
| 156 | + AppToast.show('上传异常'); | ||
| 157 | + } finally { | ||
| 158 | + isUploading.value = false; | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + Future<void> _fetchCommon({ | ||
| 163 | + required String label, | ||
| 164 | + required Future<List<HealthUploadDataPoint>> Function() fetch, | ||
| 165 | + }) async { | ||
| 166 | + final points = await fetch(); | ||
| 167 | + final models = points | ||
| 168 | + .map((point) => point.toAppleHealthUploadSample()) | ||
| 169 | + .whereType<AppleHealthUploadSample>() | ||
| 170 | + .toList(); | ||
| 171 | + _commonData.addAll(models); | ||
| 172 | + _appendDataLog(label, models.map((item) => item.toJson()).toList()); | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + Future<void> _fetchSleep(int startTime, int endTime) async { | ||
| 176 | + final points = await _hostApi.fetchSleepData(startTime, endTime); | ||
| 177 | + final models = | ||
| 178 | + points.map((item) => item.toHealthSleepUploadData()).toList(); | ||
| 179 | + _sleepData.addAll(models); | ||
| 180 | + _appendDataLog('sleep', models.map((item) => item.toJson()).toList()); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + Future<void> _fetchActivityTarget(int startTime, int endTime) async { | ||
| 184 | + final target = await _hostApi.fetchActivityTargetData(startTime, endTime); | ||
| 185 | + if (target != null) { | ||
| 186 | + _activityTarget = HealthActivityTargetUploadData( | ||
| 187 | + move: target.move, | ||
| 188 | + stand: target.stand, | ||
| 189 | + ); | ||
| 190 | + } | ||
| 191 | + _appendDataLog( | ||
| 192 | + 'activityTarget', | ||
| 193 | + _activityTarget == null ? [] : [_activityTarget!.toJson()], | ||
| 194 | + ); | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + void _appendDataLog(String label, List<Map<String, dynamic>> data) { | ||
| 198 | + _appendLog('[$label] count=${data.length}'); | ||
| 199 | + if (data.isNotEmpty) { | ||
| 200 | + _appendLog('[$label] first=${jsonEncode(data.first)}'); | ||
| 201 | + _appendLog('[$label] last=${jsonEncode(data.last)}'); | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + void _appendLog(String message) { | ||
| 206 | + final time = DateTime.now().toIso8601String(); | ||
| 207 | + logText.value = '${logText.value}[$time] $message\n'; | ||
| 208 | + } | ||
| 209 | +} |
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | +import 'package:get/get.dart'; | ||
| 3 | + | ||
| 4 | +import '../controllers/apple_health_upload_test_controller.dart'; | ||
| 5 | + | ||
| 6 | +class AppleHealthUploadTestView | ||
| 7 | + extends GetView<AppleHealthUploadTestController> { | ||
| 8 | + const AppleHealthUploadTestView({super.key}); | ||
| 9 | + | ||
| 10 | + @override | ||
| 11 | + Widget build(BuildContext context) { | ||
| 12 | + return Scaffold( | ||
| 13 | + appBar: AppBar( | ||
| 14 | + title: const Text('AppleHealth 上传测试'), | ||
| 15 | + ), | ||
| 16 | + body: SafeArea( | ||
| 17 | + child: Column( | ||
| 18 | + children: [ | ||
| 19 | + Expanded( | ||
| 20 | + child: Container( | ||
| 21 | + width: double.infinity, | ||
| 22 | + margin: const EdgeInsets.all(12), | ||
| 23 | + padding: const EdgeInsets.all(12), | ||
| 24 | + decoration: BoxDecoration( | ||
| 25 | + color: const Color(0xFF101418), | ||
| 26 | + borderRadius: BorderRadius.circular(8), | ||
| 27 | + ), | ||
| 28 | + child: Obx( | ||
| 29 | + () => SingleChildScrollView( | ||
| 30 | + reverse: true, | ||
| 31 | + child: SelectableText( | ||
| 32 | + controller.logText.value.isEmpty | ||
| 33 | + ? '点击底部按钮开始测试 HealthKitHostApiImpl 数据。' | ||
| 34 | + : controller.logText.value, | ||
| 35 | + style: const TextStyle( | ||
| 36 | + color: Color(0xFFE6EDF3), | ||
| 37 | + fontSize: 12, | ||
| 38 | + height: 1.45, | ||
| 39 | + fontFamily: 'Menlo', | ||
| 40 | + ), | ||
| 41 | + ), | ||
| 42 | + ), | ||
| 43 | + ), | ||
| 44 | + ), | ||
| 45 | + ), | ||
| 46 | + Padding( | ||
| 47 | + padding: const EdgeInsets.fromLTRB(12, 0, 12, 12), | ||
| 48 | + child: Obx( | ||
| 49 | + () => Row( | ||
| 50 | + children: [ | ||
| 51 | + Expanded( | ||
| 52 | + child: _BottomButton( | ||
| 53 | + label: controller.permissionTitle.value, | ||
| 54 | + onPressed: controller.checkPermission, | ||
| 55 | + ), | ||
| 56 | + ), | ||
| 57 | + const SizedBox(width: 8), | ||
| 58 | + Expanded( | ||
| 59 | + child: _BottomButton( | ||
| 60 | + label: controller.isSyncing.value ? '同步中' : '同步', | ||
| 61 | + onPressed: controller.isSyncing.value | ||
| 62 | + ? null | ||
| 63 | + : controller.syncHealthData, | ||
| 64 | + ), | ||
| 65 | + ), | ||
| 66 | + const SizedBox(width: 8), | ||
| 67 | + Expanded( | ||
| 68 | + child: _BottomButton( | ||
| 69 | + label: controller.isUploading.value ? '上传中' : '上传', | ||
| 70 | + onPressed: controller.isUploading.value | ||
| 71 | + ? null | ||
| 72 | + : controller.uploadHealthData, | ||
| 73 | + ), | ||
| 74 | + ), | ||
| 75 | + ], | ||
| 76 | + ), | ||
| 77 | + ), | ||
| 78 | + ), | ||
| 79 | + ], | ||
| 80 | + ), | ||
| 81 | + ), | ||
| 82 | + ); | ||
| 83 | + } | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +class _BottomButton extends StatelessWidget { | ||
| 87 | + const _BottomButton({ | ||
| 88 | + required this.label, | ||
| 89 | + required this.onPressed, | ||
| 90 | + }); | ||
| 91 | + | ||
| 92 | + final String label; | ||
| 93 | + final VoidCallback? onPressed; | ||
| 94 | + | ||
| 95 | + @override | ||
| 96 | + Widget build(BuildContext context) { | ||
| 97 | + return SizedBox( | ||
| 98 | + height: 44, | ||
| 99 | + child: FilledButton( | ||
| 100 | + onPressed: onPressed, | ||
| 101 | + style: FilledButton.styleFrom( | ||
| 102 | + padding: const EdgeInsets.symmetric(horizontal: 8), | ||
| 103 | + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), | ||
| 104 | + ), | ||
| 105 | + child: Text( | ||
| 106 | + label, | ||
| 107 | + maxLines: 1, | ||
| 108 | + overflow: TextOverflow.ellipsis, | ||
| 109 | + textAlign: TextAlign.center, | ||
| 110 | + style: const TextStyle(fontSize: 13), | ||
| 111 | + ), | ||
| 112 | + ), | ||
| 113 | + ); | ||
| 114 | + } | ||
| 115 | +} |
| 1 | +import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | ||
| 1 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; | 2 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; |
| 2 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 3 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 3 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | 4 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; |
| @@ -29,4 +30,15 @@ class MyController extends GetxController { | @@ -29,4 +30,15 @@ class MyController extends GetxController { | ||
| 29 | } | 30 | } |
| 30 | } | 31 | } |
| 31 | } | 32 | } |
| 33 | + | ||
| 34 | + void goWatchThemePage() { | ||
| 35 | + Get.toNamed( | ||
| 36 | + Routes.WATCH_THEME, | ||
| 37 | + arguments: {'isPremium': false, 'hasCustomThemes': false}, | ||
| 38 | + ); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + void testAppleHealthUpload() { | ||
| 42 | + Get.toNamed(Routes.APPLE_HEALTH_UPLOAD_TEST); | ||
| 43 | + } | ||
| 32 | } | 44 | } |
| @@ -12,7 +12,6 @@ import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | @@ -12,7 +12,6 @@ import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | ||
| 12 | import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; | 12 | import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; |
| 13 | import 'package:doublefeel_flutter/data/models/user/user_models.dart'; | 13 | import 'package:doublefeel_flutter/data/models/user/user_models.dart'; |
| 14 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 14 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 15 | -import 'package:flutter/cupertino.dart'; | ||
| 16 | import 'package:flutter/foundation.dart'; | 15 | import 'package:flutter/foundation.dart'; |
| 17 | import 'package:flutter/material.dart'; | 16 | import 'package:flutter/material.dart'; |
| 18 | import 'package:get/get.dart'; | 17 | import 'package:get/get.dart'; |
| @@ -56,10 +55,7 @@ class MyTab extends GetView<MyController> { | @@ -56,10 +55,7 @@ class MyTab extends GetView<MyController> { | ||
| 56 | : const _UnlockPremiumCard(), | 55 | : const _UnlockPremiumCard(), |
| 57 | const SizedBox(height: 12), | 56 | const SizedBox(height: 12), |
| 58 | _WatchThemeCard( | 57 | _WatchThemeCard( |
| 59 | - onTap: () => Get.toNamed( | ||
| 60 | - Routes.WATCH_THEME, | ||
| 61 | - arguments: {'isPremium': false, 'hasCustomThemes': false}, | ||
| 62 | - ), | 58 | + onTap: () => controller.goWatchThemePage(), |
| 63 | ), | 59 | ), |
| 64 | const SizedBox(height: 12), | 60 | const SizedBox(height: 12), |
| 65 | _SettingsRow( | 61 | _SettingsRow( |
| @@ -79,6 +75,13 @@ class MyTab extends GetView<MyController> { | @@ -79,6 +75,13 @@ class MyTab extends GetView<MyController> { | ||
| 79 | Get.toNamed(Routes.ROUTE_LIST); | 75 | Get.toNamed(Routes.ROUTE_LIST); |
| 80 | }, | 76 | }, |
| 81 | ), | 77 | ), |
| 78 | + const SizedBox(height: 12), | ||
| 79 | + _SettingsRow( | ||
| 80 | + title: 'Apple Health Upload 测试', | ||
| 81 | + onTap: () { | ||
| 82 | + controller.testAppleHealthUpload(); | ||
| 83 | + }, | ||
| 84 | + ), | ||
| 82 | ], | 85 | ], |
| 83 | ], | 86 | ], |
| 84 | ); | 87 | ); |
| @@ -38,6 +38,8 @@ import '../modules/watch_theme/views/watch_theme_preview_view.dart'; | @@ -38,6 +38,8 @@ import '../modules/watch_theme/views/watch_theme_preview_view.dart'; | ||
| 38 | import '../modules/watch_theme/views/watch_theme_view.dart'; | 38 | import '../modules/watch_theme/views/watch_theme_view.dart'; |
| 39 | import '../modules/account_settings/bindings/account_settings_binding.dart'; | 39 | import '../modules/account_settings/bindings/account_settings_binding.dart'; |
| 40 | import '../modules/account_settings/views/account_settings_view.dart'; | 40 | import '../modules/account_settings/views/account_settings_view.dart'; |
| 41 | +import '../modules/apple_health_upload_test/bindings/apple_health_upload_test_binding.dart'; | ||
| 42 | +import '../modules/apple_health_upload_test/views/apple_health_upload_test_view.dart'; | ||
| 41 | import '../modules/webview/bindings/webview_binding.dart'; | 43 | import '../modules/webview/bindings/webview_binding.dart'; |
| 42 | import '../modules/webview/views/webview_page.dart'; | 44 | import '../modules/webview/views/webview_page.dart'; |
| 43 | 45 | ||
| @@ -170,5 +172,10 @@ abstract final class AppPages { | @@ -170,5 +172,10 @@ abstract final class AppPages { | ||
| 170 | page: () => const AccountSettingsView(), | 172 | page: () => const AccountSettingsView(), |
| 171 | binding: AccountSettingsBinding(), | 173 | binding: AccountSettingsBinding(), |
| 172 | ), | 174 | ), |
| 175 | + GetPage( | ||
| 176 | + name: Routes.APPLE_HEALTH_UPLOAD_TEST, | ||
| 177 | + page: () => const AppleHealthUploadTestView(), | ||
| 178 | + binding: AppleHealthUploadTestBinding(), | ||
| 179 | + ), | ||
| 173 | ]; | 180 | ]; |
| 174 | } | 181 | } |
| @@ -19,6 +19,7 @@ abstract class Routes { | @@ -19,6 +19,7 @@ abstract class Routes { | ||
| 19 | static const WATCH_THEME_CUSTOM_PREVIEW = _Paths.WATCH_THEME_CUSTOM_PREVIEW; | 19 | static const WATCH_THEME_CUSTOM_PREVIEW = _Paths.WATCH_THEME_CUSTOM_PREVIEW; |
| 20 | static const ACCOUNT_SETTINGS = _Paths.ACCOUNT_SETTINGS; | 20 | static const ACCOUNT_SETTINGS = _Paths.ACCOUNT_SETTINGS; |
| 21 | static const FRIEND_TREND = _Paths.FRIEND_TREND; | 21 | static const FRIEND_TREND = _Paths.FRIEND_TREND; |
| 22 | + static const APPLE_HEALTH_UPLOAD_TEST = _Paths.APPLE_HEALTH_UPLOAD_TEST; | ||
| 22 | } | 23 | } |
| 23 | 24 | ||
| 24 | abstract class _Paths { | 25 | abstract class _Paths { |
| @@ -39,4 +40,5 @@ abstract class _Paths { | @@ -39,4 +40,5 @@ abstract class _Paths { | ||
| 39 | static const WATCH_THEME_CUSTOM_PREVIEW = '/watch-theme/custom-preview'; | 40 | static const WATCH_THEME_CUSTOM_PREVIEW = '/watch-theme/custom-preview'; |
| 40 | static const ACCOUNT_SETTINGS = '/account-settings'; | 41 | static const ACCOUNT_SETTINGS = '/account-settings'; |
| 41 | static const FRIEND_TREND = '/friend-trend'; | 42 | static const FRIEND_TREND = '/friend-trend'; |
| 43 | + static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test'; | ||
| 42 | } | 44 | } |
| @@ -21,6 +21,8 @@ abstract final class ApiPaths { | @@ -21,6 +21,8 @@ abstract final class ApiPaths { | ||
| 21 | '/client/doublefeel/health/data_upload/common/'; | 21 | '/client/doublefeel/health/data_upload/common/'; |
| 22 | static const healthUploadSleep = | 22 | static const healthUploadSleep = |
| 23 | '/client/doublefeel/health/data_upload/sleep/'; | 23 | '/client/doublefeel/health/data_upload/sleep/'; |
| 24 | + static const healthActivityTarget = | ||
| 25 | + '/client/doublefeel/health/activity_target/'; | ||
| 24 | static const healthStatsSleep = '/client/doublefeel/health/statistics/sleep/'; | 26 | static const healthStatsSleep = '/client/doublefeel/health/statistics/sleep/'; |
| 25 | static const healthStatsActivity = | 27 | static const healthStatsActivity = |
| 26 | '/client/doublefeel/health/statistics/activity/'; | 28 | '/client/doublefeel/health/statistics/activity/'; |
| @@ -15,21 +15,22 @@ PlatformException _createConnectionError(String channelName) { | @@ -15,21 +15,22 @@ PlatformException _createConnectionError(String channelName) { | ||
| 15 | message: 'Unable to establish connection on channel: "$channelName".', | 15 | message: 'Unable to establish connection on channel: "$channelName".', |
| 16 | ); | 16 | ); |
| 17 | } | 17 | } |
| 18 | + | ||
| 18 | bool _deepEquals(Object? a, Object? b) { | 19 | bool _deepEquals(Object? a, Object? b) { |
| 19 | if (a is List && b is List) { | 20 | if (a is List && b is List) { |
| 20 | return a.length == b.length && | 21 | return a.length == b.length && |
| 21 | a.indexed | 22 | a.indexed |
| 22 | - .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); | 23 | + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); |
| 23 | } | 24 | } |
| 24 | if (a is Map && b is Map) { | 25 | if (a is Map && b is Map) { |
| 25 | - return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) => | ||
| 26 | - (b as Map<Object?, Object?>).containsKey(entry.key) && | ||
| 27 | - _deepEquals(entry.value, b[entry.key])); | 26 | + return a.length == b.length && |
| 27 | + a.entries.every((MapEntry<Object?, Object?> entry) => | ||
| 28 | + (b as Map<Object?, Object?>).containsKey(entry.key) && | ||
| 29 | + _deepEquals(entry.value, b[entry.key])); | ||
| 28 | } | 30 | } |
| 29 | return a == b; | 31 | return a == b; |
| 30 | } | 32 | } |
| 31 | 33 | ||
| 32 | - | ||
| 33 | class HealthUploadResult { | 34 | class HealthUploadResult { |
| 34 | HealthUploadResult({ | 35 | HealthUploadResult({ |
| 35 | required this.commonUploadSuccess, | 36 | required this.commonUploadSuccess, |
| @@ -52,7 +53,8 @@ class HealthUploadResult { | @@ -52,7 +53,8 @@ class HealthUploadResult { | ||
| 52 | } | 53 | } |
| 53 | 54 | ||
| 54 | Object encode() { | 55 | Object encode() { |
| 55 | - return _toList(); } | 56 | + return _toList(); |
| 57 | + } | ||
| 56 | 58 | ||
| 57 | static HealthUploadResult decode(Object result) { | 59 | static HealthUploadResult decode(Object result) { |
| 58 | result as List<Object?>; | 60 | result as List<Object?>; |
| @@ -77,10 +79,158 @@ class HealthUploadResult { | @@ -77,10 +79,158 @@ class HealthUploadResult { | ||
| 77 | 79 | ||
| 78 | @override | 80 | @override |
| 79 | // ignore: avoid_equals_and_hash_code_on_mutable_classes | 81 | // ignore: avoid_equals_and_hash_code_on_mutable_classes |
| 80 | - int get hashCode => Object.hashAll(_toList()) | ||
| 81 | -; | 82 | + int get hashCode => Object.hashAll(_toList()); |
| 83 | +} | ||
| 84 | + | ||
| 85 | +class HealthUploadDataPoint { | ||
| 86 | + HealthUploadDataPoint({ | ||
| 87 | + required this.dataType, | ||
| 88 | + required this.time, | ||
| 89 | + required this.value, | ||
| 90 | + }); | ||
| 91 | + | ||
| 92 | + int dataType; | ||
| 93 | + | ||
| 94 | + int time; | ||
| 95 | + | ||
| 96 | + double value; | ||
| 97 | + | ||
| 98 | + List<Object?> _toList() { | ||
| 99 | + return <Object?>[ | ||
| 100 | + dataType, | ||
| 101 | + time, | ||
| 102 | + value, | ||
| 103 | + ]; | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + Object encode() { | ||
| 107 | + return _toList(); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + static HealthUploadDataPoint decode(Object result) { | ||
| 111 | + result as List<Object?>; | ||
| 112 | + return HealthUploadDataPoint( | ||
| 113 | + dataType: result[0]! as int, | ||
| 114 | + time: result[1]! as int, | ||
| 115 | + value: result[2]! as double, | ||
| 116 | + ); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @override | ||
| 120 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 121 | + bool operator ==(Object other) { | ||
| 122 | + if (other is! HealthUploadDataPoint || other.runtimeType != runtimeType) { | ||
| 123 | + return false; | ||
| 124 | + } | ||
| 125 | + if (identical(this, other)) { | ||
| 126 | + return true; | ||
| 127 | + } | ||
| 128 | + return _deepEquals(encode(), other.encode()); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + @override | ||
| 132 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 133 | + int get hashCode => Object.hashAll(_toList()); | ||
| 82 | } | 134 | } |
| 83 | 135 | ||
| 136 | +class HealthSleepUploadDataPoint { | ||
| 137 | + HealthSleepUploadDataPoint({ | ||
| 138 | + required this.dataType, | ||
| 139 | + required this.fromTime, | ||
| 140 | + required this.toTime, | ||
| 141 | + }); | ||
| 142 | + | ||
| 143 | + int dataType; | ||
| 144 | + | ||
| 145 | + int fromTime; | ||
| 146 | + | ||
| 147 | + int toTime; | ||
| 148 | + | ||
| 149 | + List<Object?> _toList() { | ||
| 150 | + return <Object?>[ | ||
| 151 | + dataType, | ||
| 152 | + fromTime, | ||
| 153 | + toTime, | ||
| 154 | + ]; | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + Object encode() { | ||
| 158 | + return _toList(); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + static HealthSleepUploadDataPoint decode(Object result) { | ||
| 162 | + result as List<Object?>; | ||
| 163 | + return HealthSleepUploadDataPoint( | ||
| 164 | + dataType: result[0]! as int, | ||
| 165 | + fromTime: result[1]! as int, | ||
| 166 | + toTime: result[2]! as int, | ||
| 167 | + ); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + @override | ||
| 171 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 172 | + bool operator ==(Object other) { | ||
| 173 | + if (other is! HealthSleepUploadDataPoint || | ||
| 174 | + other.runtimeType != runtimeType) { | ||
| 175 | + return false; | ||
| 176 | + } | ||
| 177 | + if (identical(this, other)) { | ||
| 178 | + return true; | ||
| 179 | + } | ||
| 180 | + return _deepEquals(encode(), other.encode()); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + @override | ||
| 184 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 185 | + int get hashCode => Object.hashAll(_toList()); | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +class HealthActivityTargetData { | ||
| 189 | + HealthActivityTargetData({ | ||
| 190 | + this.move, | ||
| 191 | + this.stand, | ||
| 192 | + }); | ||
| 193 | + | ||
| 194 | + int? move; | ||
| 195 | + | ||
| 196 | + int? stand; | ||
| 197 | + | ||
| 198 | + List<Object?> _toList() { | ||
| 199 | + return <Object?>[ | ||
| 200 | + move, | ||
| 201 | + stand, | ||
| 202 | + ]; | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + Object encode() { | ||
| 206 | + return _toList(); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + static HealthActivityTargetData decode(Object result) { | ||
| 210 | + result as List<Object?>; | ||
| 211 | + return HealthActivityTargetData( | ||
| 212 | + move: result[0] as int?, | ||
| 213 | + stand: result[1] as int?, | ||
| 214 | + ); | ||
| 215 | + } | ||
| 216 | + | ||
| 217 | + @override | ||
| 218 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 219 | + bool operator ==(Object other) { | ||
| 220 | + if (other is! HealthActivityTargetData || | ||
| 221 | + other.runtimeType != runtimeType) { | ||
| 222 | + return false; | ||
| 223 | + } | ||
| 224 | + if (identical(this, other)) { | ||
| 225 | + return true; | ||
| 226 | + } | ||
| 227 | + return _deepEquals(encode(), other.encode()); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + @override | ||
| 231 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 232 | + int get hashCode => Object.hashAll(_toList()); | ||
| 233 | +} | ||
| 84 | 234 | ||
| 85 | class _PigeonCodec extends StandardMessageCodec { | 235 | class _PigeonCodec extends StandardMessageCodec { |
| 86 | const _PigeonCodec(); | 236 | const _PigeonCodec(); |
| @@ -89,9 +239,18 @@ class _PigeonCodec extends StandardMessageCodec { | @@ -89,9 +239,18 @@ class _PigeonCodec extends StandardMessageCodec { | ||
| 89 | if (value is int) { | 239 | if (value is int) { |
| 90 | buffer.putUint8(4); | 240 | buffer.putUint8(4); |
| 91 | buffer.putInt64(value); | 241 | buffer.putInt64(value); |
| 92 | - } else if (value is HealthUploadResult) { | 242 | + } else if (value is HealthUploadResult) { |
| 93 | buffer.putUint8(129); | 243 | buffer.putUint8(129); |
| 94 | writeValue(buffer, value.encode()); | 244 | writeValue(buffer, value.encode()); |
| 245 | + } else if (value is HealthUploadDataPoint) { | ||
| 246 | + buffer.putUint8(130); | ||
| 247 | + writeValue(buffer, value.encode()); | ||
| 248 | + } else if (value is HealthSleepUploadDataPoint) { | ||
| 249 | + buffer.putUint8(131); | ||
| 250 | + writeValue(buffer, value.encode()); | ||
| 251 | + } else if (value is HealthActivityTargetData) { | ||
| 252 | + buffer.putUint8(132); | ||
| 253 | + writeValue(buffer, value.encode()); | ||
| 95 | } else { | 254 | } else { |
| 96 | super.writeValue(buffer, value); | 255 | super.writeValue(buffer, value); |
| 97 | } | 256 | } |
| @@ -100,8 +259,14 @@ class _PigeonCodec extends StandardMessageCodec { | @@ -100,8 +259,14 @@ class _PigeonCodec extends StandardMessageCodec { | ||
| 100 | @override | 259 | @override |
| 101 | Object? readValueOfType(int type, ReadBuffer buffer) { | 260 | Object? readValueOfType(int type, ReadBuffer buffer) { |
| 102 | switch (type) { | 261 | switch (type) { |
| 103 | - case 129: | 262 | + case 129: |
| 104 | return HealthUploadResult.decode(readValue(buffer)!); | 263 | return HealthUploadResult.decode(readValue(buffer)!); |
| 264 | + case 130: | ||
| 265 | + return HealthUploadDataPoint.decode(readValue(buffer)!); | ||
| 266 | + case 131: | ||
| 267 | + return HealthSleepUploadDataPoint.decode(readValue(buffer)!); | ||
| 268 | + case 132: | ||
| 269 | + return HealthActivityTargetData.decode(readValue(buffer)!); | ||
| 105 | default: | 270 | default: |
| 106 | return super.readValueOfType(type, buffer); | 271 | return super.readValueOfType(type, buffer); |
| 107 | } | 272 | } |
| @@ -112,9 +277,11 @@ class HealthKitHostApi { | @@ -112,9 +277,11 @@ class HealthKitHostApi { | ||
| 112 | /// Constructor for [HealthKitHostApi]. The [binaryMessenger] named argument is | 277 | /// Constructor for [HealthKitHostApi]. The [binaryMessenger] named argument is |
| 113 | /// available for dependency injection. If it is left null, the default | 278 | /// available for dependency injection. If it is left null, the default |
| 114 | /// BinaryMessenger will be used which routes to the host platform. | 279 | /// BinaryMessenger will be used which routes to the host platform. |
| 115 | - HealthKitHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) | 280 | + HealthKitHostApi( |
| 281 | + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) | ||
| 116 | : pigeonVar_binaryMessenger = binaryMessenger, | 282 | : pigeonVar_binaryMessenger = binaryMessenger, |
| 117 | - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; | 283 | + pigeonVar_messageChannelSuffix = |
| 284 | + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; | ||
| 118 | final BinaryMessenger? pigeonVar_binaryMessenger; | 285 | final BinaryMessenger? pigeonVar_binaryMessenger; |
| 119 | 286 | ||
| 120 | static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); | 287 | static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); |
| @@ -122,8 +289,10 @@ class HealthKitHostApi { | @@ -122,8 +289,10 @@ class HealthKitHostApi { | ||
| 122 | final String pigeonVar_messageChannelSuffix; | 289 | final String pigeonVar_messageChannelSuffix; |
| 123 | 290 | ||
| 124 | Future<bool> checkHealthAppAuthorization() async { | 291 | Future<bool> checkHealthAppAuthorization() async { |
| 125 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization$pigeonVar_messageChannelSuffix'; | ||
| 126 | - final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 292 | + final String pigeonVar_channelName = |
| 293 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization$pigeonVar_messageChannelSuffix'; | ||
| 294 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 295 | + BasicMessageChannel<Object?>( | ||
| 127 | pigeonVar_channelName, | 296 | pigeonVar_channelName, |
| 128 | pigeonChannelCodec, | 297 | pigeonChannelCodec, |
| 129 | binaryMessenger: pigeonVar_binaryMessenger, | 298 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -150,8 +319,10 @@ class HealthKitHostApi { | @@ -150,8 +319,10 @@ class HealthKitHostApi { | ||
| 150 | } | 319 | } |
| 151 | 320 | ||
| 152 | Future<String> getHealthServerAuthUrl() async { | 321 | Future<String> getHealthServerAuthUrl() async { |
| 153 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.getHealthServerAuthUrl$pigeonVar_messageChannelSuffix'; | ||
| 154 | - final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 322 | + final String pigeonVar_channelName = |
| 323 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.getHealthServerAuthUrl$pigeonVar_messageChannelSuffix'; | ||
| 324 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 325 | + BasicMessageChannel<Object?>( | ||
| 155 | pigeonVar_channelName, | 326 | pigeonVar_channelName, |
| 156 | pigeonChannelCodec, | 327 | pigeonChannelCodec, |
| 157 | binaryMessenger: pigeonVar_binaryMessenger, | 328 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -179,8 +350,10 @@ class HealthKitHostApi { | @@ -179,8 +350,10 @@ class HealthKitHostApi { | ||
| 179 | 350 | ||
| 180 | /// Opens Huawei Health client authorization UI. Returns whether user granted. | 351 | /// Opens Huawei Health client authorization UI. Returns whether user granted. |
| 181 | Future<bool> requestHealthClientAuthorization() async { | 352 | Future<bool> requestHealthClientAuthorization() async { |
| 182 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.requestHealthClientAuthorization$pigeonVar_messageChannelSuffix'; | ||
| 183 | - final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 353 | + final String pigeonVar_channelName = |
| 354 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.requestHealthClientAuthorization$pigeonVar_messageChannelSuffix'; | ||
| 355 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 356 | + BasicMessageChannel<Object?>( | ||
| 184 | pigeonVar_channelName, | 357 | pigeonVar_channelName, |
| 185 | pigeonChannelCodec, | 358 | pigeonChannelCodec, |
| 186 | binaryMessenger: pigeonVar_binaryMessenger, | 359 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -207,8 +380,10 @@ class HealthKitHostApi { | @@ -207,8 +380,10 @@ class HealthKitHostApi { | ||
| 207 | } | 380 | } |
| 208 | 381 | ||
| 209 | Future<bool> cancelHealthAppAuthorization() async { | 382 | Future<bool> cancelHealthAppAuthorization() async { |
| 210 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.cancelHealthAppAuthorization$pigeonVar_messageChannelSuffix'; | ||
| 211 | - final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 383 | + final String pigeonVar_channelName = |
| 384 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.cancelHealthAppAuthorization$pigeonVar_messageChannelSuffix'; | ||
| 385 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 386 | + BasicMessageChannel<Object?>( | ||
| 212 | pigeonVar_channelName, | 387 | pigeonVar_channelName, |
| 213 | pigeonChannelCodec, | 388 | pigeonChannelCodec, |
| 214 | binaryMessenger: pigeonVar_binaryMessenger, | 389 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -236,8 +411,10 @@ class HealthKitHostApi { | @@ -236,8 +411,10 @@ class HealthKitHostApi { | ||
| 236 | 411 | ||
| 237 | /// Runs native health read and server upload pipeline. | 412 | /// Runs native health read and server upload pipeline. |
| 238 | Future<HealthUploadResult> performHealthUpload() async { | 413 | Future<HealthUploadResult> performHealthUpload() async { |
| 239 | - final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.performHealthUpload$pigeonVar_messageChannelSuffix'; | ||
| 240 | - final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | 414 | + final String pigeonVar_channelName = |
| 415 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.performHealthUpload$pigeonVar_messageChannelSuffix'; | ||
| 416 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 417 | + BasicMessageChannel<Object?>( | ||
| 241 | pigeonVar_channelName, | 418 | pigeonVar_channelName, |
| 242 | pigeonChannelCodec, | 419 | pigeonChannelCodec, |
| 243 | binaryMessenger: pigeonVar_binaryMessenger, | 420 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -262,4 +439,493 @@ class HealthKitHostApi { | @@ -262,4 +439,493 @@ class HealthKitHostApi { | ||
| 262 | return (pigeonVar_replyList[0] as HealthUploadResult?)!; | 439 | return (pigeonVar_replyList[0] as HealthUploadResult?)!; |
| 263 | } | 440 | } |
| 264 | } | 441 | } |
| 442 | + | ||
| 443 | + Future<List<HealthUploadDataPoint>> fetchHrvData( | ||
| 444 | + int startTime, int endTime) async { | ||
| 445 | + final String pigeonVar_channelName = | ||
| 446 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHrvData$pigeonVar_messageChannelSuffix'; | ||
| 447 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 448 | + BasicMessageChannel<Object?>( | ||
| 449 | + pigeonVar_channelName, | ||
| 450 | + pigeonChannelCodec, | ||
| 451 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 452 | + ); | ||
| 453 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 454 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 455 | + final List<Object?>? pigeonVar_replyList = | ||
| 456 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 457 | + if (pigeonVar_replyList == null) { | ||
| 458 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 459 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 460 | + throw PlatformException( | ||
| 461 | + code: pigeonVar_replyList[0]! as String, | ||
| 462 | + message: pigeonVar_replyList[1] as String?, | ||
| 463 | + details: pigeonVar_replyList[2], | ||
| 464 | + ); | ||
| 465 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 466 | + throw PlatformException( | ||
| 467 | + code: 'null-error', | ||
| 468 | + message: 'Host platform returned null value for non-null return value.', | ||
| 469 | + ); | ||
| 470 | + } else { | ||
| 471 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 472 | + .cast<HealthUploadDataPoint>(); | ||
| 473 | + } | ||
| 474 | + } | ||
| 475 | + | ||
| 476 | + Future<List<HealthUploadDataPoint>> fetchHeartRateData( | ||
| 477 | + int startTime, int endTime) async { | ||
| 478 | + final String pigeonVar_channelName = | ||
| 479 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHeartRateData$pigeonVar_messageChannelSuffix'; | ||
| 480 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 481 | + BasicMessageChannel<Object?>( | ||
| 482 | + pigeonVar_channelName, | ||
| 483 | + pigeonChannelCodec, | ||
| 484 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 485 | + ); | ||
| 486 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 487 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 488 | + final List<Object?>? pigeonVar_replyList = | ||
| 489 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 490 | + if (pigeonVar_replyList == null) { | ||
| 491 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 492 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 493 | + throw PlatformException( | ||
| 494 | + code: pigeonVar_replyList[0]! as String, | ||
| 495 | + message: pigeonVar_replyList[1] as String?, | ||
| 496 | + details: pigeonVar_replyList[2], | ||
| 497 | + ); | ||
| 498 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 499 | + throw PlatformException( | ||
| 500 | + code: 'null-error', | ||
| 501 | + message: 'Host platform returned null value for non-null return value.', | ||
| 502 | + ); | ||
| 503 | + } else { | ||
| 504 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 505 | + .cast<HealthUploadDataPoint>(); | ||
| 506 | + } | ||
| 507 | + } | ||
| 508 | + | ||
| 509 | + Future<List<HealthUploadDataPoint>> fetchWalkingHeartRateData( | ||
| 510 | + int startTime, int endTime) async { | ||
| 511 | + final String pigeonVar_channelName = | ||
| 512 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchWalkingHeartRateData$pigeonVar_messageChannelSuffix'; | ||
| 513 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 514 | + BasicMessageChannel<Object?>( | ||
| 515 | + pigeonVar_channelName, | ||
| 516 | + pigeonChannelCodec, | ||
| 517 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 518 | + ); | ||
| 519 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 520 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 521 | + final List<Object?>? pigeonVar_replyList = | ||
| 522 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 523 | + if (pigeonVar_replyList == null) { | ||
| 524 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 525 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 526 | + throw PlatformException( | ||
| 527 | + code: pigeonVar_replyList[0]! as String, | ||
| 528 | + message: pigeonVar_replyList[1] as String?, | ||
| 529 | + details: pigeonVar_replyList[2], | ||
| 530 | + ); | ||
| 531 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 532 | + throw PlatformException( | ||
| 533 | + code: 'null-error', | ||
| 534 | + message: 'Host platform returned null value for non-null return value.', | ||
| 535 | + ); | ||
| 536 | + } else { | ||
| 537 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 538 | + .cast<HealthUploadDataPoint>(); | ||
| 539 | + } | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + Future<List<HealthUploadDataPoint>> fetchRestingHeartRateData( | ||
| 543 | + int startTime, int endTime) async { | ||
| 544 | + final String pigeonVar_channelName = | ||
| 545 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRestingHeartRateData$pigeonVar_messageChannelSuffix'; | ||
| 546 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 547 | + BasicMessageChannel<Object?>( | ||
| 548 | + pigeonVar_channelName, | ||
| 549 | + pigeonChannelCodec, | ||
| 550 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 551 | + ); | ||
| 552 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 553 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 554 | + final List<Object?>? pigeonVar_replyList = | ||
| 555 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 556 | + if (pigeonVar_replyList == null) { | ||
| 557 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 558 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 559 | + throw PlatformException( | ||
| 560 | + code: pigeonVar_replyList[0]! as String, | ||
| 561 | + message: pigeonVar_replyList[1] as String?, | ||
| 562 | + details: pigeonVar_replyList[2], | ||
| 563 | + ); | ||
| 564 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 565 | + throw PlatformException( | ||
| 566 | + code: 'null-error', | ||
| 567 | + message: 'Host platform returned null value for non-null return value.', | ||
| 568 | + ); | ||
| 569 | + } else { | ||
| 570 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 571 | + .cast<HealthUploadDataPoint>(); | ||
| 572 | + } | ||
| 573 | + } | ||
| 574 | + | ||
| 575 | + Future<List<HealthUploadDataPoint>> fetchSleepingHeartRateData( | ||
| 576 | + int startTime, int endTime) async { | ||
| 577 | + final String pigeonVar_channelName = | ||
| 578 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingHeartRateData$pigeonVar_messageChannelSuffix'; | ||
| 579 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 580 | + BasicMessageChannel<Object?>( | ||
| 581 | + pigeonVar_channelName, | ||
| 582 | + pigeonChannelCodec, | ||
| 583 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 584 | + ); | ||
| 585 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 586 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 587 | + final List<Object?>? pigeonVar_replyList = | ||
| 588 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 589 | + if (pigeonVar_replyList == null) { | ||
| 590 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 591 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 592 | + throw PlatformException( | ||
| 593 | + code: pigeonVar_replyList[0]! as String, | ||
| 594 | + message: pigeonVar_replyList[1] as String?, | ||
| 595 | + details: pigeonVar_replyList[2], | ||
| 596 | + ); | ||
| 597 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 598 | + throw PlatformException( | ||
| 599 | + code: 'null-error', | ||
| 600 | + message: 'Host platform returned null value for non-null return value.', | ||
| 601 | + ); | ||
| 602 | + } else { | ||
| 603 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 604 | + .cast<HealthUploadDataPoint>(); | ||
| 605 | + } | ||
| 606 | + } | ||
| 607 | + | ||
| 608 | + Future<List<HealthUploadDataPoint>> fetchOxygenSaturationData( | ||
| 609 | + int startTime, int endTime) async { | ||
| 610 | + final String pigeonVar_channelName = | ||
| 611 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchOxygenSaturationData$pigeonVar_messageChannelSuffix'; | ||
| 612 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 613 | + BasicMessageChannel<Object?>( | ||
| 614 | + pigeonVar_channelName, | ||
| 615 | + pigeonChannelCodec, | ||
| 616 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 617 | + ); | ||
| 618 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 619 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 620 | + final List<Object?>? pigeonVar_replyList = | ||
| 621 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 622 | + if (pigeonVar_replyList == null) { | ||
| 623 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 624 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 625 | + throw PlatformException( | ||
| 626 | + code: pigeonVar_replyList[0]! as String, | ||
| 627 | + message: pigeonVar_replyList[1] as String?, | ||
| 628 | + details: pigeonVar_replyList[2], | ||
| 629 | + ); | ||
| 630 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 631 | + throw PlatformException( | ||
| 632 | + code: 'null-error', | ||
| 633 | + message: 'Host platform returned null value for non-null return value.', | ||
| 634 | + ); | ||
| 635 | + } else { | ||
| 636 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 637 | + .cast<HealthUploadDataPoint>(); | ||
| 638 | + } | ||
| 639 | + } | ||
| 640 | + | ||
| 641 | + Future<List<HealthUploadDataPoint>> fetchActiveEnergyData( | ||
| 642 | + int startTime, int endTime) async { | ||
| 643 | + final String pigeonVar_channelName = | ||
| 644 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActiveEnergyData$pigeonVar_messageChannelSuffix'; | ||
| 645 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 646 | + BasicMessageChannel<Object?>( | ||
| 647 | + pigeonVar_channelName, | ||
| 648 | + pigeonChannelCodec, | ||
| 649 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 650 | + ); | ||
| 651 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 652 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 653 | + final List<Object?>? pigeonVar_replyList = | ||
| 654 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 655 | + if (pigeonVar_replyList == null) { | ||
| 656 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 657 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 658 | + throw PlatformException( | ||
| 659 | + code: pigeonVar_replyList[0]! as String, | ||
| 660 | + message: pigeonVar_replyList[1] as String?, | ||
| 661 | + details: pigeonVar_replyList[2], | ||
| 662 | + ); | ||
| 663 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 664 | + throw PlatformException( | ||
| 665 | + code: 'null-error', | ||
| 666 | + message: 'Host platform returned null value for non-null return value.', | ||
| 667 | + ); | ||
| 668 | + } else { | ||
| 669 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 670 | + .cast<HealthUploadDataPoint>(); | ||
| 671 | + } | ||
| 672 | + } | ||
| 673 | + | ||
| 674 | + Future<List<HealthUploadDataPoint>> fetchExerciseData( | ||
| 675 | + int startTime, int endTime) async { | ||
| 676 | + final String pigeonVar_channelName = | ||
| 677 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchExerciseData$pigeonVar_messageChannelSuffix'; | ||
| 678 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 679 | + BasicMessageChannel<Object?>( | ||
| 680 | + pigeonVar_channelName, | ||
| 681 | + pigeonChannelCodec, | ||
| 682 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 683 | + ); | ||
| 684 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 685 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 686 | + final List<Object?>? pigeonVar_replyList = | ||
| 687 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 688 | + if (pigeonVar_replyList == null) { | ||
| 689 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 690 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 691 | + throw PlatformException( | ||
| 692 | + code: pigeonVar_replyList[0]! as String, | ||
| 693 | + message: pigeonVar_replyList[1] as String?, | ||
| 694 | + details: pigeonVar_replyList[2], | ||
| 695 | + ); | ||
| 696 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 697 | + throw PlatformException( | ||
| 698 | + code: 'null-error', | ||
| 699 | + message: 'Host platform returned null value for non-null return value.', | ||
| 700 | + ); | ||
| 701 | + } else { | ||
| 702 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 703 | + .cast<HealthUploadDataPoint>(); | ||
| 704 | + } | ||
| 705 | + } | ||
| 706 | + | ||
| 707 | + Future<List<HealthUploadDataPoint>> fetchStandData( | ||
| 708 | + int startTime, int endTime) async { | ||
| 709 | + final String pigeonVar_channelName = | ||
| 710 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStandData$pigeonVar_messageChannelSuffix'; | ||
| 711 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 712 | + BasicMessageChannel<Object?>( | ||
| 713 | + pigeonVar_channelName, | ||
| 714 | + pigeonChannelCodec, | ||
| 715 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 716 | + ); | ||
| 717 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 718 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 719 | + final List<Object?>? pigeonVar_replyList = | ||
| 720 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 721 | + if (pigeonVar_replyList == null) { | ||
| 722 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 723 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 724 | + throw PlatformException( | ||
| 725 | + code: pigeonVar_replyList[0]! as String, | ||
| 726 | + message: pigeonVar_replyList[1] as String?, | ||
| 727 | + details: pigeonVar_replyList[2], | ||
| 728 | + ); | ||
| 729 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 730 | + throw PlatformException( | ||
| 731 | + code: 'null-error', | ||
| 732 | + message: 'Host platform returned null value for non-null return value.', | ||
| 733 | + ); | ||
| 734 | + } else { | ||
| 735 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 736 | + .cast<HealthUploadDataPoint>(); | ||
| 737 | + } | ||
| 738 | + } | ||
| 739 | + | ||
| 740 | + Future<List<HealthUploadDataPoint>> fetchStepCountData( | ||
| 741 | + int startTime, int endTime) async { | ||
| 742 | + final String pigeonVar_channelName = | ||
| 743 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStepCountData$pigeonVar_messageChannelSuffix'; | ||
| 744 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 745 | + BasicMessageChannel<Object?>( | ||
| 746 | + pigeonVar_channelName, | ||
| 747 | + pigeonChannelCodec, | ||
| 748 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 749 | + ); | ||
| 750 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 751 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 752 | + final List<Object?>? pigeonVar_replyList = | ||
| 753 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 754 | + if (pigeonVar_replyList == null) { | ||
| 755 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 756 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 757 | + throw PlatformException( | ||
| 758 | + code: pigeonVar_replyList[0]! as String, | ||
| 759 | + message: pigeonVar_replyList[1] as String?, | ||
| 760 | + details: pigeonVar_replyList[2], | ||
| 761 | + ); | ||
| 762 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 763 | + throw PlatformException( | ||
| 764 | + code: 'null-error', | ||
| 765 | + message: 'Host platform returned null value for non-null return value.', | ||
| 766 | + ); | ||
| 767 | + } else { | ||
| 768 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 769 | + .cast<HealthUploadDataPoint>(); | ||
| 770 | + } | ||
| 771 | + } | ||
| 772 | + | ||
| 773 | + Future<List<HealthSleepUploadDataPoint>> fetchSleepData( | ||
| 774 | + int startTime, int endTime) async { | ||
| 775 | + final String pigeonVar_channelName = | ||
| 776 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepData$pigeonVar_messageChannelSuffix'; | ||
| 777 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 778 | + BasicMessageChannel<Object?>( | ||
| 779 | + pigeonVar_channelName, | ||
| 780 | + pigeonChannelCodec, | ||
| 781 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 782 | + ); | ||
| 783 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 784 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 785 | + final List<Object?>? pigeonVar_replyList = | ||
| 786 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 787 | + if (pigeonVar_replyList == null) { | ||
| 788 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 789 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 790 | + throw PlatformException( | ||
| 791 | + code: pigeonVar_replyList[0]! as String, | ||
| 792 | + message: pigeonVar_replyList[1] as String?, | ||
| 793 | + details: pigeonVar_replyList[2], | ||
| 794 | + ); | ||
| 795 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 796 | + throw PlatformException( | ||
| 797 | + code: 'null-error', | ||
| 798 | + message: 'Host platform returned null value for non-null return value.', | ||
| 799 | + ); | ||
| 800 | + } else { | ||
| 801 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 802 | + .cast<HealthSleepUploadDataPoint>(); | ||
| 803 | + } | ||
| 804 | + } | ||
| 805 | + | ||
| 806 | + Future<List<HealthUploadDataPoint>> fetchSleepingWristTemperatureData( | ||
| 807 | + int startTime, int endTime) async { | ||
| 808 | + final String pigeonVar_channelName = | ||
| 809 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingWristTemperatureData$pigeonVar_messageChannelSuffix'; | ||
| 810 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 811 | + BasicMessageChannel<Object?>( | ||
| 812 | + pigeonVar_channelName, | ||
| 813 | + pigeonChannelCodec, | ||
| 814 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 815 | + ); | ||
| 816 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 817 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 818 | + final List<Object?>? pigeonVar_replyList = | ||
| 819 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 820 | + if (pigeonVar_replyList == null) { | ||
| 821 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 822 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 823 | + throw PlatformException( | ||
| 824 | + code: pigeonVar_replyList[0]! as String, | ||
| 825 | + message: pigeonVar_replyList[1] as String?, | ||
| 826 | + details: pigeonVar_replyList[2], | ||
| 827 | + ); | ||
| 828 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 829 | + throw PlatformException( | ||
| 830 | + code: 'null-error', | ||
| 831 | + message: 'Host platform returned null value for non-null return value.', | ||
| 832 | + ); | ||
| 833 | + } else { | ||
| 834 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 835 | + .cast<HealthUploadDataPoint>(); | ||
| 836 | + } | ||
| 837 | + } | ||
| 838 | + | ||
| 839 | + Future<List<HealthUploadDataPoint>> fetchRespiratoryRateData( | ||
| 840 | + int startTime, int endTime) async { | ||
| 841 | + final String pigeonVar_channelName = | ||
| 842 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRespiratoryRateData$pigeonVar_messageChannelSuffix'; | ||
| 843 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 844 | + BasicMessageChannel<Object?>( | ||
| 845 | + pigeonVar_channelName, | ||
| 846 | + pigeonChannelCodec, | ||
| 847 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 848 | + ); | ||
| 849 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 850 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 851 | + final List<Object?>? pigeonVar_replyList = | ||
| 852 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 853 | + if (pigeonVar_replyList == null) { | ||
| 854 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 855 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 856 | + throw PlatformException( | ||
| 857 | + code: pigeonVar_replyList[0]! as String, | ||
| 858 | + message: pigeonVar_replyList[1] as String?, | ||
| 859 | + details: pigeonVar_replyList[2], | ||
| 860 | + ); | ||
| 861 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 862 | + throw PlatformException( | ||
| 863 | + code: 'null-error', | ||
| 864 | + message: 'Host platform returned null value for non-null return value.', | ||
| 865 | + ); | ||
| 866 | + } else { | ||
| 867 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 868 | + .cast<HealthUploadDataPoint>(); | ||
| 869 | + } | ||
| 870 | + } | ||
| 871 | + | ||
| 872 | + Future<List<HealthUploadDataPoint>> fetchIrregularHeartRhythmData( | ||
| 873 | + int startTime, int endTime) async { | ||
| 874 | + final String pigeonVar_channelName = | ||
| 875 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchIrregularHeartRhythmData$pigeonVar_messageChannelSuffix'; | ||
| 876 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 877 | + BasicMessageChannel<Object?>( | ||
| 878 | + pigeonVar_channelName, | ||
| 879 | + pigeonChannelCodec, | ||
| 880 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 881 | + ); | ||
| 882 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 883 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 884 | + final List<Object?>? pigeonVar_replyList = | ||
| 885 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 886 | + if (pigeonVar_replyList == null) { | ||
| 887 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 888 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 889 | + throw PlatformException( | ||
| 890 | + code: pigeonVar_replyList[0]! as String, | ||
| 891 | + message: pigeonVar_replyList[1] as String?, | ||
| 892 | + details: pigeonVar_replyList[2], | ||
| 893 | + ); | ||
| 894 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 895 | + throw PlatformException( | ||
| 896 | + code: 'null-error', | ||
| 897 | + message: 'Host platform returned null value for non-null return value.', | ||
| 898 | + ); | ||
| 899 | + } else { | ||
| 900 | + return (pigeonVar_replyList[0] as List<Object?>?)! | ||
| 901 | + .cast<HealthUploadDataPoint>(); | ||
| 902 | + } | ||
| 903 | + } | ||
| 904 | + | ||
| 905 | + Future<HealthActivityTargetData?> fetchActivityTargetData( | ||
| 906 | + int startTime, int endTime) async { | ||
| 907 | + final String pigeonVar_channelName = | ||
| 908 | + 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActivityTargetData$pigeonVar_messageChannelSuffix'; | ||
| 909 | + final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 910 | + BasicMessageChannel<Object?>( | ||
| 911 | + pigeonVar_channelName, | ||
| 912 | + pigeonChannelCodec, | ||
| 913 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 914 | + ); | ||
| 915 | + final Future<Object?> pigeonVar_sendFuture = | ||
| 916 | + pigeonVar_channel.send(<Object?>[startTime, endTime]); | ||
| 917 | + final List<Object?>? pigeonVar_replyList = | ||
| 918 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 919 | + if (pigeonVar_replyList == null) { | ||
| 920 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 921 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 922 | + throw PlatformException( | ||
| 923 | + code: pigeonVar_replyList[0]! as String, | ||
| 924 | + message: pigeonVar_replyList[1] as String?, | ||
| 925 | + details: pigeonVar_replyList[2], | ||
| 926 | + ); | ||
| 927 | + } else { | ||
| 928 | + return (pigeonVar_replyList[0] as HealthActivityTargetData?); | ||
| 929 | + } | ||
| 930 | + } | ||
| 265 | } | 931 | } |
| @@ -15,22 +15,21 @@ PlatformException _createConnectionError(String channelName) { | @@ -15,22 +15,21 @@ PlatformException _createConnectionError(String channelName) { | ||
| 15 | message: 'Unable to establish connection on channel: "$channelName".', | 15 | message: 'Unable to establish connection on channel: "$channelName".', |
| 16 | ); | 16 | ); |
| 17 | } | 17 | } |
| 18 | - | ||
| 19 | bool _deepEquals(Object? a, Object? b) { | 18 | bool _deepEquals(Object? a, Object? b) { |
| 20 | if (a is List && b is List) { | 19 | if (a is List && b is List) { |
| 21 | return a.length == b.length && | 20 | return a.length == b.length && |
| 22 | a.indexed | 21 | a.indexed |
| 23 | - .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); | 22 | + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); |
| 24 | } | 23 | } |
| 25 | if (a is Map && b is Map) { | 24 | if (a is Map && b is Map) { |
| 26 | - return a.length == b.length && | ||
| 27 | - a.entries.every((MapEntry<Object?, Object?> entry) => | ||
| 28 | - (b as Map<Object?, Object?>).containsKey(entry.key) && | ||
| 29 | - _deepEquals(entry.value, b[entry.key])); | 25 | + return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) => |
| 26 | + (b as Map<Object?, Object?>).containsKey(entry.key) && | ||
| 27 | + _deepEquals(entry.value, b[entry.key])); | ||
| 30 | } | 28 | } |
| 31 | return a == b; | 29 | return a == b; |
| 32 | } | 30 | } |
| 33 | 31 | ||
| 32 | + | ||
| 34 | class WearDeviceInfo { | 33 | class WearDeviceInfo { |
| 35 | WearDeviceInfo({ | 34 | WearDeviceInfo({ |
| 36 | this.deviceId, | 35 | this.deviceId, |
| @@ -53,8 +52,7 @@ class WearDeviceInfo { | @@ -53,8 +52,7 @@ class WearDeviceInfo { | ||
| 53 | } | 52 | } |
| 54 | 53 | ||
| 55 | Object encode() { | 54 | Object encode() { |
| 56 | - return _toList(); | ||
| 57 | - } | 55 | + return _toList(); } |
| 58 | 56 | ||
| 59 | static WearDeviceInfo decode(Object result) { | 57 | static WearDeviceInfo decode(Object result) { |
| 60 | result as List<Object?>; | 58 | result as List<Object?>; |
| @@ -79,9 +77,11 @@ class WearDeviceInfo { | @@ -79,9 +77,11 @@ class WearDeviceInfo { | ||
| 79 | 77 | ||
| 80 | @override | 78 | @override |
| 81 | // ignore: avoid_equals_and_hash_code_on_mutable_classes | 79 | // ignore: avoid_equals_and_hash_code_on_mutable_classes |
| 82 | - int get hashCode => Object.hashAll(_toList()); | 80 | + int get hashCode => Object.hashAll(_toList()) |
| 81 | +; | ||
| 83 | } | 82 | } |
| 84 | 83 | ||
| 84 | + | ||
| 85 | class _PigeonCodec extends StandardMessageCodec { | 85 | class _PigeonCodec extends StandardMessageCodec { |
| 86 | const _PigeonCodec(); | 86 | const _PigeonCodec(); |
| 87 | @override | 87 | @override |
| @@ -89,7 +89,7 @@ class _PigeonCodec extends StandardMessageCodec { | @@ -89,7 +89,7 @@ class _PigeonCodec extends StandardMessageCodec { | ||
| 89 | if (value is int) { | 89 | if (value is int) { |
| 90 | buffer.putUint8(4); | 90 | buffer.putUint8(4); |
| 91 | buffer.putInt64(value); | 91 | buffer.putInt64(value); |
| 92 | - } else if (value is WearDeviceInfo) { | 92 | + } else if (value is WearDeviceInfo) { |
| 93 | buffer.putUint8(129); | 93 | buffer.putUint8(129); |
| 94 | writeValue(buffer, value.encode()); | 94 | writeValue(buffer, value.encode()); |
| 95 | } else { | 95 | } else { |
| @@ -100,7 +100,7 @@ class _PigeonCodec extends StandardMessageCodec { | @@ -100,7 +100,7 @@ class _PigeonCodec extends StandardMessageCodec { | ||
| 100 | @override | 100 | @override |
| 101 | Object? readValueOfType(int type, ReadBuffer buffer) { | 101 | Object? readValueOfType(int type, ReadBuffer buffer) { |
| 102 | switch (type) { | 102 | switch (type) { |
| 103 | - case 129: | 103 | + case 129: |
| 104 | return WearDeviceInfo.decode(readValue(buffer)!); | 104 | return WearDeviceInfo.decode(readValue(buffer)!); |
| 105 | default: | 105 | default: |
| 106 | return super.readValueOfType(type, buffer); | 106 | return super.readValueOfType(type, buffer); |
| @@ -112,11 +112,9 @@ class WearEngineHostApi { | @@ -112,11 +112,9 @@ class WearEngineHostApi { | ||
| 112 | /// Constructor for [WearEngineHostApi]. The [binaryMessenger] named argument is | 112 | /// Constructor for [WearEngineHostApi]. The [binaryMessenger] named argument is |
| 113 | /// available for dependency injection. If it is left null, the default | 113 | /// available for dependency injection. If it is left null, the default |
| 114 | /// BinaryMessenger will be used which routes to the host platform. | 114 | /// BinaryMessenger will be used which routes to the host platform. |
| 115 | - WearEngineHostApi( | ||
| 116 | - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) | 115 | + WearEngineHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) |
| 117 | : pigeonVar_binaryMessenger = binaryMessenger, | 116 | : pigeonVar_binaryMessenger = binaryMessenger, |
| 118 | - pigeonVar_messageChannelSuffix = | ||
| 119 | - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; | 117 | + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; |
| 120 | final BinaryMessenger? pigeonVar_binaryMessenger; | 118 | final BinaryMessenger? pigeonVar_binaryMessenger; |
| 121 | 119 | ||
| 122 | static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); | 120 | static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); |
| @@ -124,10 +122,8 @@ class WearEngineHostApi { | @@ -124,10 +122,8 @@ class WearEngineHostApi { | ||
| 124 | final String pigeonVar_messageChannelSuffix; | 122 | final String pigeonVar_messageChannelSuffix; |
| 125 | 123 | ||
| 126 | Future<bool> hasAvailableDevices() async { | 124 | Future<bool> hasAvailableDevices() async { |
| 127 | - final String pigeonVar_channelName = | ||
| 128 | - 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.hasAvailableDevices$pigeonVar_messageChannelSuffix'; | ||
| 129 | - final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 130 | - BasicMessageChannel<Object?>( | 125 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.hasAvailableDevices$pigeonVar_messageChannelSuffix'; |
| 126 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 131 | pigeonVar_channelName, | 127 | pigeonVar_channelName, |
| 132 | pigeonChannelCodec, | 128 | pigeonChannelCodec, |
| 133 | binaryMessenger: pigeonVar_binaryMessenger, | 129 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -154,10 +150,8 @@ class WearEngineHostApi { | @@ -154,10 +150,8 @@ class WearEngineHostApi { | ||
| 154 | } | 150 | } |
| 155 | 151 | ||
| 156 | Future<WearDeviceInfo?> checkConnectedDevice() async { | 152 | Future<WearDeviceInfo?> checkConnectedDevice() async { |
| 157 | - final String pigeonVar_channelName = | ||
| 158 | - 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.checkConnectedDevice$pigeonVar_messageChannelSuffix'; | ||
| 159 | - final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 160 | - BasicMessageChannel<Object?>( | 153 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.checkConnectedDevice$pigeonVar_messageChannelSuffix'; |
| 154 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 161 | pigeonVar_channelName, | 155 | pigeonVar_channelName, |
| 162 | pigeonChannelCodec, | 156 | pigeonChannelCodec, |
| 163 | binaryMessenger: pigeonVar_binaryMessenger, | 157 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -179,10 +173,8 @@ class WearEngineHostApi { | @@ -179,10 +173,8 @@ class WearEngineHostApi { | ||
| 179 | } | 173 | } |
| 180 | 174 | ||
| 181 | Future<bool> registerMessageReceiver() async { | 175 | Future<bool> registerMessageReceiver() async { |
| 182 | - final String pigeonVar_channelName = | ||
| 183 | - 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.registerMessageReceiver$pigeonVar_messageChannelSuffix'; | ||
| 184 | - final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 185 | - BasicMessageChannel<Object?>( | 176 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.registerMessageReceiver$pigeonVar_messageChannelSuffix'; |
| 177 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 186 | pigeonVar_channelName, | 178 | pigeonVar_channelName, |
| 187 | pigeonChannelCodec, | 179 | pigeonChannelCodec, |
| 188 | binaryMessenger: pigeonVar_binaryMessenger, | 180 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -209,16 +201,13 @@ class WearEngineHostApi { | @@ -209,16 +201,13 @@ class WearEngineHostApi { | ||
| 209 | } | 201 | } |
| 210 | 202 | ||
| 211 | Future<bool> sendTextMessage(String message) async { | 203 | Future<bool> sendTextMessage(String message) async { |
| 212 | - final String pigeonVar_channelName = | ||
| 213 | - 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendTextMessage$pigeonVar_messageChannelSuffix'; | ||
| 214 | - final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 215 | - BasicMessageChannel<Object?>( | 204 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendTextMessage$pigeonVar_messageChannelSuffix'; |
| 205 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 216 | pigeonVar_channelName, | 206 | pigeonVar_channelName, |
| 217 | pigeonChannelCodec, | 207 | pigeonChannelCodec, |
| 218 | binaryMessenger: pigeonVar_binaryMessenger, | 208 | binaryMessenger: pigeonVar_binaryMessenger, |
| 219 | ); | 209 | ); |
| 220 | - final Future<Object?> pigeonVar_sendFuture = | ||
| 221 | - pigeonVar_channel.send(<Object?>[message]); | 210 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[message]); |
| 222 | final List<Object?>? pigeonVar_replyList = | 211 | final List<Object?>? pigeonVar_replyList = |
| 223 | await pigeonVar_sendFuture as List<Object?>?; | 212 | await pigeonVar_sendFuture as List<Object?>?; |
| 224 | if (pigeonVar_replyList == null) { | 213 | if (pigeonVar_replyList == null) { |
| @@ -240,16 +229,13 @@ class WearEngineHostApi { | @@ -240,16 +229,13 @@ class WearEngineHostApi { | ||
| 240 | } | 229 | } |
| 241 | 230 | ||
| 242 | Future<bool> sendWatchSyncPayload(String jsonPayload) async { | 231 | Future<bool> sendWatchSyncPayload(String jsonPayload) async { |
| 243 | - final String pigeonVar_channelName = | ||
| 244 | - 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload$pigeonVar_messageChannelSuffix'; | ||
| 245 | - final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 246 | - BasicMessageChannel<Object?>( | 232 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload$pigeonVar_messageChannelSuffix'; |
| 233 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 247 | pigeonVar_channelName, | 234 | pigeonVar_channelName, |
| 248 | pigeonChannelCodec, | 235 | pigeonChannelCodec, |
| 249 | binaryMessenger: pigeonVar_binaryMessenger, | 236 | binaryMessenger: pigeonVar_binaryMessenger, |
| 250 | ); | 237 | ); |
| 251 | - final Future<Object?> pigeonVar_sendFuture = | ||
| 252 | - pigeonVar_channel.send(<Object?>[jsonPayload]); | 238 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[jsonPayload]); |
| 253 | final List<Object?>? pigeonVar_replyList = | 239 | final List<Object?>? pigeonVar_replyList = |
| 254 | await pigeonVar_sendFuture as List<Object?>?; | 240 | await pigeonVar_sendFuture as List<Object?>?; |
| 255 | if (pigeonVar_replyList == null) { | 241 | if (pigeonVar_replyList == null) { |
| @@ -270,11 +256,11 @@ class WearEngineHostApi { | @@ -270,11 +256,11 @@ class WearEngineHostApi { | ||
| 270 | } | 256 | } |
| 271 | } | 257 | } |
| 272 | 258 | ||
| 259 | + /// Opens the system photo picker and returns a local PNG file path after | ||
| 260 | + /// removing the image background on the host platform. | ||
| 273 | Future<String?> pickImageAndRemoveBackground() async { | 261 | Future<String?> pickImageAndRemoveBackground() async { |
| 274 | - final String pigeonVar_channelName = | ||
| 275 | - 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground$pigeonVar_messageChannelSuffix'; | ||
| 276 | - final BasicMessageChannel<Object?> pigeonVar_channel = | ||
| 277 | - BasicMessageChannel<Object?>( | 262 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground$pigeonVar_messageChannelSuffix'; |
| 263 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 278 | pigeonVar_channelName, | 264 | pigeonVar_channelName, |
| 279 | pigeonChannelCodec, | 265 | pigeonChannelCodec, |
| 280 | binaryMessenger: pigeonVar_binaryMessenger, | 266 | binaryMessenger: pigeonVar_binaryMessenger, |
| @@ -12,6 +12,40 @@ class HealthUploadResult { | @@ -12,6 +12,40 @@ class HealthUploadResult { | ||
| 12 | String? errorMessage; | 12 | String? errorMessage; |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | +class HealthUploadDataPoint { | ||
| 16 | + HealthUploadDataPoint({ | ||
| 17 | + required this.dataType, | ||
| 18 | + required this.time, | ||
| 19 | + required this.value, | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + int dataType; | ||
| 23 | + int time; | ||
| 24 | + double value; | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +class HealthSleepUploadDataPoint { | ||
| 28 | + HealthSleepUploadDataPoint({ | ||
| 29 | + required this.dataType, | ||
| 30 | + required this.fromTime, | ||
| 31 | + required this.toTime, | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + int dataType; | ||
| 35 | + int fromTime; | ||
| 36 | + int toTime; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +class HealthActivityTargetData { | ||
| 40 | + HealthActivityTargetData({ | ||
| 41 | + this.move, | ||
| 42 | + this.stand, | ||
| 43 | + }); | ||
| 44 | + | ||
| 45 | + int? move; | ||
| 46 | + int? stand; | ||
| 47 | +} | ||
| 48 | + | ||
| 15 | @ConfigurePigeon( | 49 | @ConfigurePigeon( |
| 16 | PigeonOptions( | 50 | PigeonOptions( |
| 17 | dartOut: 'lib/pigeon/health_kit_api.g.dart', | 51 | dartOut: 'lib/pigeon/health_kit_api.g.dart', |
| @@ -41,4 +75,61 @@ abstract class HealthKitHostApi { | @@ -41,4 +75,61 @@ abstract class HealthKitHostApi { | ||
| 41 | 75 | ||
| 42 | /// Runs native health read and server upload pipeline. | 76 | /// Runs native health read and server upload pipeline. |
| 43 | HealthUploadResult performHealthUpload(); | 77 | HealthUploadResult performHealthUpload(); |
| 78 | + | ||
| 79 | + List<HealthUploadDataPoint> fetchHrvData(int startTime, int endTime); | ||
| 80 | + | ||
| 81 | + List<HealthUploadDataPoint> fetchHeartRateData(int startTime, int endTime); | ||
| 82 | + | ||
| 83 | + List<HealthUploadDataPoint> fetchWalkingHeartRateData( | ||
| 84 | + int startTime, | ||
| 85 | + int endTime, | ||
| 86 | + ); | ||
| 87 | + | ||
| 88 | + List<HealthUploadDataPoint> fetchRestingHeartRateData( | ||
| 89 | + int startTime, | ||
| 90 | + int endTime, | ||
| 91 | + ); | ||
| 92 | + | ||
| 93 | + List<HealthUploadDataPoint> fetchSleepingHeartRateData( | ||
| 94 | + int startTime, | ||
| 95 | + int endTime, | ||
| 96 | + ); | ||
| 97 | + | ||
| 98 | + List<HealthUploadDataPoint> fetchOxygenSaturationData( | ||
| 99 | + int startTime, | ||
| 100 | + int endTime, | ||
| 101 | + ); | ||
| 102 | + | ||
| 103 | + List<HealthUploadDataPoint> fetchActiveEnergyData( | ||
| 104 | + int startTime, | ||
| 105 | + int endTime, | ||
| 106 | + ); | ||
| 107 | + | ||
| 108 | + List<HealthUploadDataPoint> fetchExerciseData(int startTime, int endTime); | ||
| 109 | + | ||
| 110 | + List<HealthUploadDataPoint> fetchStandData(int startTime, int endTime); | ||
| 111 | + | ||
| 112 | + List<HealthUploadDataPoint> fetchStepCountData(int startTime, int endTime); | ||
| 113 | + | ||
| 114 | + List<HealthSleepUploadDataPoint> fetchSleepData(int startTime, int endTime); | ||
| 115 | + | ||
| 116 | + List<HealthUploadDataPoint> fetchSleepingWristTemperatureData( | ||
| 117 | + int startTime, | ||
| 118 | + int endTime, | ||
| 119 | + ); | ||
| 120 | + | ||
| 121 | + List<HealthUploadDataPoint> fetchRespiratoryRateData( | ||
| 122 | + int startTime, | ||
| 123 | + int endTime, | ||
| 124 | + ); | ||
| 125 | + | ||
| 126 | + List<HealthUploadDataPoint> fetchIrregularHeartRhythmData( | ||
| 127 | + int startTime, | ||
| 128 | + int endTime, | ||
| 129 | + ); | ||
| 130 | + | ||
| 131 | + HealthActivityTargetData? fetchActivityTargetData( | ||
| 132 | + int startTime, | ||
| 133 | + int endTime, | ||
| 134 | + ); | ||
| 44 | } | 135 | } |
-
Please register or login to post a comment