feat(workout): 新增锻炼结束监听和个性化通知功能
实现了iOS HealthKit锻炼数据实时监听,当用户完成锻炼时自动发送个性化鼓励通知。包括锻炼类型筛选、时间范围控制、用户偏好设置等完整功能,并提供了测试工具和详细文档。
This commit is contained in:
@@ -82,4 +82,11 @@ RCT_EXTERN_METHOD(getRecentWorkouts:(NSDictionary *)options
|
||||
resolver:(RCTPromiseResolveBlock)resolver
|
||||
rejecter:(RCTPromiseRejectBlock)rejecter)
|
||||
|
||||
// Workout Observer Methods
|
||||
RCT_EXTERN_METHOD(startWorkoutObserver:(RCTPromiseResolveBlock)resolver
|
||||
rejecter:(RCTPromiseRejectBlock)rejecter)
|
||||
|
||||
RCT_EXTERN_METHOD(stopWorkoutObserver:(RCTPromiseResolveBlock)resolver
|
||||
rejecter:(RCTPromiseRejectBlock)rejecter)
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>processing</string>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
|
||||
Reference in New Issue
Block a user