feat(nutrition): 添加营养数据保存功能到HealthKit,包括蛋白质、脂肪和碳水化合物

This commit is contained in:
richarjiang
2025-11-19 14:27:49 +08:00
parent f43cfe7ac6
commit dc205ad56e
7 changed files with 371 additions and 48 deletions

View File

@@ -78,6 +78,19 @@ RCT_EXTERN_METHOD(getWaterIntakeFromHealthKit:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolver
rejecter:(RCTPromiseRejectBlock)rejecter)
// Nutrition Data Methods
RCT_EXTERN_METHOD(saveProteinToHealthKit:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolver
rejecter:(RCTPromiseRejectBlock)rejecter)
RCT_EXTERN_METHOD(saveFatToHealthKit:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolver
rejecter:(RCTPromiseRejectBlock)rejecter)
RCT_EXTERN_METHOD(saveCarbohydratesToHealthKit:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolver
rejecter:(RCTPromiseRejectBlock)rejecter)
// Workout Data Methods
RCT_EXTERN_METHOD(getRecentWorkouts:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolver

View File

@@ -88,11 +88,23 @@ class HealthKitManager: RCTEventEmitter {
static var dietaryWater: HKQuantityType? {
return HKObjectType.quantityType(forIdentifier: .dietaryWater)
}
static var dietaryProtein: HKQuantityType? {
return HKObjectType.quantityType(forIdentifier: .dietaryProtein)
}
static var dietaryFatTotal: HKQuantityType? {
return HKObjectType.quantityType(forIdentifier: .dietaryFatTotal)
}
static var dietaryCarbohydrates: HKQuantityType? {
return HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates)
}
static var all: Set<HKSampleType> {
var types: Set<HKSampleType> = []
if let bodyMass = bodyMass { types.insert(bodyMass) }
if let dietaryWater = dietaryWater { types.insert(dietaryWater) }
if let dietaryProtein = dietaryProtein { types.insert(dietaryProtein) }
if let dietaryFatTotal = dietaryFatTotal { types.insert(dietaryFatTotal) }
if let dietaryCarbohydrates = dietaryCarbohydrates { types.insert(dietaryCarbohydrates) }
return types
}
}
@@ -1669,6 +1681,194 @@ class HealthKitManager: RCTEventEmitter {
healthStore.execute(query)
}
// MARK: - Nutrition Data Methods
@objc
func saveProteinToHealthKit(
_ options: NSDictionary,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard HKHealthStore.isHealthDataAvailable() else {
rejecter("HEALTHKIT_NOT_AVAILABLE", "HealthKit is not available on this device", nil)
return
}
// Parse parameters
guard let amount = options["amount"] as? Double else {
rejecter("INVALID_PARAMETERS", "Amount is required", nil)
return
}
let recordedAt: Date
if let recordedAtString = options["recordedAt"] as? String,
let date = parseDate(from: recordedAtString) {
recordedAt = date
} else {
recordedAt = Date()
}
guard let proteinType = WriteTypes.dietaryProtein else {
rejecter("TYPE_NOT_AVAILABLE", "Protein type is not available", nil)
return
}
// Create quantity sample (protein in grams)
let quantity = HKQuantity(unit: HKUnit.gram(), doubleValue: amount)
let sample = HKQuantitySample(
type: proteinType,
quantity: quantity,
start: recordedAt,
end: recordedAt,
metadata: nil
)
// Save to HealthKit
healthStore.save(sample) { [weak self] (success, error) in
DispatchQueue.main.async {
if let error = error {
rejecter("SAVE_ERROR", "Failed to save protein: \(error.localizedDescription)", error)
return
}
if success {
let result: [String: Any] = [
"success": true,
"amount": amount,
"recordedAt": self?.dateToISOString(recordedAt) ?? ""
]
resolver(result)
} else {
rejecter("SAVE_FAILED", "Failed to save protein", nil)
}
}
}
}
@objc
func saveFatToHealthKit(
_ options: NSDictionary,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard HKHealthStore.isHealthDataAvailable() else {
rejecter("HEALTHKIT_NOT_AVAILABLE", "HealthKit is not available on this device", nil)
return
}
// Parse parameters
guard let amount = options["amount"] as? Double else {
rejecter("INVALID_PARAMETERS", "Amount is required", nil)
return
}
let recordedAt: Date
if let recordedAtString = options["recordedAt"] as? String,
let date = parseDate(from: recordedAtString) {
recordedAt = date
} else {
recordedAt = Date()
}
guard let fatType = WriteTypes.dietaryFatTotal else {
rejecter("TYPE_NOT_AVAILABLE", "Fat type is not available", nil)
return
}
// Create quantity sample (fat in grams)
let quantity = HKQuantity(unit: HKUnit.gram(), doubleValue: amount)
let sample = HKQuantitySample(
type: fatType,
quantity: quantity,
start: recordedAt,
end: recordedAt,
metadata: nil
)
// Save to HealthKit
healthStore.save(sample) { [weak self] (success, error) in
DispatchQueue.main.async {
if let error = error {
rejecter("SAVE_ERROR", "Failed to save fat: \(error.localizedDescription)", error)
return
}
if success {
let result: [String: Any] = [
"success": true,
"amount": amount,
"recordedAt": self?.dateToISOString(recordedAt) ?? ""
]
resolver(result)
} else {
rejecter("SAVE_FAILED", "Failed to save fat", nil)
}
}
}
}
@objc
func saveCarbohydratesToHealthKit(
_ options: NSDictionary,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard HKHealthStore.isHealthDataAvailable() else {
rejecter("HEALTHKIT_NOT_AVAILABLE", "HealthKit is not available on this device", nil)
return
}
// Parse parameters
guard let amount = options["amount"] as? Double else {
rejecter("INVALID_PARAMETERS", "Amount is required", nil)
return
}
let recordedAt: Date
if let recordedAtString = options["recordedAt"] as? String,
let date = parseDate(from: recordedAtString) {
recordedAt = date
} else {
recordedAt = Date()
}
guard let carbohydratesType = WriteTypes.dietaryCarbohydrates else {
rejecter("TYPE_NOT_AVAILABLE", "Carbohydrates type is not available", nil)
return
}
// Create quantity sample (carbohydrates in grams)
let quantity = HKQuantity(unit: HKUnit.gram(), doubleValue: amount)
let sample = HKQuantitySample(
type: carbohydratesType,
quantity: quantity,
start: recordedAt,
end: recordedAt,
metadata: nil
)
// Save to HealthKit
healthStore.save(sample) { [weak self] (success, error) in
DispatchQueue.main.async {
if let error = error {
rejecter("SAVE_ERROR", "Failed to save carbohydrates: \(error.localizedDescription)", error)
return
}
if success {
let result: [String: Any] = [
"success": true,
"amount": amount,
"recordedAt": self?.dateToISOString(recordedAt) ?? ""
]
resolver(result)
} else {
rejecter("SAVE_FAILED", "Failed to save carbohydrates", nil)
}
}
}
}
// MARK: - Workout Data Methods
@objc