WatchHealthWidgetRefreshCoordinator.swift 3.68 KB
import Foundation
import HealthKit

/// Bridges HealthKit observer events to the three HealthKit-backed widgets.
/// This path is intentionally independent of login state and server uploads.
@MainActor
final class WatchHealthWidgetRefreshCoordinator {
    static let shared = WatchHealthWidgetRefreshCoordinator()

    private let healthStore = HKHealthStore()
    private let defaultStepGoal = 10_000

    private init() {}

    func refresh(for dataType: HealthDataType) {
        switch dataType {
        case .heartRate:
            WatchDataManager.share.refreshHeartRateWidgetData()
            WatchDataManager.share.getMyLatestData()
        case .steps:
            WatchDataManager.share.refreshStepWidgetData()
            refreshLocalActivityTarget()
        case .move, .exercise, .stand:
            WatchDataManager.share.refreshActivityWidgetData()
            refreshLocalActivityTarget()
        default:
            break
        }
    }

    func refreshAll() {
        WatchDataManager.share.getMyLatestData()
        WatchDataManager.share.refreshHeartRateWidgetData()
        WatchDataManager.share.refreshStepWidgetData()
        WatchDataManager.share.refreshActivityWidgetData()
        refreshLocalActivityTarget()
    }

    func refreshActivityTargetData() {
        refreshLocalActivityTarget()
    }

    private func refreshLocalActivityTarget() {
        let defaultStepGoal = defaultStepGoal
        let calendar = Calendar.current
        var start = calendar.dateComponents([.era, .year, .month, .day], from: Date())
        var end = start
        start.calendar = calendar
        end.calendar = calendar

        let predicate = HKQuery.predicate(forActivitySummariesBetweenStart: start, end: end)
        let query = HKActivitySummaryQuery(predicate: predicate) { _, summaries, error in
            guard error == nil else { return }
            guard let summary = summaries?.last else {
                AppGroupConstants.defaults?.removeObject(
                    forKey: AppGroupConstants.Key.localHealthActivityTarget
                )
                WatchWidgetReloader.reloadActivityTarget()
                return
            }

            let target = ActivityTarget(
                id: nil,
                create_time: nil,
                update_time: nil,
                user_id: nil,
                move: Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())),
                step: defaultStepGoal,
                stand: Int(summary.appleStandHoursGoal.doubleValue(for: .count())),
                exercise: Int(summary.appleExerciseTimeGoal.doubleValue(for: .minute())),
                activity_move_mode: summary.activityMoveMode.rawValue,
                active_energy_burned: summary.activeEnergyBurned.doubleValue(for: .kilocalorie()),
                active_energy_burned_goal: summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie()),
                apple_move_time: summary.appleMoveTime.doubleValue(for: .minute()),
                apple_move_time_goal: summary.appleMoveTimeGoal.doubleValue(for: .minute()),
                apple_exercise_time: summary.appleExerciseTime.doubleValue(for: .minute()),
                exercise_time_goal: summary.exerciseTimeGoal?.doubleValue(for: .minute()),
                apple_stand_hours: summary.appleStandHours.doubleValue(for: .count()),
                stand_hours_goal: summary.standHoursGoal?.doubleValue(for: .count())
            )

            guard let data = try? JSONEncoder().encode(target) else { return }
            AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.localHealthActivityTarget)
            WatchWidgetReloader.reloadActivityTarget()
        }
        healthStore.execute(query)
    }
}