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>
|
||||
|
||||
@@ -6,9 +6,9 @@ PODS:
|
||||
- EXImageLoader (6.0.0):
|
||||
- ExpoModulesCore
|
||||
- React-Core
|
||||
- EXNotifications (0.32.11):
|
||||
- EXNotifications (0.32.12):
|
||||
- ExpoModulesCore
|
||||
- Expo (54.0.10):
|
||||
- Expo (54.0.13):
|
||||
- ExpoModulesCore
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
@@ -45,18 +45,18 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ZXingObjC/OneD
|
||||
- ZXingObjC/PDF417
|
||||
- ExpoFileSystem (19.0.15):
|
||||
- ExpoFileSystem (19.0.17):
|
||||
- ExpoModulesCore
|
||||
- ExpoFont (14.0.8):
|
||||
- ExpoFont (14.0.9):
|
||||
- ExpoModulesCore
|
||||
- ExpoGlassEffect (0.1.4):
|
||||
- ExpoModulesCore
|
||||
- ExpoHaptics (15.0.7):
|
||||
- ExpoModulesCore
|
||||
- ExpoHead (6.0.8):
|
||||
- ExpoHead (6.0.12):
|
||||
- ExpoModulesCore
|
||||
- RNScreens
|
||||
- ExpoImage (3.0.8):
|
||||
- ExpoImage (3.0.9):
|
||||
- ExpoModulesCore
|
||||
- libavif/libdav1d
|
||||
- SDWebImage (~> 5.21.0)
|
||||
@@ -71,7 +71,7 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoLinking (8.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (3.0.18):
|
||||
- ExpoModulesCore (3.0.21):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -106,7 +106,7 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoUI (0.2.0-beta.4):
|
||||
- ExpoModulesCore
|
||||
- ExpoWebBrowser (15.0.7):
|
||||
- ExpoWebBrowser (15.0.8):
|
||||
- ExpoModulesCore
|
||||
- EXTaskManager (14.0.7):
|
||||
- ExpoModulesCore
|
||||
@@ -156,8 +156,8 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- PurchasesHybridCommon (17.7.0):
|
||||
- RevenueCat (= 5.39.0)
|
||||
- PurchasesHybridCommon (17.10.0):
|
||||
- RevenueCat (= 5.43.0)
|
||||
- RCTDeprecation (0.81.4)
|
||||
- RCTRequired (0.81.4)
|
||||
- RCTTypeSafety (0.81.4):
|
||||
@@ -1882,7 +1882,7 @@ PODS:
|
||||
- React-utils (= 0.81.4)
|
||||
- ReactNativeDependencies
|
||||
- ReactNativeDependencies (0.81.4)
|
||||
- RevenueCat (5.39.0)
|
||||
- RevenueCat (5.43.0)
|
||||
- RNCAsyncStorage (2.2.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
@@ -1971,7 +1971,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNDeviceInfo (14.0.4):
|
||||
- RNDeviceInfo (14.1.1):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.28.0):
|
||||
- hermes-engine
|
||||
@@ -1995,10 +1995,10 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNPurchases (9.4.3):
|
||||
- PurchasesHybridCommon (= 17.7.0)
|
||||
- RNPurchases (9.5.4):
|
||||
- PurchasesHybridCommon (= 17.10.0)
|
||||
- React-Core
|
||||
- RNReanimated (4.1.0):
|
||||
- RNReanimated (4.1.3):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2020,10 +2020,10 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/reanimated (= 4.1.0)
|
||||
- RNReanimated/reanimated (= 4.1.3)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/reanimated (4.1.0):
|
||||
- RNReanimated/reanimated (4.1.3):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2045,10 +2045,10 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/reanimated/apple (= 4.1.0)
|
||||
- RNReanimated/reanimated/apple (= 4.1.3)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/reanimated/apple (4.1.0):
|
||||
- RNReanimated/reanimated/apple (4.1.3):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2143,7 +2143,7 @@ PODS:
|
||||
- ReactNativeDependencies
|
||||
- Sentry/HybridSDK (= 8.56.0)
|
||||
- Yoga
|
||||
- RNSVG (15.13.0):
|
||||
- RNSVG (15.14.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2164,9 +2164,9 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNSVG/common (= 15.13.0)
|
||||
- RNSVG/common (= 15.14.0)
|
||||
- Yoga
|
||||
- RNSVG/common (15.13.0):
|
||||
- RNSVG/common (15.14.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2188,7 +2188,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNWorklets (0.5.1):
|
||||
- RNWorklets (0.6.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2210,9 +2210,9 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNWorklets/worklets (= 0.5.1)
|
||||
- RNWorklets/worklets (= 0.6.1)
|
||||
- Yoga
|
||||
- RNWorklets/worklets (0.5.1):
|
||||
- RNWorklets/worklets (0.6.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2234,9 +2234,9 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNWorklets/worklets/apple (= 0.5.1)
|
||||
- RNWorklets/worklets/apple (= 0.6.1)
|
||||
- Yoga
|
||||
- RNWorklets/worklets/apple (0.5.1):
|
||||
- RNWorklets/worklets/apple (0.6.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -2259,9 +2259,9 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- SDWebImage (5.21.2):
|
||||
- SDWebImage/Core (= 5.21.2)
|
||||
- SDWebImage/Core (5.21.2)
|
||||
- SDWebImage (5.21.3):
|
||||
- SDWebImage/Core (= 5.21.3)
|
||||
- SDWebImage/Core (5.21.3)
|
||||
- SDWebImageAVIFCoder (0.11.1):
|
||||
- libavif/core (>= 0.11.0)
|
||||
- SDWebImage (~> 5.10)
|
||||
@@ -2646,31 +2646,31 @@ SPEC CHECKSUMS:
|
||||
EXApplication: 296622817d459f46b6c5fe8691f4aac44d2b79e7
|
||||
EXConstants: a95804601ee4a6aa7800645f9b070d753b1142b3
|
||||
EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05
|
||||
EXNotifications: 7a2975f4e282b827a0bc78bb1d232650cb569bbd
|
||||
Expo: c839a13691635386c0134f204dbbaed3cebff0a8
|
||||
EXNotifications: 7cff475adb5d7a255a9ea46bbd2589cb3b454506
|
||||
Expo: 6580dbf21d94626792b38a95cddb2fb369ec6b0c
|
||||
ExpoAppleAuthentication: bc9de6e9ff3340604213ab9031d4c4f7f802623e
|
||||
ExpoAsset: 9ba6fbd677fb8e241a3899ac00fa735bc911eadf
|
||||
ExpoBackgroundTask: e0d201d38539c571efc5f9cb661fae8ab36ed61b
|
||||
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f
|
||||
ExpoCamera: e75f6807a2c047f3338bbadd101af4c71a1d13a5
|
||||
ExpoFileSystem: 5fb091ea11198e109ceef2bdef2e6e66523e62c4
|
||||
ExpoFont: 86ceec09ffed1c99cfee36ceb79ba149074901b5
|
||||
ExpoFileSystem: b79eadbda7b7f285f378f95f959cc9313a1c9c61
|
||||
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961
|
||||
ExpoGlassEffect: 744bf0c58c26a1b0212dff92856be07b98d01d8c
|
||||
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84
|
||||
ExpoHead: 5570e5edbe54fd8f88e51e8b94bf2931caaa7363
|
||||
ExpoImage: e88f500585913969b930e13a4be47277eb7c6de8
|
||||
ExpoHead: 7141e494b0773a8f0dc5ca3366ce91b1300f5a9d
|
||||
ExpoImage: 6356eb13d3a076a991cf191e4bb22cca91a8f317
|
||||
ExpoImagePicker: d251aab45a1b1857e4156fed88511b278b4eee1c
|
||||
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe
|
||||
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27
|
||||
ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d
|
||||
ExpoModulesCore: c03ac4bc5a83469ca1222c3954a0499cd059addf
|
||||
ExpoModulesCore: 3a6eb12a5f4d67b2f5fc7d0bc4777b18348f2d7a
|
||||
ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f
|
||||
ExpoSplashScreen: cbb839de72110dea1851dd3e85080b7923af2540
|
||||
ExpoSQLite: 7fa091ba5562474093fef09be644161a65e11b3f
|
||||
ExpoSymbols: 1ae04ce686de719b9720453b988d8bc5bf776c68
|
||||
ExpoSystemUI: 6cd74248a2282adf6dec488a75fa532d69dee314
|
||||
ExpoUI: 5e44b62e2589b7bc8a6123943105a230c693d000
|
||||
ExpoWebBrowser: 533bc2a1b188eec1c10e4926decf658f1687b5e5
|
||||
ExpoWebBrowser: d04a0d6247a0bea4519fbc2ea816610019ad83e0
|
||||
EXTaskManager: cf225704fab8de8794a6f57f7fa41a90c0e2cd47
|
||||
FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42
|
||||
hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394
|
||||
@@ -2679,7 +2679,7 @@ SPEC CHECKSUMS:
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
|
||||
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
|
||||
PurchasesHybridCommon: 6bc96162fb0c061e1980f474be618c088cfd1428
|
||||
PurchasesHybridCommon: b7b4eafb55fbaaac19b4c36d4082657a3f0d8490
|
||||
RCTDeprecation: 7487d6dda857ccd4cb3dd6ecfccdc3170e85dcbc
|
||||
RCTRequired: 54128b7df8be566881d48c7234724a78cb9b6157
|
||||
RCTTypeSafety: d2b07797a79e45d7b19e1cd2f53c79ab419fe217
|
||||
@@ -2748,20 +2748,20 @@ SPEC CHECKSUMS:
|
||||
ReactCodegen: a15ad48730e9fb2a51a4c9f61fe1ed253dfcf10f
|
||||
ReactCommon: 149b6c05126f2e99f2ed0d3c63539369546f8cae
|
||||
ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e
|
||||
RevenueCat: 4743a5eee0004e1c03eabeb3498818f902a5d622
|
||||
RevenueCat: a51003d4cb33820cc504cf177c627832b462a98e
|
||||
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
|
||||
RNCMaskedView: d2578d41c59b936db122b2798ba37e4722d21035
|
||||
RNCPicker: ddce382c4b42ea2ee36dd588066f0c6d5a240707
|
||||
RNDateTimePicker: 7dda2673bd2a6022ea8888fe669d735b2eac0b2d
|
||||
RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047
|
||||
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
|
||||
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
|
||||
RNPurchases: 1bc60e3a69af65d9cfe23967328421dd1df1763c
|
||||
RNReanimated: 8d3a14606ad49f022c17d9e12a2d339e9e5ad9b0
|
||||
RNPurchases: 2569675abdc1dbc739f2eec0fa564a112cf860de
|
||||
RNReanimated: 3895a29fdf77bbe2a627e1ed599a5e5d1df76c29
|
||||
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
|
||||
RNSentry: dbee413744aec703b8763b620b14ed7a1e2db095
|
||||
RNSVG: efc8a09e4ef50e7df0dbc9327752be127a2f610c
|
||||
RNWorklets: 76fce72926e28e304afb44f0da23b2d24f2c1fa0
|
||||
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
|
||||
RNSVG: 6c534e37eaaefe882b3f55294d0d607de20562dc
|
||||
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1
|
||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
|
||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
|
||||
Reference in New Issue
Block a user