Commit bbe2b7ca48642113cc8044ac799ad35d9fbcd869

Authored by 权海
1 parent 73fe2db2

feat(ui):切换到v2上传apple health数据

... ... @@ -41,13 +41,7 @@ final class HealthDataReader {
startDate: startDate,
endDate: endDate
)
async let stand = fetchDailyCumulativeSamples(
identifier: .appleStandTime,
dataType: .stand,
unit: .second(),
startDate: startDate,
endDate: endDate
)
async let stand = fetchStandData(startDate: startDate, endDate: endDate)
async let steps = fetchDailyCumulativeSamples(
identifier: .stepCount,
dataType: .steps,
... ... @@ -225,14 +219,7 @@ final class HealthDataReader {
}
func fetchStandData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
try await fetchDailyCumulativeSamples(identifier: .appleStandTime, dataType: .stand, unit: .second(), startDate: startDate, endDate: endDate)
// try await fetchQuantitySamples(
// identifier: .appleStandTime,
// dataType: .stand,
// unit: .minute(),
// startDate: startDate,
// endDate: endDate
// )
try await fetchDailyStandHours(startDate: startDate, endDate: endDate)
}
func fetchStepCountData(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
... ... @@ -296,7 +283,16 @@ final class HealthDataReader {
continuation.resume(
returning: NativeActivityTarget(
move: Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())),
stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count()))
stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count())),
activityMoveMode: Self.activityMoveModeValue(from: summary),
activeEnergyBurned: summary.activeEnergyBurned.doubleValue(for: .kilocalorie()),
activeEnergyBurnedGoal: summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie()),
appleMoveTime: Self.appleMoveTimeValue(from: summary),
appleMoveTimeGoal: Self.appleMoveTimeGoalValue(from: summary),
appleExerciseTime: summary.appleExerciseTime.doubleValue(for: .minute()),
exerciseTimeGoal: Self.exerciseTimeGoalValue(from: summary),
appleStandHours: summary.appleStandHours.doubleValue(for: .count()),
standHoursGoal: Self.standHoursGoalValue(from: summary)
)
)
}
... ... @@ -304,6 +300,41 @@ final class HealthDataReader {
}
}
private static func activityMoveModeValue(from summary: HKActivitySummary) -> Int? {
if #available(iOS 14.0, *) {
return summary.activityMoveMode.rawValue
}
return nil
}
private static func appleMoveTimeValue(from summary: HKActivitySummary) -> Double? {
if #available(iOS 14.0, *) {
return summary.appleMoveTime.doubleValue(for: .minute())
}
return nil
}
private static func appleMoveTimeGoalValue(from summary: HKActivitySummary) -> Double? {
if #available(iOS 14.0, *) {
return summary.appleMoveTimeGoal.doubleValue(for: .minute())
}
return nil
}
private static func exerciseTimeGoalValue(from summary: HKActivitySummary) -> Double? {
if #available(iOS 16.0, *) {
return summary.exerciseTimeGoal?.doubleValue(for: .minute())
}
return nil
}
private static func standHoursGoalValue(from summary: HKActivitySummary) -> Double? {
if #available(iOS 16.0, *) {
return summary.standHoursGoal?.doubleValue(for: .count())
}
return nil
}
private func fetchHeartRateFamily(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
async let heartRate = fetchQuantitySamples(
identifier: .heartRate,
... ... @@ -416,6 +447,41 @@ final class HealthDataReader {
}
}
private func fetchDailyStandHours(startDate: Date, endDate: Date) async throws -> [NativeHealthDataPoint] {
let calendar = Calendar.current
var start = calendar.dateComponents([.era, .year, .month, .day], from: startDate)
var end = calendar.dateComponents([.era, .year, .month, .day], from: endDate)
start.calendar = calendar
end.calendar = calendar
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
}
let points = (summaries ?? [])
.compactMap { summary -> NativeHealthDataPoint? in
guard let date = calendar.date(from: summary.dateComponents(for: calendar)) else {
return nil
}
return NativeHealthDataPoint(
dataType: .stand,
time: date.timeIntervalSince1970,
value: summary.appleStandHours.doubleValue(for: .count())
)
}
.sorted { $0.time < $1.time }
continuation.resume(returning: points)
}
healthStore.execute(query)
}
}
private func fetchSleepIntervals(startDate: Date, endDate: Date) async throws -> [NativeSleepInterval] {
guard let type = NativeHealthTypeCatalog.category(.sleepAnalysis) else {
throw NativeHealthKitError.invalidType("sleepAnalysis")
... ...
... ... @@ -52,6 +52,63 @@ struct NativeSleepInterval: Codable {
struct NativeActivityTarget {
let move: Int?
let stand: Int?
let activityMoveMode: Int?
// 千卡
let activeEnergyBurned: Double?
let activeEnergyBurnedGoal: Double?
// 公里
let appleMoveTime: Double?
let appleMoveTimeGoal: Double?
// 分钟
let appleExerciseTime: Double?
let exerciseTimeGoal: Double?
//小时
let appleStandHours: Double?
let standHoursGoal: Double?
}
extension NativeActivityTarget {
var uploadBody: [String: Any] {
var body: [String: Any] = [:]
for child in Mirror(reflecting: self).children {
guard let label = child.label, let value = Self.unwrapOptional(child.value) else {
continue
}
body[label.camelCaseToSnakeCase] = value
}
return body
}
var uploadBodyForDebug: String {
let pairs = uploadBody
.map { "\($0.key)=\($0.value)" }
.sorted()
.joined(separator: ", ")
return "{\(pairs)}"
}
private static func unwrapOptional(_ value: Any) -> Any? {
let mirror = Mirror(reflecting: value)
guard mirror.displayStyle == .optional else {
return value
}
return mirror.children.first?.value
}
}
private extension String {
var camelCaseToSnakeCase: String {
unicodeScalars.reduce(into: "") { result, scalar in
if CharacterSet.uppercaseLetters.contains(scalar) {
if !result.isEmpty {
result.append("_")
}
result.append(String(scalar).lowercased())
} else {
result.append(String(scalar))
}
}
}
}
enum NativeSleepStage {
... ...
... ... @@ -269,7 +269,7 @@ private extension NativeHealthDataUploader {
debugLog("[activityTarget] no target data")
return UploadTaskResult(success: true, uploadedCount: 0, errorMessage: nil)
}
debugLog("[activityTarget] uploading, move=\(target.move.map(String.init) ?? "<nil>"), stand=\(target.stand.map(String.init) ?? "<nil>")")
debugLog("[activityTarget] uploading, body=\(target.uploadBodyForDebug)")
try await uploadActivityTarget(target)
debugLog("[activityTarget] upload success")
return UploadTaskResult(success: true, uploadedCount: 1, errorMessage: nil)
... ... @@ -338,7 +338,7 @@ private extension NativeHealthDataUploader {
"value": $0.value,
] as [String: Any]
}
try await request(path: "/client/doublefeel/health/data_upload/common/", method: "POST", body: ["data_list": list])
try await request(path: "/client/doublefeel/health/v2/data_upload/common/", method: "POST", body: ["data_list": list])
}
func uploadSleep(_ data: [NativeSleepInterval]) async throws {
... ... @@ -349,18 +349,15 @@ private extension NativeHealthDataUploader {
"to_time": $0.toTime,
] as [String: Any]
}
try await request(path: "/client/doublefeel/health/data_upload/sleep/", method: "POST", body: ["data_list": list])
try await request(path: "/client/doublefeel/health/v2/data_upload/sleep/", method: "POST", body: ["data_list": list])
}
func uploadActivityTarget(_ target: NativeActivityTarget) async throws {
var body: [String: Any] = [:]
body["move"] = target.move
body["stand"] = target.stand
try await request(path: "/client/doublefeel/health/activity_target/", method: "POST", body: body)
try await request(path: "/client/doublefeel/health/v2/activity_target/", method: "POST", body: target.uploadBody)
}
func fetchLastUploadTime() async throws -> NativeHealthUploadTimeList {
let data = try await request(path: "/client/doublefeel/health/data_upload/common/", method: "GET")
let data = try await request(path: "/client/doublefeel/health/v2/data_upload/common/", method: "GET")
return try NativeHealthUploadTimeList.decode(from: data)
}
... ...
... ... @@ -215,22 +215,66 @@ struct HealthSleepUploadDataPoint: Hashable {
struct HealthActivityTargetData: Hashable {
var move: Int64? = nil
var stand: Int64? = nil
var activityMoveMode: Int64? = nil
var activeEnergyBurned: Double? = nil
var activeEnergyBurnedGoal: Double? = nil
var appleMoveTime: Double? = nil
var appleMoveTimeGoal: Double? = nil
var appleExerciseTime: Double? = nil
var appleExerciseTimeGoal: Double? = nil
var exerciseTimeGoal: Double? = nil
var appleStandHours: Double? = nil
var appleStandHoursGoal: Double? = nil
var standHoursGoal: Double? = 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])
let activityMoveMode: Int64? = nilOrValue(pigeonVar_list[2])
let activeEnergyBurned: Double? = nilOrValue(pigeonVar_list[3])
let activeEnergyBurnedGoal: Double? = nilOrValue(pigeonVar_list[4])
let appleMoveTime: Double? = nilOrValue(pigeonVar_list[5])
let appleMoveTimeGoal: Double? = nilOrValue(pigeonVar_list[6])
let appleExerciseTime: Double? = nilOrValue(pigeonVar_list[7])
let appleExerciseTimeGoal: Double? = nilOrValue(pigeonVar_list[8])
let exerciseTimeGoal: Double? = nilOrValue(pigeonVar_list[9])
let appleStandHours: Double? = nilOrValue(pigeonVar_list[10])
let appleStandHoursGoal: Double? = nilOrValue(pigeonVar_list[11])
let standHoursGoal: Double? = nilOrValue(pigeonVar_list[12])
return HealthActivityTargetData(
move: move,
stand: stand
stand: stand,
activityMoveMode: activityMoveMode,
activeEnergyBurned: activeEnergyBurned,
activeEnergyBurnedGoal: activeEnergyBurnedGoal,
appleMoveTime: appleMoveTime,
appleMoveTimeGoal: appleMoveTimeGoal,
appleExerciseTime: appleExerciseTime,
appleExerciseTimeGoal: appleExerciseTimeGoal,
exerciseTimeGoal: exerciseTimeGoal,
appleStandHours: appleStandHours,
appleStandHoursGoal: appleStandHoursGoal,
standHoursGoal: standHoursGoal
)
}
func toList() -> [Any?] {
return [
move,
stand,
activityMoveMode,
activeEnergyBurned,
activeEnergyBurnedGoal,
appleMoveTime,
appleMoveTimeGoal,
appleExerciseTime,
appleExerciseTimeGoal,
exerciseTimeGoal,
appleStandHours,
appleStandHoursGoal,
standHoursGoal,
]
}
static func == (lhs: HealthActivityTargetData, rhs: HealthActivityTargetData) -> Bool {
... ...
... ... @@ -161,7 +161,16 @@ final class HealthKitHostApiImpl: HealthKitHostApi {
completion(.success(target.map {
HealthActivityTargetData(
move: $0.move.map(Int64.init),
stand: $0.stand.map(Int64.init)
stand: $0.stand.map(Int64.init),
activityMoveMode: $0.activityMoveMode.map(Int64.init),
activeEnergyBurned: $0.activeEnergyBurned,
activeEnergyBurnedGoal: $0.activeEnergyBurnedGoal,
appleMoveTime: $0.appleMoveTime,
appleMoveTimeGoal: $0.appleMoveTimeGoal,
appleExerciseTime: $0.appleExerciseTime,
exerciseTimeGoal: $0.exerciseTimeGoal,
appleStandHours: $0.appleStandHours,
standHoursGoal: $0.standHoursGoal
)
}))
} catch {
... ...