Commit a388346ad0159ecf70fadc7a5498832aaa2c6dd4

Authored by 权海
1 parent bcaf2779

feat(ui):初步添加上传数据的api和添加测试AppleHealth数据页面

Showing 46 changed files with 3042 additions and 162 deletions
... ... @@ -12,6 +12,7 @@
.swiftpm/
migrate_working_dir/
SDK/
ohos/
# IntelliJ related
*.iml
... ...
package com.doublefeel.app.native
import com.doublefeel.app.MainActivity
import com.doublefeel.app.pigeon.HealthActivityTargetData
import com.doublefeel.app.pigeon.HealthKitHostApi
import com.doublefeel.app.pigeon.HealthSleepUploadDataPoint
import com.doublefeel.app.pigeon.HealthUploadDataPoint
import com.doublefeel.app.pigeon.HealthUploadResult
class HealthKitHostApiImpl(
... ... @@ -31,4 +34,34 @@ class HealthKitHostApiImpl(
errorMessage = "Native bulk upload runs via Dart HealthKitUploadService; HMS read bridge pending",
)
}
override fun fetchHrvData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchWalkingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchRestingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchSleepingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchOxygenSaturationData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchActiveEnergyData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchExerciseData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchStandData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchStepCountData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchSleepData(startTime: Long, endTime: Long): List<HealthSleepUploadDataPoint> = emptyList()
override fun fetchSleepingWristTemperatureData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchRespiratoryRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchIrregularHeartRhythmData(startTime: Long, endTime: Long): List<HealthUploadDataPoint> = emptyList()
override fun fetchActivityTargetData(startTime: Long, endTime: Long): HealthActivityTargetData? = null
}
... ...
... ... @@ -112,6 +112,105 @@ data class HealthUploadResult (
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class HealthUploadDataPoint (
val dataType: Long,
val time: Long,
val value: Double
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): HealthUploadDataPoint {
val dataType = pigeonVar_list[0] as Long
val time = pigeonVar_list[1] as Long
val value = pigeonVar_list[2] as Double
return HealthUploadDataPoint(dataType, time, value)
}
}
fun toList(): List<Any?> {
return listOf(
dataType,
time,
value,
)
}
override fun equals(other: Any?): Boolean {
if (other !is HealthUploadDataPoint) {
return false
}
if (this === other) {
return true
}
return HealthKitApiPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class HealthSleepUploadDataPoint (
val dataType: Long,
val fromTime: Long,
val toTime: Long
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): HealthSleepUploadDataPoint {
val dataType = pigeonVar_list[0] as Long
val fromTime = pigeonVar_list[1] as Long
val toTime = pigeonVar_list[2] as Long
return HealthSleepUploadDataPoint(dataType, fromTime, toTime)
}
}
fun toList(): List<Any?> {
return listOf(
dataType,
fromTime,
toTime,
)
}
override fun equals(other: Any?): Boolean {
if (other !is HealthSleepUploadDataPoint) {
return false
}
if (this === other) {
return true
}
return HealthKitApiPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class HealthActivityTargetData (
val move: Long? = null,
val stand: Long? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): HealthActivityTargetData {
val move = pigeonVar_list[0] as Long?
val stand = pigeonVar_list[1] as Long?
return HealthActivityTargetData(move, stand)
}
}
fun toList(): List<Any?> {
return listOf(
move,
stand,
)
}
override fun equals(other: Any?): Boolean {
if (other !is HealthActivityTargetData) {
return false
}
if (this === other) {
return true
}
return HealthKitApiPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
private open class HealthKitApiPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
... ... @@ -120,6 +219,21 @@ private open class HealthKitApiPigeonCodec : StandardMessageCodec() {
HealthUploadResult.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HealthUploadDataPoint.fromList(it)
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HealthSleepUploadDataPoint.fromList(it)
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HealthActivityTargetData.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
... ... @@ -129,6 +243,18 @@ private open class HealthKitApiPigeonCodec : StandardMessageCodec() {
stream.write(129)
writeValue(stream, value.toList())
}
is HealthUploadDataPoint -> {
stream.write(130)
writeValue(stream, value.toList())
}
is HealthSleepUploadDataPoint -> {
stream.write(131)
writeValue(stream, value.toList())
}
is HealthActivityTargetData -> {
stream.write(132)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
... ... @@ -143,6 +269,21 @@ interface HealthKitHostApi {
fun cancelHealthAppAuthorization(): Boolean
/** Runs native health read and server upload pipeline. */
fun performHealthUpload(): HealthUploadResult
fun fetchHrvData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchWalkingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchRestingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchSleepingHeartRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchOxygenSaturationData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchActiveEnergyData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchExerciseData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchStandData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchStepCountData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchSleepData(startTime: Long, endTime: Long): List<HealthSleepUploadDataPoint>
fun fetchSleepingWristTemperatureData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchRespiratoryRateData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchIrregularHeartRhythmData(startTime: Long, endTime: Long): List<HealthUploadDataPoint>
fun fetchActivityTargetData(startTime: Long, endTime: Long): HealthActivityTargetData?
companion object {
/** The codec used by HealthKitHostApi. */
... ... @@ -228,6 +369,276 @@ interface HealthKitHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHrvData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchHrvData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHeartRateData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchHeartRateData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchWalkingHeartRateData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchWalkingHeartRateData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRestingHeartRateData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchRestingHeartRateData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingHeartRateData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchSleepingHeartRateData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchOxygenSaturationData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchOxygenSaturationData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActiveEnergyData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchActiveEnergyData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchExerciseData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchExerciseData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStandData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchStandData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStepCountData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchStepCountData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchSleepData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingWristTemperatureData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchSleepingWristTemperatureData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRespiratoryRateData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchRespiratoryRateData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchIrregularHeartRhythmData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchIrregularHeartRhythmData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActivityTargetData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val startTimeArg = args[0] as Long
val endTimeArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.fetchActivityTargetData(startTimeArg, endTimeArg))
} catch (exception: Throwable) {
HealthKitApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
... ...
... ... @@ -141,6 +141,11 @@ interface WearEngineHostApi {
fun registerMessageReceiver(): Boolean
fun sendTextMessage(message: String): Boolean
fun sendWatchSyncPayload(jsonPayload: String): Boolean
/**
* Opens the system photo picker and returns a local PNG file path after
* removing the image background on the host platform.
*/
fun pickImageAndRemoveBackground(): String?
companion object {
/** The codec used by WearEngineHostApi. */
... ... @@ -230,6 +235,21 @@ interface WearEngineHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.pickImageAndRemoveBackground())
} catch (exception: Throwable) {
WearEngineApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
... ...
import SwiftUI
import UIKit
struct DFDefaultWatchHomeView: View {
@State private var batteryLevel: Float = UIDevice.current.batteryLevel
@State private var batteryState: UIDevice.BatteryState = UIDevice.current.batteryState
@State private var weekday: String = ""
@State private var day: String = ""
var hasData: Bool = false
var hrvValue: String = "38ms"
var hrvTime: String = "19:04"
var batteryColor: Color{
if batteryLevel < 0.1{
// 0
return Color(hex: "#FF0000")
}else if batteryLevel < 40{
// 25
return Color(hex: "#FF9A6E")
}else if batteryLevel < 60{
// 50
return Color(hex: "#7B9BFB")
}else if batteryLevel < 80{
// 75
return Color(hex: "#3BD49D")
}else{
// 100
return Color(hex: "#3BD49D")
}
}
var batteryImageName: String{
if batteryLevel < 0.1{
// 0
return "battery.0percent"
}else if batteryLevel < 40{
// 25
return "battery.25percent"
}else if batteryLevel < 60{
// 50
return "battery.50percent"
}else if batteryLevel < 80{
// 75
return "battery.75percent"
}else{
// 100
return "battery.100percent"
}
}
var body: some View {
VStack{
HStack{
Image(systemName: batteryImageName)
.frame(height: 20)
.tint(batteryColor)
Spacer()
Text(weekday)
.font(.system(size: 16, weight: .medium))
... ... @@ -98,30 +54,9 @@ struct DFDefaultWatchHomeView: View {
.padding(.all, 15)
.onAppear {
updateDate()
updateBattery()
NotificationCenter.default.addObserver(
forName: UIDevice.batteryLevelDidChangeNotification,
object: nil,
queue: .main
) { _ in
updateBattery()
}
NotificationCenter.default.addObserver(
forName: UIDevice.batteryStateDidChangeNotification,
object: nil,
queue: .main
) { _ in
updateBattery()
}
}
}
private func updateBattery() {
UIDevice.current.isBatteryMonitoringEnabled = true
batteryLevel = UIDevice.current.batteryLevel
batteryState = UIDevice.current.batteryState
}
private func updateDate(){
let calendar = Calendar.current
let component = calendar.component(.weekday, from: .now)
... ...
... ... @@ -3,15 +3,15 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
0798E27DB95F2A823881D75A /* Pods_Runner_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A488EFC0BD642ED604C4E43 /* Pods_Runner_Watch_App.framework */; };
20A3C4994C9B0F60D1F3BCBE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8DC48B3736B7FEB548D0E65 /* Pods_Runner.framework */; };
66FB19002FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */; };
66FB19012FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */; };
66FB19022FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */; };
66FB19002FDBC80100F515B4 /* DFWatchFigmaTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */; };
66FB19012FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */; };
66FB19022FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */; };
66FBE58C2FDA4F0F00F515B4 /* Runner Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
... ... @@ -54,9 +54,9 @@
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>"; };
4A9B7C102FDB0A1200F515B4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
4A9B7C112FDB0A1200F515B4 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift"; sourceTree = "<group>"; };
66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift"; sourceTree = "<group>"; };
66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift"; sourceTree = "<group>"; };
66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift"; sourceTree = "<group>"; };
66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift"; sourceTree = "<group>"; };
66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift"; sourceTree = "<group>"; };
66FBE57E2FDA4F0E00F515B4 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Runner Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
66FBE5A72FDA518C00F515B4 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
... ... @@ -97,6 +97,8 @@
};
66FBE58F2FDA4F0F00F515B4 /* Runner Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = "Runner Watch App";
sourceTree = "<group>";
};
... ... @@ -134,9 +136,9 @@
66FB19072FDBC80100F515B4 /* FigmaHome Preview Sources */ = {
isa = PBXGroup;
children = (
66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */,
66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */,
66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */,
66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */,
66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */,
66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */,
);
name = "FigmaHome Preview Sources";
sourceTree = "<group>";
... ... @@ -303,14 +305,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
... ... @@ -381,14 +379,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
... ... @@ -402,14 +396,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks.sh\"\n";
... ... @@ -451,9 +441,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
66FB19022FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift in Sources */,
66FB19012FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift in Sources */,
66FB19002FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift in Sources */,
66FB19022FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift in Sources */,
66FB19012FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift in Sources */,
66FB19002FDBC80100F515B4 /* DFWatchFigmaTokens.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
... ...
... ... @@ -133,6 +133,170 @@ final class HealthDataReader {
}
}
func fetchHrvData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .heartRateVariabilitySDNN,
dataType: .hrv,
unit: .secondUnit(with: .milli),
startDate: startDate,
endDate: endDate
)
}
func fetchHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .heartRate,
dataType: .heartRate,
unit: HKUnit.count().unitDivided(by: .minute()),
startDate: startDate,
endDate: endDate
)
}
func fetchWalkingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .walkingHeartRateAverage,
dataType: .walkingHeartRate,
unit: HKUnit.count().unitDivided(by: .minute()),
startDate: startDate,
endDate: endDate
)
}
func fetchRestingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .restingHeartRate,
dataType: .restingHeartRate,
unit: HKUnit.count().unitDivided(by: .minute()),
startDate: startDate,
endDate: endDate
)
}
func fetchSleepingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
let sleepIntervals = try await fetchSleepIntervals(startDate: startDate, endDate: endDate)
var points: [NativeHealthDataPoint] = []
for interval in sleepIntervals where NativeSleepStage.isAsleep(interval.dataType) {
let samples = try await fetchQuantitySamples(
identifier: .heartRate,
dataType: .sleepingHeartRate,
unit: HKUnit.count().unitDivided(by: .minute()),
startDate: Date(timeIntervalSince1970: interval.fromTime),
endDate: Date(timeIntervalSince1970: interval.toTime)
)
points.append(contentsOf: samples)
}
return points
}
func fetchOxygenSaturationData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
let points = try await fetchQuantitySamples(
identifier: .oxygenSaturation,
dataType: .oxygenSaturation,
unit: .percent(),
startDate: startDate,
endDate: endDate
)
return points.map { point in
NativeHealthDataPoint(dataType: point.dataType, time: point.time, value: point.value * 100)
}
}
func fetchActiveEnergyData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .activeEnergyBurned,
dataType: .activeEnergy,
unit: .kilocalorie(),
startDate: startDate,
endDate: endDate
)
}
func fetchExerciseData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .appleExerciseTime,
dataType: .exercise,
unit: .minute(),
startDate: startDate,
endDate: endDate
)
}
func fetchStandData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .appleStandTime,
dataType: .stand,
unit: .minute(),
startDate: startDate,
endDate: endDate
)
}
func fetchStepCountData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchDailyCumulativeSamples(
identifier: .stepCount,
dataType: .steps,
unit: .count(),
startDate: startDate,
endDate: endDate
)
}
func fetchSleepData(startDate: Date, endDate: Date) async throws -> [NativeSleepInterval] {
try await fetchSleepIntervals(startDate: startDate, endDate: endDate)
}
func fetchSleepingWristTemperatureData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .appleSleepingWristTemperature,
dataType: .sleepingWristTemperature,
unit: .degreeCelsius(),
startDate: startDate,
endDate: endDate
)
}
func fetchRespiratoryRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchQuantitySamples(
identifier: .respiratoryRate,
dataType: .respiratoryRate,
unit: HKUnit.count().unitDivided(by: .minute()),
startDate: startDate,
endDate: endDate
)
}
func fetchIrregularHeartRhythmData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchIrregularHeartRhythmEvents(startDate: startDate, endDate: endDate)
}
func fetchActivityTargetData(startDate: Date, endDate: Date) async throws -> NativeActivityTarget? {
let calendar = Calendar.current
let start = calendar.dateComponents([.era, .year, .month, .day], from: startDate)
let end = calendar.dateComponents([.era, .year, .month, .day], from: endDate)
let predicate = HKQuery.predicate(forActivitySummariesBetweenStart: start, end: end)
return try await withCheckedThrowingContinuation { continuation in
let query = HKActivitySummaryQuery(predicate: predicate) { _, summaries, error in
if let error {
continuation.resume(throwing: error)
return
}
guard let summary = summaries?.last else {
continuation.resume(returning: nil)
return
}
continuation.resume(
returning: NativeActivityTarget(
move: Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())),
stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count()))
)
)
}
healthStore.execute(query)
}
}
private func fetchHeartRateFamily(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
async let heartRate = fetchQuantitySamples(
identifier: .heartRate,
... ...
... ... @@ -108,6 +108,66 @@ final class HealthKitService {
}
}
func fetchHrvData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchHrvData(startDate: startDate, endDate: endDate)
}
func fetchHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchHeartRateData(startDate: startDate, endDate: endDate)
}
func fetchWalkingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchWalkingHeartRateData(startDate: startDate, endDate: endDate)
}
func fetchRestingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchRestingHeartRateData(startDate: startDate, endDate: endDate)
}
func fetchSleepingHeartRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchSleepingHeartRateData(startDate: startDate, endDate: endDate)
}
func fetchOxygenSaturationData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchOxygenSaturationData(startDate: startDate, endDate: endDate)
}
func fetchActiveEnergyData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchActiveEnergyData(startDate: startDate, endDate: endDate)
}
func fetchExerciseData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchExerciseData(startDate: startDate, endDate: endDate)
}
func fetchStandData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchStandData(startDate: startDate, endDate: endDate)
}
func fetchStepCountData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchStepCountData(startDate: startDate, endDate: endDate)
}
func fetchSleepData(startDate: Date, endDate: Date) async throws -> [NativeSleepInterval] {
try await reader.fetchSleepData(startDate: startDate, endDate: endDate)
}
func fetchSleepingWristTemperatureData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchSleepingWristTemperatureData(startDate: startDate, endDate: endDate)
}
func fetchRespiratoryRateData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchRespiratoryRateData(startDate: startDate, endDate: endDate)
}
func fetchIrregularHeartRhythmData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await reader.fetchIrregularHeartRhythmData(startDate: startDate, endDate: endDate)
}
func fetchActivityTargetData(startDate: Date, endDate: Date) async throws -> NativeActivityTarget? {
try await reader.fetchActivityTargetData(startDate: startDate, endDate: endDate)
}
private func earliestStartDate() -> Date {
NativeHealthDataType.allCases
.filter { $0 != .unknown }
... ... @@ -174,4 +234,3 @@ private extension NativeHealthDataType {
}
}
}
... ...
... ... @@ -49,6 +49,25 @@ struct NativeSleepInterval: Codable {
let toTime: TimeInterval
}
struct NativeActivityTarget {
let move: Int?
let stand: Int?
}
enum NativeSleepStage {
static func isAsleep(_ rawValue: Int) -> Bool {
guard let value = HKCategoryValueSleepAnalysis(rawValue: rawValue) else {
return false
}
switch value {
case .asleepUnspecified, .asleepCore, .asleepDeep, .asleepREM:
return true
default:
return false
}
}
}
struct NativeHealthSyncSummary {
var commonCount = 0
var sleepCount = 0
... ...
... ... @@ -145,11 +145,112 @@ struct HealthUploadResult: Hashable {
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct HealthUploadDataPoint: Hashable {
var dataType: Int64
var time: Int64
var value: Double
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> HealthUploadDataPoint? {
let dataType = pigeonVar_list[0] as! Int64
let time = pigeonVar_list[1] as! Int64
let value = pigeonVar_list[2] as! Double
return HealthUploadDataPoint(
dataType: dataType,
time: time,
value: value
)
}
func toList() -> [Any?] {
return [
dataType,
time,
value,
]
}
static func == (lhs: HealthUploadDataPoint, rhs: HealthUploadDataPoint) -> Bool {
return deepEqualsHealthKitApi(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashHealthKitApi(value: toList(), hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct HealthSleepUploadDataPoint: Hashable {
var dataType: Int64
var fromTime: Int64
var toTime: Int64
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> HealthSleepUploadDataPoint? {
let dataType = pigeonVar_list[0] as! Int64
let fromTime = pigeonVar_list[1] as! Int64
let toTime = pigeonVar_list[2] as! Int64
return HealthSleepUploadDataPoint(
dataType: dataType,
fromTime: fromTime,
toTime: toTime
)
}
func toList() -> [Any?] {
return [
dataType,
fromTime,
toTime,
]
}
static func == (lhs: HealthSleepUploadDataPoint, rhs: HealthSleepUploadDataPoint) -> Bool {
return deepEqualsHealthKitApi(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashHealthKitApi(value: toList(), hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct HealthActivityTargetData: Hashable {
var move: Int64? = nil
var stand: Int64? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> HealthActivityTargetData? {
let move: Int64? = nilOrValue(pigeonVar_list[0])
let stand: Int64? = nilOrValue(pigeonVar_list[1])
return HealthActivityTargetData(
move: move,
stand: stand
)
}
func toList() -> [Any?] {
return [
move,
stand,
]
}
static func == (lhs: HealthActivityTargetData, rhs: HealthActivityTargetData) -> Bool {
return deepEqualsHealthKitApi(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashHealthKitApi(value: toList(), hasher: &hasher)
}
}
private class HealthKitApiPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
return HealthUploadResult.fromList(self.readValue() as! [Any?])
case 130:
return HealthUploadDataPoint.fromList(self.readValue() as! [Any?])
case 131:
return HealthSleepUploadDataPoint.fromList(self.readValue() as! [Any?])
case 132:
return HealthActivityTargetData.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
... ... @@ -161,6 +262,15 @@ private class HealthKitApiPigeonCodecWriter: FlutterStandardWriter {
if let value = value as? HealthUploadResult {
super.writeByte(129)
super.writeValue(value.toList())
} else if let value = value as? HealthUploadDataPoint {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? HealthSleepUploadDataPoint {
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? HealthActivityTargetData {
super.writeByte(132)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
... ... @@ -190,6 +300,21 @@ protocol HealthKitHostApi {
func cancelHealthAppAuthorization() throws -> Bool
/// Runs native health read and server upload pipeline.
func performHealthUpload() throws -> HealthUploadResult
func fetchHrvData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchWalkingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchRestingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchSleepingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchOxygenSaturationData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchActiveEnergyData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchExerciseData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchStandData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchStepCountData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchSleepData(startTime: Int64, endTime: Int64) throws -> [HealthSleepUploadDataPoint]
func fetchSleepingWristTemperatureData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchRespiratoryRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchIrregularHeartRhythmData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint]
func fetchActivityTargetData(startTime: Int64, endTime: Int64) throws -> HealthActivityTargetData?
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
... ... @@ -265,5 +390,245 @@ class HealthKitHostApiSetup {
} else {
performHealthUploadChannel.setMessageHandler(nil)
}
let fetchHrvDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHrvData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchHrvDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchHrvData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchHrvDataChannel.setMessageHandler(nil)
}
let fetchHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchHeartRateDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchHeartRateData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchHeartRateDataChannel.setMessageHandler(nil)
}
let fetchWalkingHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchWalkingHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchWalkingHeartRateDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchWalkingHeartRateData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchWalkingHeartRateDataChannel.setMessageHandler(nil)
}
let fetchRestingHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRestingHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchRestingHeartRateDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchRestingHeartRateData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchRestingHeartRateDataChannel.setMessageHandler(nil)
}
let fetchSleepingHeartRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingHeartRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSleepingHeartRateDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchSleepingHeartRateData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchSleepingHeartRateDataChannel.setMessageHandler(nil)
}
let fetchOxygenSaturationDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchOxygenSaturationData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchOxygenSaturationDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchOxygenSaturationData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchOxygenSaturationDataChannel.setMessageHandler(nil)
}
let fetchActiveEnergyDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActiveEnergyData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchActiveEnergyDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchActiveEnergyData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchActiveEnergyDataChannel.setMessageHandler(nil)
}
let fetchExerciseDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchExerciseData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchExerciseDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchExerciseData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchExerciseDataChannel.setMessageHandler(nil)
}
let fetchStandDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStandData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchStandDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchStandData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchStandDataChannel.setMessageHandler(nil)
}
let fetchStepCountDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStepCountData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchStepCountDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchStepCountData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchStepCountDataChannel.setMessageHandler(nil)
}
let fetchSleepDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSleepDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchSleepData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchSleepDataChannel.setMessageHandler(nil)
}
let fetchSleepingWristTemperatureDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingWristTemperatureData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchSleepingWristTemperatureDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchSleepingWristTemperatureData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchSleepingWristTemperatureDataChannel.setMessageHandler(nil)
}
let fetchRespiratoryRateDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRespiratoryRateData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchRespiratoryRateDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchRespiratoryRateData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchRespiratoryRateDataChannel.setMessageHandler(nil)
}
let fetchIrregularHeartRhythmDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchIrregularHeartRhythmData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchIrregularHeartRhythmDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchIrregularHeartRhythmData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchIrregularHeartRhythmDataChannel.setMessageHandler(nil)
}
let fetchActivityTargetDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActivityTargetData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
fetchActivityTargetDataChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let startTimeArg = args[0] as! Int64
let endTimeArg = args[1] as! Int64
do {
let result = try api.fetchActivityTargetData(startTime: startTimeArg, endTime: endTimeArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
fetchActivityTargetDataChannel.setMessageHandler(nil)
}
}
}
... ...
... ... @@ -50,6 +50,109 @@ final class HealthKitHostApiImpl: HealthKitHostApi {
)
}
func fetchHrvData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchHrvData)
}
func fetchHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchHeartRateData)
}
func fetchWalkingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchWalkingHeartRateData)
}
func fetchRestingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchRestingHeartRateData)
}
func fetchSleepingHeartRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchSleepingHeartRateData)
}
func fetchOxygenSaturationData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchOxygenSaturationData)
}
func fetchActiveEnergyData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchActiveEnergyData)
}
func fetchExerciseData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchExerciseData)
}
func fetchStandData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchStandData)
}
func fetchStepCountData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchStepCountData)
}
func fetchSleepData(startTime: Int64, endTime: Int64) throws -> [HealthSleepUploadDataPoint] {
let range = makeDateRange(startTime: startTime, endTime: endTime)
let intervals = try runBlockingThrows {
try await self.service.fetchSleepData(startDate: range.startDate, endDate: range.endDate)
}
return intervals.map { interval in
HealthSleepUploadDataPoint(
dataType: Int64(interval.dataType),
fromTime: Int64(interval.fromTime.rounded()),
toTime: Int64(interval.toTime.rounded())
)
}
}
func fetchSleepingWristTemperatureData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchSleepingWristTemperatureData)
}
func fetchRespiratoryRateData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchRespiratoryRateData)
}
func fetchIrregularHeartRhythmData(startTime: Int64, endTime: Int64) throws -> [HealthUploadDataPoint] {
try fetchCommon(startTime: startTime, endTime: endTime, service.fetchIrregularHeartRhythmData)
}
func fetchActivityTargetData(startTime: Int64, endTime: Int64) throws -> HealthActivityTargetData? {
let range = makeDateRange(startTime: startTime, endTime: endTime)
let target = try runBlockingThrows {
try await self.service.fetchActivityTargetData(startDate: range.startDate, endDate: range.endDate)
}
guard let target else { return nil }
return HealthActivityTargetData(
move: target.move.map(Int64.init),
stand: target.stand.map(Int64.init)
)
}
private func fetchCommon(
startTime: Int64,
endTime: Int64,
_ fetch: @escaping (Date, Date) async throws -> [NativeHealthDataPoint]
) throws -> [HealthUploadDataPoint] {
let range = makeDateRange(startTime: startTime, endTime: endTime)
let points = try runBlockingThrows {
try await fetch(range.startDate, range.endDate)
}
return points.map { point in
HealthUploadDataPoint(
dataType: Int64(point.dataType.rawValue),
time: Int64(point.time.rounded()),
value: point.value
)
}
}
private func makeDateRange(startTime: Int64, endTime: Int64) -> (startDate: Date, endDate: Date) {
(
Date(timeIntervalSince1970: TimeInterval(startTime)),
Date(timeIntervalSince1970: TimeInterval(endTime))
)
}
private func runAuthorizationRequest() -> (success: Bool, error: Error?) {
var result: (Bool, Error?) = (false, nil)
let semaphore = DispatchSemaphore(value: 0)
... ... @@ -73,6 +176,21 @@ private func runBlocking<T>(_ operation: @escaping () async -> T) -> T {
return result!
}
private func runBlockingThrows<T>(_ operation: @escaping () async throws -> T) throws -> T {
let semaphore = DispatchSemaphore(value: 0)
var result: Result<T, Error>?
Task {
do {
result = .success(try await operation())
} catch {
result = .failure(error)
}
semaphore.signal()
}
waitForSemaphore(semaphore)
return try result!.get()
}
private func waitForSemaphore(_ semaphore: DispatchSemaphore) {
if Thread.isMainThread {
while semaphore.wait(timeout: .now() + 0.05) == .timedOut {
... ...
... ... @@ -188,6 +188,8 @@ protocol WearEngineHostApi {
func registerMessageReceiver() throws -> Bool
func sendTextMessage(message: String) throws -> Bool
func sendWatchSyncPayload(jsonPayload: String) throws -> Bool
/// Opens the system photo picker and returns a local PNG file path after
/// removing the image background on the host platform.
func pickImageAndRemoveBackground() throws -> String?
}
... ... @@ -266,6 +268,8 @@ class WearEngineHostApiSetup {
} else {
sendWatchSyncPayloadChannel.setMessageHandler(nil)
}
/// Opens the system photo picker and returns a local PNG file path after
/// removing the image background on the host platform.
let pickImageAndRemoveBackgroundChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
pickImageAndRemoveBackgroundChannel.setMessageHandler { _, reply in
... ...
import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart';
import 'package:doublefeel_flutter/core/network/api_paths.dart';
import 'package:doublefeel_flutter/core/network/dio_client.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/result/safe_call.dart';
import 'models/apple_health_upload_record.dart';
import 'models/apple_health_upload_request.dart';
import 'models/upload_activity_target.dart';
class AppleHealthUploadApi {
AppleHealthUploadApi(this._dioClient);
final DioClient _dioClient;
Future<AppResult<void>> uploadCommonData(
AppleHealthCommonUploadRequest request,
) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.healthUploadCommon,
data: request.toJson(),
);
},
);
}
Future<AppResult<AppleHealthLatestUploadRecordList>>
getLatestCommonUploadRecordList({
HttpErrorHandlingPolicy? errorHandlingPolicy =
HttpErrorHandlingPolicy.defaultPolicy,
}) {
return safeCall(
call: () async {
final response = await _dioClient.dio.get(ApiPaths.healthUploadCommon);
return AppleHealthLatestUploadRecordList.fromJson(
response.data as Map<String, dynamic>,
);
},
errorHandlingPolicy: errorHandlingPolicy,
);
}
Future<AppResult<void>> uploadSleepData(
AppleHealthSleepUploadRequest request,
) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.healthUploadSleep,
data: request.toJson(),
);
},
);
}
Future<AppResult<AppleHealthLatestSleepUploadRecord>>
getLatestSleepUploadRecord({
HttpErrorHandlingPolicy? errorHandlingPolicy =
HttpErrorHandlingPolicy.defaultPolicy,
}) {
return safeCall(
call: () async {
final response = await _dioClient.dio.get(ApiPaths.healthUploadSleep);
return AppleHealthLatestSleepUploadRecord.fromJson(
response.data as Map<String, dynamic>,
);
},
errorHandlingPolicy: errorHandlingPolicy,
);
}
Future<AppResult<void>> uploadActivityTarget(
HealthActivityTargetUploadData target,
) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.healthActivityTarget,
data: target.toJson(),
);
},
);
}
}
... ...
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'apple_health_upload_api.dart';
import 'models/apple_health_upload_record.dart';
import 'models/apple_health_upload_request.dart';
import 'models/apple_health_upload_sample.dart';
import 'models/upload_activity_target.dart';
import 'models/upload_sleep.dart';
class AppleHealthUploadTool {
AppleHealthUploadTool(this._api);
final AppleHealthUploadApi _api;
Future<AppleHealthUploadResult> upload({
List<AppleHealthUploadSample> commonData = const [],
List<HealthSleepUploadData> sleepData = const [],
HealthActivityTargetUploadData? activityTarget,
}) async {
AppResult<void>? commonResult;
AppResult<void>? sleepResult;
AppResult<void>? activityTargetResult;
if (commonData.isNotEmpty) {
commonResult = await _api.uploadCommonData(
AppleHealthCommonUploadRequest(dataList: commonData),
);
}
if (sleepData.isNotEmpty) {
sleepResult = await _api.uploadSleepData(
AppleHealthSleepUploadRequest(dataList: sleepData),
);
}
if (activityTarget != null) {
activityTargetResult = await _api.uploadActivityTarget(activityTarget);
}
return AppleHealthUploadResult(
commonUploadSuccess:
commonResult == null || commonResult is AppSuccess<void>,
sleepUploadSuccess:
sleepResult == null || sleepResult is AppSuccess<void>,
activityTargetUploadSuccess: activityTargetResult == null ||
activityTargetResult is AppSuccess<void>,
);
}
Future<AppResult<AppleHealthLatestUploadRecordList>>
getLatestCommonUploadRecordList() {
return _api.getLatestCommonUploadRecordList();
}
Future<AppResult<AppleHealthLatestSleepUploadRecord>>
getLatestSleepUploadRecord() {
return _api.getLatestSleepUploadRecord();
}
}
class AppleHealthUploadResult {
const AppleHealthUploadResult({
required this.commonUploadSuccess,
required this.sleepUploadSuccess,
required this.activityTargetUploadSuccess,
});
final bool commonUploadSuccess;
final bool sleepUploadSuccess;
final bool activityTargetUploadSuccess;
bool get isSuccess =>
commonUploadSuccess && sleepUploadSuccess && activityTargetUploadSuccess;
}
... ...
import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart';
import 'models/apple_health_upload_sample.dart';
import 'models/health_upload_data_type.dart';
import 'models/upload_active_energy.dart';
import 'models/upload_exercise.dart';
import 'models/upload_heart_rate.dart';
import 'models/upload_hrv.dart';
import 'models/upload_irregular_heart_rhythm.dart';
import 'models/upload_oxygen_saturation.dart';
import 'models/upload_respiratory_rate.dart';
import 'models/upload_resting_heart_rate.dart';
import 'models/upload_sleep.dart';
import 'models/upload_sleeping_heart_rate.dart';
import 'models/upload_sleeping_wrist_temperature.dart';
import 'models/upload_stand.dart';
import 'models/upload_step_count.dart';
import 'models/upload_walking_heart_rate.dart';
extension HealthUploadDataPointMapper on HealthUploadDataPoint {
AppleHealthUploadSample? toAppleHealthUploadSample() {
switch (HealthUploadDataType.fromValue(dataType)) {
case HealthUploadDataType.hrv:
return HealthHrvUploadData(time: time, value: value);
case HealthUploadDataType.heartRate:
return HealthHeartRateUploadData(time: time, value: value);
case HealthUploadDataType.oxygenSaturation:
return HealthOxygenSaturationUploadData(time: time, value: value);
case HealthUploadDataType.activeEnergy:
return HealthActiveEnergyUploadData(time: time, value: value);
case HealthUploadDataType.exercise:
return HealthExerciseUploadData(time: time, value: value);
case HealthUploadDataType.stand:
return HealthStandUploadData(time: time, value: value);
case HealthUploadDataType.steps:
return HealthStepCountUploadData(time: time, value: value);
case HealthUploadDataType.walkingHeartRate:
return HealthWalkingHeartRateUploadData(time: time, value: value);
case HealthUploadDataType.restingHeartRate:
return HealthRestingHeartRateUploadData(time: time, value: value);
case HealthUploadDataType.sleepingHeartRate:
return HealthSleepingHeartRateUploadData(time: time, value: value);
case HealthUploadDataType.sleepingWristTemperature:
return HealthSleepingWristTemperatureUploadData(
time: time,
value: value,
);
case HealthUploadDataType.respiratoryRate:
return HealthRespiratoryRateUploadData(time: time, value: value);
case HealthUploadDataType.irregularHeartRhythm:
return HealthIrregularHeartRhythmUploadData(time: time, value: value);
case HealthUploadDataType.unknown:
case HealthUploadDataType.sleep:
return null;
}
}
}
extension HealthSleepUploadDataPointMapper on HealthSleepUploadDataPoint {
HealthSleepUploadData toHealthSleepUploadData() {
return HealthSleepUploadData(
dataType: dataType,
fromTime: fromTime,
toTime: toTime,
);
}
}
... ...
import 'health_upload_data_type.dart';
class AppleHealthLatestUploadRecord {
const AppleHealthLatestUploadRecord({
this.dataType,
this.latestDataTime,
});
final HealthUploadDataType? dataType;
final int? latestDataTime;
factory AppleHealthLatestUploadRecord.fromJson(Map<String, dynamic> json) {
final rawDataType = json['data_type'];
return AppleHealthLatestUploadRecord(
dataType: rawDataType is int
? HealthUploadDataType.fromValue(rawDataType)
: null,
latestDataTime: json['latest_data_time'] as int?,
);
}
}
class AppleHealthLatestUploadRecordList {
const AppleHealthLatestUploadRecordList({this.latestDataTimeList});
final List<AppleHealthLatestUploadRecord>? latestDataTimeList;
factory AppleHealthLatestUploadRecordList.fromJson(
Map<String, dynamic> json,
) {
return AppleHealthLatestUploadRecordList(
latestDataTimeList: (json['latest_data_time_list'] as List<dynamic>?)
?.map((item) => AppleHealthLatestUploadRecord.fromJson(
item as Map<String, dynamic>,
))
.toList(),
);
}
}
class AppleHealthLatestSleepUploadRecord {
const AppleHealthLatestSleepUploadRecord({this.latestDataTime});
final int? latestDataTime;
factory AppleHealthLatestSleepUploadRecord.fromJson(
Map<String, dynamic> json,
) {
return AppleHealthLatestSleepUploadRecord(
latestDataTime: json['latest_data_time'] as int?,
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'upload_sleep.dart';
class AppleHealthCommonUploadRequest {
const AppleHealthCommonUploadRequest({required this.dataList});
final List<AppleHealthUploadSample> dataList;
Map<String, dynamic> toJson() {
return {
'data_list': dataList.map((item) => item.toJson()).toList(),
};
}
}
class AppleHealthSleepUploadRequest {
const AppleHealthSleepUploadRequest({required this.dataList});
final List<HealthSleepUploadData> dataList;
Map<String, dynamic> toJson() {
return {
'data_list': dataList.map((item) => item.toJson()).toList(),
};
}
}
... ...
import 'health_upload_data_type.dart';
abstract class AppleHealthUploadSample {
const AppleHealthUploadSample({
required this.dataType,
required this.time,
required this.value,
});
final HealthUploadDataType dataType;
final int time;
final double value;
Map<String, dynamic> toJson() => {
'data_type': dataType.value,
'time': time,
'value': value,
};
}
int healthUploadTimeFromJson(Object? value) {
if (value is int) return value;
if (value is double) return value.round();
if (value is String) return int.tryParse(value) ?? 0;
return 0;
}
double healthUploadDoubleFromJson(Object? value) {
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0;
return 0;
}
... ...
enum HealthUploadDataType {
unknown(0),
hrv(1),
heartRate(2),
oxygenSaturation(3),
activeEnergy(4),
exercise(5),
stand(6),
steps(7),
walkingHeartRate(8),
restingHeartRate(9),
sleepingHeartRate(10),
sleepingWristTemperature(11),
respiratoryRate(12),
irregularHeartRhythm(13),
sleep(100);
const HealthUploadDataType(this.value);
final int value;
static HealthUploadDataType fromValue(int value) {
return HealthUploadDataType.values.firstWhere(
(type) => type.value == value,
orElse: () => HealthUploadDataType.unknown,
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthActiveEnergyUploadData extends AppleHealthUploadSample {
const HealthActiveEnergyUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.activeEnergy);
factory HealthActiveEnergyUploadData.fromJson(Map<String, dynamic> json) {
return HealthActiveEnergyUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
class HealthActivityTargetUploadData {
const HealthActivityTargetUploadData({
this.move,
this.stand,
});
final int? move;
final int? stand;
factory HealthActivityTargetUploadData.fromJson(Map<String, dynamic> json) {
return HealthActivityTargetUploadData(
move: json['move'] as int?,
stand: json['stand'] as int?,
);
}
Map<String, dynamic> toJson() {
return {
if (move != null) 'move': move,
if (stand != null) 'stand': stand,
};
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthExerciseUploadData extends AppleHealthUploadSample {
const HealthExerciseUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.exercise);
factory HealthExerciseUploadData.fromJson(Map<String, dynamic> json) {
return HealthExerciseUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthHeartRateUploadData extends AppleHealthUploadSample {
const HealthHeartRateUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.heartRate);
factory HealthHeartRateUploadData.fromJson(Map<String, dynamic> json) {
return HealthHeartRateUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthHrvUploadData extends AppleHealthUploadSample {
const HealthHrvUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.hrv);
factory HealthHrvUploadData.fromJson(Map<String, dynamic> json) {
return HealthHrvUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthIrregularHeartRhythmUploadData extends AppleHealthUploadSample {
const HealthIrregularHeartRhythmUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.irregularHeartRhythm);
factory HealthIrregularHeartRhythmUploadData.fromJson(
Map<String, dynamic> json,
) {
return HealthIrregularHeartRhythmUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthOxygenSaturationUploadData extends AppleHealthUploadSample {
const HealthOxygenSaturationUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.oxygenSaturation);
factory HealthOxygenSaturationUploadData.fromJson(
Map<String, dynamic> json,
) {
return HealthOxygenSaturationUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthRespiratoryRateUploadData extends AppleHealthUploadSample {
const HealthRespiratoryRateUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.respiratoryRate);
factory HealthRespiratoryRateUploadData.fromJson(Map<String, dynamic> json) {
return HealthRespiratoryRateUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthRestingHeartRateUploadData extends AppleHealthUploadSample {
const HealthRestingHeartRateUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.restingHeartRate);
factory HealthRestingHeartRateUploadData.fromJson(Map<String, dynamic> json) {
return HealthRestingHeartRateUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
class HealthSleepUploadData {
const HealthSleepUploadData({
required this.dataType,
required this.fromTime,
required this.toTime,
});
final int dataType;
final int fromTime;
final int toTime;
Map<String, dynamic> toJson() {
return {
'data_type': dataType,
'from_time': fromTime,
'to_time': toTime,
};
}
factory HealthSleepUploadData.fromJson(Map<String, dynamic> json) {
return HealthSleepUploadData(
dataType: healthUploadTimeFromJson(json['data_type']),
fromTime: healthUploadTimeFromJson(json['from_time']),
toTime: healthUploadTimeFromJson(json['to_time']),
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is HealthSleepUploadData &&
other.fromTime == fromTime &&
other.toTime == toTime &&
other.dataType == dataType;
}
@override
int get hashCode => Object.hash(dataType, fromTime, toTime);
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthSleepingHeartRateUploadData extends AppleHealthUploadSample {
const HealthSleepingHeartRateUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.sleepingHeartRate);
factory HealthSleepingHeartRateUploadData.fromJson(
Map<String, dynamic> json,
) {
return HealthSleepingHeartRateUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthSleepingWristTemperatureUploadData extends AppleHealthUploadSample {
const HealthSleepingWristTemperatureUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.sleepingWristTemperature);
factory HealthSleepingWristTemperatureUploadData.fromJson(
Map<String, dynamic> json,
) {
return HealthSleepingWristTemperatureUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthStandUploadData extends AppleHealthUploadSample {
const HealthStandUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.stand);
factory HealthStandUploadData.fromJson(Map<String, dynamic> json) {
return HealthStandUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthStepCountUploadData extends AppleHealthUploadSample {
const HealthStepCountUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.steps);
factory HealthStepCountUploadData.fromJson(Map<String, dynamic> json) {
return HealthStepCountUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'apple_health_upload_sample.dart';
import 'health_upload_data_type.dart';
class HealthWalkingHeartRateUploadData extends AppleHealthUploadSample {
const HealthWalkingHeartRateUploadData({
required super.time,
required super.value,
}) : super(dataType: HealthUploadDataType.walkingHeartRate);
factory HealthWalkingHeartRateUploadData.fromJson(Map<String, dynamic> json) {
return HealthWalkingHeartRateUploadData(
time: healthUploadTimeFromJson(json['time']),
value: healthUploadDoubleFromJson(json['value']),
);
}
}
... ...
import 'package:get/get.dart';
import '../apple_health_upload/apple_health_upload_api.dart';
import '../apple_health_upload/apple_health_upload_tool.dart';
import '../../core/config/app_environment_config.dart';
import '../../core/error/app_error_handler.dart';
import '../../core/network/api/config_api.dart';
... ... @@ -71,6 +73,11 @@ void registerUserSessionDeps() {
/// Health data API and upload pipeline (lazy, recreated after dispose).
void registerHealthDeps(DioClient dioClient) {
Get.lazyPut(() => HealthApi(dioClient), fenix: true);
Get.lazyPut(() => AppleHealthUploadApi(dioClient), fenix: true);
Get.lazyPut(
() => AppleHealthUploadTool(Get.find<AppleHealthUploadApi>()),
fenix: true,
);
Get.lazyPut(
() => HealthKitUploadService(Get.find<HealthApi>()),
fenix: true,
... ...
import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart';
import 'package:get/get.dart';
import '../controllers/apple_health_upload_test_controller.dart';
class AppleHealthUploadTestBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<AppleHealthUploadTestController>(
() => AppleHealthUploadTestController(
Get.find<AppleHealthUploadTool>(),
),
);
}
}
... ...
import 'dart:convert';
import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart';
import 'package:doublefeel_flutter/app/apple_health_upload/health_kit_upload_mapper.dart';
import 'package:doublefeel_flutter/app/apple_health_upload/models/apple_health_upload_sample.dart';
import 'package:doublefeel_flutter/app/apple_health_upload/models/upload_activity_target.dart';
import 'package:doublefeel_flutter/app/apple_health_upload/models/upload_sleep.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart';
import 'package:get/get.dart';
class AppleHealthUploadTestController extends GetxController {
AppleHealthUploadTestController(this._uploadTool);
final AppleHealthUploadTool _uploadTool;
final HealthKitHostApi _hostApi = HealthKitHostApi();
final logText = ''.obs;
final permissionTitle = '检测 AppleHealth 权限'.obs;
final isSyncing = false.obs;
final isUploading = false.obs;
final List<AppleHealthUploadSample> _commonData = [];
final List<HealthSleepUploadData> _sleepData = [];
HealthActivityTargetUploadData? _activityTarget;
Future<void> checkPermission() async {
_appendLog('开始检测 AppleHealth 权限');
try {
final hasPermission = await _hostApi.checkHealthAppAuthorization();
if (hasPermission) {
permissionTitle.value = '权限:已授权';
_appendLog('AppleHealth 权限已授权');
return;
}
final granted = await _hostApi.requestHealthClientAuthorization();
permissionTitle.value = granted ? '权限:已授权' : '权限:未授权';
_appendLog('AppleHealth 授权请求结果:$granted');
} catch (error, stackTrace) {
permissionTitle.value = '权限:检测失败';
_appendLog('权限检测失败:$error');
_appendLog(stackTrace.toString());
}
}
Future<void> syncHealthData() async {
if (isSyncing.value) return;
isSyncing.value = true;
_commonData.clear();
_sleepData.clear();
_activityTarget = null;
_appendLog('开始同步 HealthKitHostApi 数据');
final endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final startTime = DateTime.now()
.subtract(const Duration(days: 7))
.millisecondsSinceEpoch ~/
1000;
_appendLog('同步范围:$startTime -> $endTime');
try {
await _fetchCommon(
label: 'hrv',
fetch: () => _hostApi.fetchHrvData(startTime, endTime),
);
await _fetchCommon(
label: 'heartRate',
fetch: () => _hostApi.fetchHeartRateData(startTime, endTime),
);
await _fetchCommon(
label: 'walkingHeartRate',
fetch: () => _hostApi.fetchWalkingHeartRateData(startTime, endTime),
);
await _fetchCommon(
label: 'restingHeartRate',
fetch: () => _hostApi.fetchRestingHeartRateData(startTime, endTime),
);
await _fetchCommon(
label: 'sleepingHeartRate',
fetch: () => _hostApi.fetchSleepingHeartRateData(startTime, endTime),
);
await _fetchCommon(
label: 'oxygenSaturation',
fetch: () => _hostApi.fetchOxygenSaturationData(startTime, endTime),
);
await _fetchCommon(
label: 'activeEnergy',
fetch: () => _hostApi.fetchActiveEnergyData(startTime, endTime),
);
await _fetchCommon(
label: 'exercise',
fetch: () => _hostApi.fetchExerciseData(startTime, endTime),
);
await _fetchCommon(
label: 'stand',
fetch: () => _hostApi.fetchStandData(startTime, endTime),
);
await _fetchCommon(
label: 'stepCount',
fetch: () => _hostApi.fetchStepCountData(startTime, endTime),
);
await _fetchSleep(startTime, endTime);
await _fetchCommon(
label: 'sleepingWristTemperature',
fetch: () =>
_hostApi.fetchSleepingWristTemperatureData(startTime, endTime),
);
await _fetchCommon(
label: 'respiratoryRate',
fetch: () => _hostApi.fetchRespiratoryRateData(startTime, endTime),
);
await _fetchCommon(
label: 'irregularHeartRhythm',
fetch: () => _hostApi.fetchIrregularHeartRhythmData(startTime, endTime),
);
await _fetchActivityTarget(startTime, endTime);
_appendLog(
'同步完成:common=${_commonData.length}, sleep=${_sleepData.length}, '
'activityTarget=${_activityTarget == null ? 0 : 1}',
);
} catch (error, stackTrace) {
_appendLog('同步失败:$error');
_appendLog(stackTrace.toString());
} finally {
isSyncing.value = false;
}
}
Future<void> uploadHealthData() async {
if (isUploading.value) return;
if (_commonData.isEmpty && _sleepData.isEmpty && _activityTarget == null) {
_appendLog('没有缓存数据,先执行一次同步');
await syncHealthData();
}
isUploading.value = true;
_appendLog('开始上传到服务器');
try {
final result = await _uploadTool.upload(
commonData: _commonData,
sleepData: _sleepData,
activityTarget: _activityTarget,
);
_appendLog(
'上传结果:common=${result.commonUploadSuccess}, '
'sleep=${result.sleepUploadSuccess}, '
'activityTarget=${result.activityTargetUploadSuccess}, '
'success=${result.isSuccess}',
);
AppToast.show(result.isSuccess ? '上传完成' : '上传失败');
} catch (error, stackTrace) {
_appendLog('上传异常:$error');
_appendLog(stackTrace.toString());
AppToast.show('上传异常');
} finally {
isUploading.value = false;
}
}
Future<void> _fetchCommon({
required String label,
required Future<List<HealthUploadDataPoint>> Function() fetch,
}) async {
final points = await fetch();
final models = points
.map((point) => point.toAppleHealthUploadSample())
.whereType<AppleHealthUploadSample>()
.toList();
_commonData.addAll(models);
_appendDataLog(label, models.map((item) => item.toJson()).toList());
}
Future<void> _fetchSleep(int startTime, int endTime) async {
final points = await _hostApi.fetchSleepData(startTime, endTime);
final models =
points.map((item) => item.toHealthSleepUploadData()).toList();
_sleepData.addAll(models);
_appendDataLog('sleep', models.map((item) => item.toJson()).toList());
}
Future<void> _fetchActivityTarget(int startTime, int endTime) async {
final target = await _hostApi.fetchActivityTargetData(startTime, endTime);
if (target != null) {
_activityTarget = HealthActivityTargetUploadData(
move: target.move,
stand: target.stand,
);
}
_appendDataLog(
'activityTarget',
_activityTarget == null ? [] : [_activityTarget!.toJson()],
);
}
void _appendDataLog(String label, List<Map<String, dynamic>> data) {
_appendLog('[$label] count=${data.length}');
if (data.isNotEmpty) {
_appendLog('[$label] first=${jsonEncode(data.first)}');
_appendLog('[$label] last=${jsonEncode(data.last)}');
}
}
void _appendLog(String message) {
final time = DateTime.now().toIso8601String();
logText.value = '${logText.value}[$time] $message\n';
}
}
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/apple_health_upload_test_controller.dart';
class AppleHealthUploadTestView
extends GetView<AppleHealthUploadTestController> {
const AppleHealthUploadTestView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AppleHealth 上传测试'),
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: Container(
width: double.infinity,
margin: const EdgeInsets.all(12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF101418),
borderRadius: BorderRadius.circular(8),
),
child: Obx(
() => SingleChildScrollView(
reverse: true,
child: SelectableText(
controller.logText.value.isEmpty
? '点击底部按钮开始测试 HealthKitHostApiImpl 数据。'
: controller.logText.value,
style: const TextStyle(
color: Color(0xFFE6EDF3),
fontSize: 12,
height: 1.45,
fontFamily: 'Menlo',
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
child: Obx(
() => Row(
children: [
Expanded(
child: _BottomButton(
label: controller.permissionTitle.value,
onPressed: controller.checkPermission,
),
),
const SizedBox(width: 8),
Expanded(
child: _BottomButton(
label: controller.isSyncing.value ? '同步中' : '同步',
onPressed: controller.isSyncing.value
? null
: controller.syncHealthData,
),
),
const SizedBox(width: 8),
Expanded(
child: _BottomButton(
label: controller.isUploading.value ? '上传中' : '上传',
onPressed: controller.isUploading.value
? null
: controller.uploadHealthData,
),
),
],
),
),
),
],
),
),
);
}
}
class _BottomButton extends StatelessWidget {
const _BottomButton({
required this.label,
required this.onPressed,
});
final String label;
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 44,
child: FilledButton(
onPressed: onPressed,
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 13),
),
),
);
}
}
... ...
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/core/network/api/user_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
... ... @@ -29,4 +30,15 @@ class MyController extends GetxController {
}
}
}
void goWatchThemePage() {
Get.toNamed(
Routes.WATCH_THEME,
arguments: {'isPremium': false, 'hasCustomThemes': false},
);
}
void testAppleHealthUpload() {
Get.toNamed(Routes.APPLE_HEALTH_UPLOAD_TEST);
}
}
... ...
... ... @@ -12,7 +12,6 @@ import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
import 'package:doublefeel_flutter/data/models/user/user_models.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
... ... @@ -56,10 +55,7 @@ class MyTab extends GetView<MyController> {
: const _UnlockPremiumCard(),
const SizedBox(height: 12),
_WatchThemeCard(
onTap: () => Get.toNamed(
Routes.WATCH_THEME,
arguments: {'isPremium': false, 'hasCustomThemes': false},
),
onTap: () => controller.goWatchThemePage(),
),
const SizedBox(height: 12),
_SettingsRow(
... ... @@ -79,6 +75,13 @@ class MyTab extends GetView<MyController> {
Get.toNamed(Routes.ROUTE_LIST);
},
),
const SizedBox(height: 12),
_SettingsRow(
title: 'Apple Health Upload 测试',
onTap: () {
controller.testAppleHealthUpload();
},
),
],
],
);
... ...
... ... @@ -38,6 +38,8 @@ import '../modules/watch_theme/views/watch_theme_preview_view.dart';
import '../modules/watch_theme/views/watch_theme_view.dart';
import '../modules/account_settings/bindings/account_settings_binding.dart';
import '../modules/account_settings/views/account_settings_view.dart';
import '../modules/apple_health_upload_test/bindings/apple_health_upload_test_binding.dart';
import '../modules/apple_health_upload_test/views/apple_health_upload_test_view.dart';
import '../modules/webview/bindings/webview_binding.dart';
import '../modules/webview/views/webview_page.dart';
... ... @@ -170,5 +172,10 @@ abstract final class AppPages {
page: () => const AccountSettingsView(),
binding: AccountSettingsBinding(),
),
GetPage(
name: Routes.APPLE_HEALTH_UPLOAD_TEST,
page: () => const AppleHealthUploadTestView(),
binding: AppleHealthUploadTestBinding(),
),
];
}
... ...
... ... @@ -19,6 +19,7 @@ abstract class Routes {
static const WATCH_THEME_CUSTOM_PREVIEW = _Paths.WATCH_THEME_CUSTOM_PREVIEW;
static const ACCOUNT_SETTINGS = _Paths.ACCOUNT_SETTINGS;
static const FRIEND_TREND = _Paths.FRIEND_TREND;
static const APPLE_HEALTH_UPLOAD_TEST = _Paths.APPLE_HEALTH_UPLOAD_TEST;
}
abstract class _Paths {
... ... @@ -39,4 +40,5 @@ abstract class _Paths {
static const WATCH_THEME_CUSTOM_PREVIEW = '/watch-theme/custom-preview';
static const ACCOUNT_SETTINGS = '/account-settings';
static const FRIEND_TREND = '/friend-trend';
static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test';
}
... ...
... ... @@ -21,6 +21,8 @@ abstract final class ApiPaths {
'/client/doublefeel/health/data_upload/common/';
static const healthUploadSleep =
'/client/doublefeel/health/data_upload/sleep/';
static const healthActivityTarget =
'/client/doublefeel/health/activity_target/';
static const healthStatsSleep = '/client/doublefeel/health/statistics/sleep/';
static const healthStatsActivity =
'/client/doublefeel/health/statistics/activity/';
... ...
... ... @@ -15,21 +15,22 @@ PlatformException _createConnectionError(String channelName) {
message: 'Unable to establish connection on channel: "$channelName".',
);
}
bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) {
return a.length == b.length &&
a.indexed
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
}
if (a is Map && b is Map) {
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]));
return a.length == b.length &&
a.entries.every((MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]));
}
return a == b;
}
class HealthUploadResult {
HealthUploadResult({
required this.commonUploadSuccess,
... ... @@ -52,7 +53,8 @@ class HealthUploadResult {
}
Object encode() {
return _toList(); }
return _toList();
}
static HealthUploadResult decode(Object result) {
result as List<Object?>;
... ... @@ -77,10 +79,158 @@ class HealthUploadResult {
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList())
;
int get hashCode => Object.hashAll(_toList());
}
class HealthUploadDataPoint {
HealthUploadDataPoint({
required this.dataType,
required this.time,
required this.value,
});
int dataType;
int time;
double value;
List<Object?> _toList() {
return <Object?>[
dataType,
time,
value,
];
}
Object encode() {
return _toList();
}
static HealthUploadDataPoint decode(Object result) {
result as List<Object?>;
return HealthUploadDataPoint(
dataType: result[0]! as int,
time: result[1]! as int,
value: result[2]! as double,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! HealthUploadDataPoint || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
}
class HealthSleepUploadDataPoint {
HealthSleepUploadDataPoint({
required this.dataType,
required this.fromTime,
required this.toTime,
});
int dataType;
int fromTime;
int toTime;
List<Object?> _toList() {
return <Object?>[
dataType,
fromTime,
toTime,
];
}
Object encode() {
return _toList();
}
static HealthSleepUploadDataPoint decode(Object result) {
result as List<Object?>;
return HealthSleepUploadDataPoint(
dataType: result[0]! as int,
fromTime: result[1]! as int,
toTime: result[2]! as int,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! HealthSleepUploadDataPoint ||
other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
}
class HealthActivityTargetData {
HealthActivityTargetData({
this.move,
this.stand,
});
int? move;
int? stand;
List<Object?> _toList() {
return <Object?>[
move,
stand,
];
}
Object encode() {
return _toList();
}
static HealthActivityTargetData decode(Object result) {
result as List<Object?>;
return HealthActivityTargetData(
move: result[0] as int?,
stand: result[1] as int?,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! HealthActivityTargetData ||
other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
... ... @@ -89,9 +239,18 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is HealthUploadResult) {
} else if (value is HealthUploadResult) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is HealthUploadDataPoint) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is HealthSleepUploadDataPoint) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is HealthActivityTargetData) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
... ... @@ -100,8 +259,14 @@ class _PigeonCodec extends StandardMessageCodec {
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
case 129:
return HealthUploadResult.decode(readValue(buffer)!);
case 130:
return HealthUploadDataPoint.decode(readValue(buffer)!);
case 131:
return HealthSleepUploadDataPoint.decode(readValue(buffer)!);
case 132:
return HealthActivityTargetData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
... ... @@ -112,9 +277,11 @@ class HealthKitHostApi {
/// Constructor for [HealthKitHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
HealthKitHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
HealthKitHostApi(
{BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
pigeonVar_messageChannelSuffix =
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
... ... @@ -122,8 +289,10 @@ class HealthKitHostApi {
final String pigeonVar_messageChannelSuffix;
Future<bool> checkHealthAppAuthorization() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.checkHealthAppAuthorization$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -150,8 +319,10 @@ class HealthKitHostApi {
}
Future<String> getHealthServerAuthUrl() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.getHealthServerAuthUrl$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.getHealthServerAuthUrl$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -179,8 +350,10 @@ class HealthKitHostApi {
/// Opens Huawei Health client authorization UI. Returns whether user granted.
Future<bool> requestHealthClientAuthorization() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.requestHealthClientAuthorization$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.requestHealthClientAuthorization$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -207,8 +380,10 @@ class HealthKitHostApi {
}
Future<bool> cancelHealthAppAuthorization() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.cancelHealthAppAuthorization$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.cancelHealthAppAuthorization$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -236,8 +411,10 @@ class HealthKitHostApi {
/// Runs native health read and server upload pipeline.
Future<HealthUploadResult> performHealthUpload() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.performHealthUpload$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.performHealthUpload$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -262,4 +439,493 @@ class HealthKitHostApi {
return (pigeonVar_replyList[0] as HealthUploadResult?)!;
}
}
Future<List<HealthUploadDataPoint>> fetchHrvData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHrvData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchHeartRateData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchHeartRateData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchWalkingHeartRateData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchWalkingHeartRateData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchRestingHeartRateData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRestingHeartRateData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchSleepingHeartRateData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingHeartRateData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchOxygenSaturationData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchOxygenSaturationData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchActiveEnergyData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActiveEnergyData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchExerciseData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchExerciseData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchStandData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStandData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchStepCountData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchStepCountData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthSleepUploadDataPoint>> fetchSleepData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthSleepUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchSleepingWristTemperatureData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchSleepingWristTemperatureData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchRespiratoryRateData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchRespiratoryRateData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<List<HealthUploadDataPoint>> fetchIrregularHeartRhythmData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchIrregularHeartRhythmData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!
.cast<HealthUploadDataPoint>();
}
}
Future<HealthActivityTargetData?> fetchActivityTargetData(
int startTime, int endTime) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.HealthKitHostApi.fetchActivityTargetData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[startTime, endTime]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return (pigeonVar_replyList[0] as HealthActivityTargetData?);
}
}
}
... ...
... ... @@ -15,22 +15,21 @@ PlatformException _createConnectionError(String channelName) {
message: 'Unable to establish connection on channel: "$channelName".',
);
}
bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) {
return a.length == b.length &&
a.indexed
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
}
if (a is Map && b is Map) {
return a.length == b.length &&
a.entries.every((MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]));
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]));
}
return a == b;
}
class WearDeviceInfo {
WearDeviceInfo({
this.deviceId,
... ... @@ -53,8 +52,7 @@ class WearDeviceInfo {
}
Object encode() {
return _toList();
}
return _toList(); }
static WearDeviceInfo decode(Object result) {
result as List<Object?>;
... ... @@ -79,9 +77,11 @@ class WearDeviceInfo {
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
int get hashCode => Object.hashAll(_toList())
;
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
... ... @@ -89,7 +89,7 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is WearDeviceInfo) {
} else if (value is WearDeviceInfo) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else {
... ... @@ -100,7 +100,7 @@ class _PigeonCodec extends StandardMessageCodec {
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
case 129:
return WearDeviceInfo.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
... ... @@ -112,11 +112,9 @@ class WearEngineHostApi {
/// Constructor for [WearEngineHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
WearEngineHostApi(
{BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
WearEngineHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix =
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
... ... @@ -124,10 +122,8 @@ class WearEngineHostApi {
final String pigeonVar_messageChannelSuffix;
Future<bool> hasAvailableDevices() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.hasAvailableDevices$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.hasAvailableDevices$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -154,10 +150,8 @@ class WearEngineHostApi {
}
Future<WearDeviceInfo?> checkConnectedDevice() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.checkConnectedDevice$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.checkConnectedDevice$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -179,10 +173,8 @@ class WearEngineHostApi {
}
Future<bool> registerMessageReceiver() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.registerMessageReceiver$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.registerMessageReceiver$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ... @@ -209,16 +201,13 @@ class WearEngineHostApi {
}
Future<bool> sendTextMessage(String message) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendTextMessage$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendTextMessage$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[message]);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[message]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
... ... @@ -240,16 +229,13 @@ class WearEngineHostApi {
}
Future<bool> sendWatchSyncPayload(String jsonPayload) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.sendWatchSyncPayload$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[jsonPayload]);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[jsonPayload]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
... ... @@ -270,11 +256,11 @@ class WearEngineHostApi {
}
}
/// Opens the system photo picker and returns a local PNG file path after
/// removing the image background on the host platform.
Future<String?> pickImageAndRemoveBackground() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.WearEngineHostApi.pickImageAndRemoveBackground$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
... ...
... ... @@ -12,6 +12,40 @@ class HealthUploadResult {
String? errorMessage;
}
class HealthUploadDataPoint {
HealthUploadDataPoint({
required this.dataType,
required this.time,
required this.value,
});
int dataType;
int time;
double value;
}
class HealthSleepUploadDataPoint {
HealthSleepUploadDataPoint({
required this.dataType,
required this.fromTime,
required this.toTime,
});
int dataType;
int fromTime;
int toTime;
}
class HealthActivityTargetData {
HealthActivityTargetData({
this.move,
this.stand,
});
int? move;
int? stand;
}
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/pigeon/health_kit_api.g.dart',
... ... @@ -41,4 +75,61 @@ abstract class HealthKitHostApi {
/// Runs native health read and server upload pipeline.
HealthUploadResult performHealthUpload();
List<HealthUploadDataPoint> fetchHrvData(int startTime, int endTime);
List<HealthUploadDataPoint> fetchHeartRateData(int startTime, int endTime);
List<HealthUploadDataPoint> fetchWalkingHeartRateData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchRestingHeartRateData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchSleepingHeartRateData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchOxygenSaturationData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchActiveEnergyData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchExerciseData(int startTime, int endTime);
List<HealthUploadDataPoint> fetchStandData(int startTime, int endTime);
List<HealthUploadDataPoint> fetchStepCountData(int startTime, int endTime);
List<HealthSleepUploadDataPoint> fetchSleepData(int startTime, int endTime);
List<HealthUploadDataPoint> fetchSleepingWristTemperatureData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchRespiratoryRateData(
int startTime,
int endTime,
);
List<HealthUploadDataPoint> fetchIrregularHeartRhythmData(
int startTime,
int endTime,
);
HealthActivityTargetData? fetchActivityTargetData(
int startTime,
int endTime,
);
}
... ...