|
...
|
...
|
@@ -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")
|
...
|
...
|
|