HealthKitRawDataHostApiImpl.swift 7.22 KB
import Foundation

final class HealthKitRawDataHostApiImpl: HealthKitRawDataHostApi {
  private let service: HealthKitService

  init(service: HealthKitService = .shared) {
    self.service = service
  }
    
  func hasHealthData(completion: @escaping (Result<Bool, Error>) -> Void) {
    Task {
      let hasData = await service.hasAnyReadableData()
      completion(.success(hasData))
    }
  }
    
    func performHealthDataUpload(completion: @escaping (Result<Bool, any Error>) -> Void) {
        print("trigger HealthDataUpload")
        Task {
          let summary = await AnchoredHealthDataUploader.shared.uploadAll()
          completion(.success(true))
        }
    }
    
    func performHRDataUpload(sqliteFilePath: String, completion: @escaping (Result<Int64, any Error>) -> Void) {
        print("trigger HRDataUpload")
        Task {
            do {
                let uploadedUntil = try await HealthRawStressSQLiteUploader.shared.uploadRealtimeStress(sqliteFilePath: sqliteFilePath)
                completion(.success(uploadedUntil))
            } catch {
                completion(.failure(error))
            }
        }
    }
    
    func performHRVDataUpload(sqliteFilePath: String, completion: @escaping (Result<Int64, any Error>) -> Void) {
        print("trigger HRVDataUpload")
        Task {
            do {
                let uploadedUntil = try await HealthRawStressSQLiteUploader.shared.uploadHrv(sqliteFilePath: sqliteFilePath)
                completion(.success(uploadedUntil))
            } catch {
                completion(.failure(error))
            }
        }
    }
    
    func performAvgRealtimeStressDataUpload(sqliteFilePath: String, completion: @escaping (Result<Bool, any Error>) -> Void) {
        print("trigger AvgRealtimeStressDataUpload")
        Task {
            do {
                let success = try await HealthRawStressSQLiteUploader.shared.uploadDailyStress(sqliteFilePath: sqliteFilePath)
                completion(.success(success))
            } catch {
                completion(.failure(error))
            }
        }
    }
    

  func getHealthKitRawData(
    dataType: Int64,
    startTime: Int64,
    endTime: Int64,
    completion: @escaping (Result<[HealthKitRawDataPoint], Error>) -> Void
  ) {
    guard let nativeDataType = NativeHealthDataType(rawValue: Int(dataType)) else {
      completion(.failure(PigeonError(
        code: "invalid_data_type",
        message: "无效的健康数据类型:\(dataType)",
        details: nil
      )))
      return
    }
    guard nativeDataType != .sleep else {
      completion(.failure(PigeonError(
        code: "unsupported_data_type",
        message: "睡眠数据请使用 getHealthKitRawSleepData 读取",
        details: nil
      )))
      return
    }

    let startDate = Date(timeIntervalSince1970: TimeInterval(startTime))
    let endDate = Date(timeIntervalSince1970: TimeInterval(endTime))

    Task {
      do {
        let points = try await service.fetchRawData(
          for: nativeDataType,
          startDate: startDate,
          endDate: endDate
        )
        completion(.success(points.map { Self.rawDataPoint(from: $0, dataType: nativeDataType) }))
      } catch {
        completion(.failure(error))
      }
    }
  }

  func getHealthKitRawSleepData(
    startTime: Int64,
    endTime: Int64,
    completion: @escaping (Result<[HealthKitRawSleepDataPoint], Error>) -> Void
  ) {
    let startDate = Date(timeIntervalSince1970: TimeInterval(startTime))
    let endDate = Date(timeIntervalSince1970: TimeInterval(endTime))
      print("trigger getHealthKitRawSleepData from: \(startDate) to \(endDate)")
    Task {
      do {
        let intervals = try await service.fetchSleepData(
          startDate: startDate,
          endDate: endDate
        )
        let points = intervals.map(Self.sleepDataPoint)
        guard !points.isEmpty else {
          completion(.success([]))
          return
        }
          print("trigger getHealthKitRawSleepData back: \(points)")
        completion(.success([
          HealthKitRawSleepDataPoint(
            dataType: Int64(NativeHealthDataType.sleep.rawValue),
            sleepDataPoints: points
          )
        ]))
      } catch {
        completion(.failure(error))
      }
    }
  }

  func getHealthKitRawActivityData(
    startTime: Int64,
    endTime: Int64,
    completion: @escaping (Result<[HealthKitRawActivityDataPoint], Error>) -> Void
  ) {
    let startDate = Date(timeIntervalSince1970: TimeInterval(startTime))
    let endDate = Date(timeIntervalSince1970: TimeInterval(endTime))

    Task {
      do {
        let targets = try await service.fetchActivityTargetDataList(
          startDate: startDate,
          endDate: endDate
        )
        completion(.success(targets.compactMap(Self.activityDataPoint)))
      } catch {
        completion(.failure(error))
      }
    }
  }
    
  func getHealthKitRawWorkoutData(
    startTime: Int64,
    endTime: Int64,
    completion: @escaping (Result<[HealthKitRawWorkoutDataPoint], Error>) -> Void
  ) {
    let startDate = Date(timeIntervalSince1970: TimeInterval(startTime))
    let endDate = Date(timeIntervalSince1970: TimeInterval(endTime))

    Task {
      do {
        let workouts = try await service.fetchWorkoutDataList(
          startDate: startDate,
          endDate: endDate
        )
        completion(.success(workouts.map(Self.workoutDataPoint)))
      } catch {
        completion(.failure(error))
      }
    }
  }

  func performSleepDataUpload(completion: @escaping (Result<Bool, Error>) -> Void) {
    Task {
      let summary = await AnchoredHealthDataUploader.shared.uploadAll()
      completion(.success(summary.sleepUploadSuccess))
    }
  }

  private static func rawDataPoint(
    from point: NativeHealthRawDataPoint,
    dataType: NativeHealthDataType
  ) -> HealthKitRawDataPoint {
    HealthKitRawDataPoint(
      dataType: Int64(dataType.rawValue),
      startTime: Int64(point.startTime.rounded()),
      endTime: Int64(point.endTime.rounded()),
      value: point.value
    )
  }

  private static func sleepDataPoint(from interval: NativeSleepInterval) -> HealthKitRawDataPoint {
    HealthKitRawDataPoint(
      dataType: Int64(interval.dataType),
      startTime: Int64(interval.fromTime.rounded()),
      endTime: Int64(interval.toTime.rounded()),
      value: nil
    )
  }

  private static func activityDataPoint(from target: NativeActivityTarget) -> HealthKitRawActivityDataPoint? {
    guard let endTime = target.healthValueTimestamp else {
      return nil
    }
    return HealthKitRawActivityDataPoint(
      endTime: Int64(endTime.rounded()),
      activeEnergyBurned: target.activeEnergyBurned,
      activeEnergyBurnedGoal: target.activeEnergyBurnedGoal,
      appleMoveTime: target.appleMoveTime,
      appleMoveTimeGoal: target.appleMoveTimeGoal,
      appleExerciseTime: target.appleExerciseTime,
      exerciseTimeGoal: target.exerciseTimeGoal,
      appleStandHours: target.appleStandHours,
      standHoursGoal: target.standHoursGoal
    )
  }

  private static func workoutDataPoint(from workout: NativeWorkoutInterval) -> HealthKitRawWorkoutDataPoint {
    HealthKitRawWorkoutDataPoint(
      workoutType: Int64(workout.workoutType),
      startTime: Int64(workout.startTime.rounded()),
      endTime: Int64(workout.endTime.rounded())
    )
  }
}