feat(workout): 新增锻炼结束监听和个性化通知功能

实现了iOS HealthKit锻炼数据实时监听,当用户完成锻炼时自动发送个性化鼓励通知。包括锻炼类型筛选、时间范围控制、用户偏好设置等完整功能,并提供了测试工具和详细文档。
This commit is contained in:
richarjiang
2025-10-13 10:05:02 +08:00
parent 12883c5410
commit 971aebd560
18 changed files with 2210 additions and 1264 deletions

View File

@@ -10,17 +10,14 @@ import React
import HealthKit
@objc(HealthKitManager)
class HealthKitManager: NSObject, RCTBridgeModule {
class HealthKitManager: RCTEventEmitter {
private let healthStore = HKHealthStore()
static func moduleName() -> String! {
override static func moduleName() -> String! {
return "HealthKitManager"
}
static func requiresMainQueueSetup() -> Bool {
return true
}
// MARK: - Types We Care About
@@ -1703,4 +1700,95 @@ class HealthKitManager: NSObject, RCTBridgeModule {
return description.lowercased()
}
// MARK: - Workout Observer Methods
private var workoutObserverQuery: HKObserverQuery?
@objc
func startWorkoutObserver(
_ resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard HKHealthStore.isHealthDataAvailable() else {
rejecter("HEALTHKIT_NOT_AVAILABLE", "HealthKit is not available on this device", nil)
return
}
//
if let existingQuery = workoutObserverQuery {
healthStore.stop(existingQuery)
workoutObserverQuery = nil
}
//
let workoutType = ReadTypes.workoutType
workoutObserverQuery = HKObserverQuery(sampleType: workoutType, predicate: nil) { [weak self] (query, completionHandler, error) in
if let error = error {
print("Workout observer error: \(error.localizedDescription)")
completionHandler()
return
}
print("Workout data updated, sending event to React Native")
// React Native
self?.sendWorkoutUpdateEvent()
completionHandler()
}
//
healthStore.enableBackgroundDelivery(for: workoutType, frequency: .immediate) { (success, error) in
if let error = error {
print("Failed to enable background delivery for workouts: \(error.localizedDescription)")
} else {
print("Background delivery for workouts enabled successfully")
}
}
//
healthStore.execute(workoutObserverQuery!)
resolver(["success": true])
}
@objc
func stopWorkoutObserver(
_ resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
if let query = workoutObserverQuery {
healthStore.stop(query)
workoutObserverQuery = nil
//
healthStore.disableBackgroundDelivery(for: ReadTypes.workoutType) { (success, error) in
if let error = error {
print("Failed to disable background delivery for workouts: \(error.localizedDescription)")
}
}
resolver(["success": true])
} else {
resolver(["success": true]) // 使
}
}
private func sendWorkoutUpdateEvent() {
// 使 RCTEventEmitter
sendEvent(withName: "workoutUpdate", body: [
"timestamp": Date().timeIntervalSince1970,
"type": "workout_completed"
])
}
// MARK: - RCTEventEmitter Overrides
override func supportedEvents() -> [String]! {
return ["workoutUpdate"]
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
} // end class