diff --git a/app.json b/app.json index 55b1334..e239989 100644 --- a/app.json +++ b/app.json @@ -35,6 +35,13 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" } + ], + [ + "react-native-health", + { + "enableHealthAPI": true, + "healthSharePermission": "应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。" + } ] ], "experiments": { diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx index ff7c141..139af3e 100644 --- a/app/(tabs)/explore.tsx +++ b/app/(tabs)/explore.tsx @@ -51,23 +51,38 @@ export default function ExploreScreen() { // HealthKit: 每次页面聚焦都拉取今日数据 const [stepCount, setStepCount] = useState(null); const [activeCalories, setActiveCalories] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const loadHealthData = async () => { + try { + console.log('=== 开始HealthKit初始化流程 ==='); + setIsLoading(true); + + const ok = await ensureHealthPermissions(); + if (!ok) { + const errorMsg = '无法获取健康权限,请确保在真实iOS设备上运行并授权应用访问健康数据'; + console.warn(errorMsg); + return; + } + + console.log('权限获取成功,开始获取健康数据...'); + const data = await fetchTodayHealthData(); + + console.log('设置UI状态:', data); + setStepCount(data.steps); + setActiveCalories(Math.round(data.activeEnergyBurned)); + console.log('=== HealthKit数据获取完成 ==='); + + } catch (error) { + console.error('HealthKit流程出现异常:', error); + } finally { + setIsLoading(false); + } + }; useFocusEffect( React.useCallback(() => { - let isActive = true; - const run = async () => { - console.log('HealthKit init start'); - const ok = await ensureHealthPermissions(); - if (!ok) return; - const data = await fetchTodayHealthData(); - if (!isActive) return; - setStepCount(data.steps); - setActiveCalories(Math.round(data.activeEnergyBurned)); - }; - run(); - return () => { - isActive = false; - }; + loadHealthData(); }, []) ); @@ -113,6 +128,24 @@ export default function ExploreScreen() { {/* 今日报告 标题 */} 今日报告 + {/* 健康数据错误提示 */} + {isLoading && ( + + + 加载中... + + + + + )} + {/* 指标行:左大卡(训练时间),右两小卡(消耗卡路里、步数) */} @@ -127,7 +160,7 @@ export default function ExploreScreen() { 消耗卡路里 - {activeCalories != null ? `${activeCalories} 千卡` : '——'} + {isLoading ? '加载中...' : activeCalories != null ? `${activeCalories} 千卡` : '——'} @@ -135,7 +168,7 @@ export default function ExploreScreen() { 步数 - {stepCount != null ? `${stepCount}/2000` : '——/2000'} + {isLoading ? '加载中.../2000' : stepCount != null ? `${stepCount}/2000` : '——/2000'} @@ -386,5 +419,24 @@ const styles = StyleSheet.create({ color: '#7A6A42', fontWeight: '700', marginBottom: 8, - } + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#FFE5E5', + borderRadius: 12, + padding: 12, + marginBottom: 16, + }, + errorText: { + fontSize: 14, + color: '#E54D4D', + fontWeight: '600', + marginLeft: 8, + flex: 1, + }, + retryButton: { + padding: 4, + marginLeft: 8, + }, }); diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj index 5b886f4..37598f9 100644 --- a/ios/digitalpilates.xcodeproj/project.pbxproj +++ b/ios/digitalpilates.xcodeproj/project.pbxproj @@ -8,28 +8,28 @@ /* Begin PBXBuildFile section */ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 24A7AF62E3CEAC7DED8D0215 /* libPods-digitalpilates.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E1F9207C56E5E9A17A80BC /* libPods-digitalpilates.a */; }; + 27B482E4BA415859EA8B0372 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732A3660B00244505674555 /* ExpoModulesProvider.swift */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; - 676318C8AE606E8604AB1163 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */; }; + 7F5A026795C1BF28BEBBFA4E /* libPods-digitalpilates.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */; }; BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; - E243B50C4648FB8A9C9EEF3B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 905D0A94B379FF06B1A262B2 /* PrivacyInfo.xcprivacy */; }; + CBE4D0B57478826DADA47BC9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */; }; F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 05E1F9207C56E5E9A17A80BC /* libPods-digitalpilates.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-digitalpilates.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.debug.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.debug.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* digitalpilates.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = digitalpilates.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = digitalpilates/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = digitalpilates/Info.plist; sourceTree = ""; }; - 905D0A94B379FF06B1A262B2 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = ""; }; + 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.release.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.release.xcconfig"; sourceTree = ""; }; + 8732A3660B00244505674555 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = digitalpilates/SplashScreen.storyboard; sourceTree = ""; }; - B8247601A9965B9A71737DFD /* Pods-digitalpilates.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.debug.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.debug.xcconfig"; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; - E41FEE326E770C4798A5541A /* Pods-digitalpilates.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.release.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.release.xcconfig"; sourceTree = ""; }; + C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = digitalpilates/AppDelegate.swift; sourceTree = ""; }; F11748442D0722820044C1D9 /* digitalpilates-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "digitalpilates-Bridging-Header.h"; path = "digitalpilates/digitalpilates-Bridging-Header.h"; sourceTree = ""; }; + FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-digitalpilates.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -37,7 +37,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 24A7AF62E3CEAC7DED8D0215 /* libPods-digitalpilates.a in Frameworks */, + 7F5A026795C1BF28BEBBFA4E /* libPods-digitalpilates.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -53,7 +53,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, - 905D0A94B379FF06B1A262B2 /* PrivacyInfo.xcprivacy */, + C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */, ); name = digitalpilates; sourceTree = ""; @@ -62,27 +62,19 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 05E1F9207C56E5E9A17A80BC /* libPods-digitalpilates.a */, + FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */, ); name = Frameworks; sourceTree = ""; }; - 4F7DF3E41466E05C57FED62C /* digitalpilates */ = { + 7B79F82457416491183F46D6 /* digitalpilates */ = { isa = PBXGroup; children = ( - 951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */, + 8732A3660B00244505674555 /* ExpoModulesProvider.swift */, ); name = digitalpilates; sourceTree = ""; }; - 5E539D2C9AB8910307FD73AB /* ExpoModulesProviders */ = { - isa = PBXGroup; - children = ( - 4F7DF3E41466E05C57FED62C /* digitalpilates */, - ); - name = ExpoModulesProviders; - sourceTree = ""; - }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( @@ -97,8 +89,8 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, - E08D8B522D8095FEAE625994 /* Pods */, - 5E539D2C9AB8910307FD73AB /* ExpoModulesProviders */, + 8D14961AE80832AA51F9FFA3 /* Pods */, + F7E6B913F90880BFADBE192E /* ExpoModulesProviders */, ); indentWidth = 2; sourceTree = ""; @@ -113,6 +105,16 @@ name = Products; sourceTree = ""; }; + 8D14961AE80832AA51F9FFA3 /* Pods */ = { + isa = PBXGroup; + children = ( + 031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */, + 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; BB2F792B24A3F905000567C9 /* Supporting */ = { isa = PBXGroup; children = ( @@ -122,13 +124,12 @@ path = digitalpilates/Supporting; sourceTree = ""; }; - E08D8B522D8095FEAE625994 /* Pods */ = { + F7E6B913F90880BFADBE192E /* ExpoModulesProviders */ = { isa = PBXGroup; children = ( - B8247601A9965B9A71737DFD /* Pods-digitalpilates.debug.xcconfig */, - E41FEE326E770C4798A5541A /* Pods-digitalpilates.release.xcconfig */, + 7B79F82457416491183F46D6 /* digitalpilates */, ); - path = Pods; + name = ExpoModulesProviders; sourceTree = ""; }; /* End PBXGroup section */ @@ -139,13 +140,13 @@ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "digitalpilates" */; buildPhases = ( 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, - 934267B0C231F8AAB5B2FE7D /* [Expo] Configure project */, + E959B4C3D4071EECDD504F2E /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, - E8A50A64ECE6FF559ED244DD /* [CP] Embed Pods Frameworks */, + 679509E8E2F87FD68CDC2915 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -195,7 +196,7 @@ BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, - E243B50C4648FB8A9C9EEF3B /* PrivacyInfo.xcprivacy in Resources */, + CBE4D0B57478826DADA47BC9 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -239,6 +240,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 679509E8E2F87FD68CDC2915 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -275,7 +294,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 934267B0C231F8AAB5B2FE7D /* [Expo] Configure project */ = { + E959B4C3D4071EECDD504F2E /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -294,24 +313,6 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-digitalpilates/expo-configure-project.sh\"\n"; }; - E8A50A64ECE6FF559ED244DD /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -320,7 +321,7 @@ buildActionMask = 2147483647; files = ( F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */, - 676318C8AE606E8604AB1163 /* ExpoModulesProvider.swift in Sources */, + 27B482E4BA415859EA8B0372 /* ExpoModulesProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -329,13 +330,12 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B8247601A9965B9A71737DFD /* Pods-digitalpilates.debug.xcconfig */; + baseConfigurationReference = 031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 756WVXJ6MT; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", @@ -354,7 +354,7 @@ "-lc++", ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; - PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.digitalpilates; + PRODUCT_BUNDLE_IDENTIFIER = "digital-pilates"; PRODUCT_NAME = digitalpilates; SWIFT_OBJC_BRIDGING_HEADER = "digitalpilates/digitalpilates-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -366,13 +366,12 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E41FEE326E770C4798A5541A /* Pods-digitalpilates.release.xcconfig */; + baseConfigurationReference = 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 756WVXJ6MT; INFOPLIST_FILE = digitalpilates/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -386,7 +385,7 @@ "-lc++", ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; - PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.digitalpilates; + PRODUCT_BUNDLE_IDENTIFIER = "digital-pilates"; PRODUCT_NAME = digitalpilates; SWIFT_OBJC_BRIDGING_HEADER = "digitalpilates/digitalpilates-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/digitalpilates/Info.plist b/ios/digitalpilates/Info.plist index 9eca003..9fed695 100644 --- a/ios/digitalpilates/Info.plist +++ b/ios/digitalpilates/Info.plist @@ -28,7 +28,7 @@ CFBundleURLSchemes digitalpilates - com.anonymous.digitalpilates + digital-pilates @@ -45,14 +45,14 @@ NSAllowsLocalNetworking + NSHealthShareUsageDescription + 应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。 + NSHealthUpdateUsageDescription + Allow $(PRODUCT_NAME) to update health info NSUserActivityTypes $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route - NSHealthShareUsageDescription - 应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。 - NSHealthUpdateUsageDescription - 应用需要写入健康数据以提供更准确的统计。 UILaunchStoryboardName SplashScreen UIRequiredDeviceCapabilities diff --git a/ios/digitalpilates/digitalpilates.entitlements b/ios/digitalpilates/digitalpilates.entitlements index 9c24ef2..b37fe68 100644 --- a/ios/digitalpilates/digitalpilates.entitlements +++ b/ios/digitalpilates/digitalpilates.entitlements @@ -4,5 +4,7 @@ com.apple.developer.healthkit + com.apple.developer.healthkit.access + \ No newline at end of file diff --git a/utils/health.ts b/utils/health.ts index 9c08d0a..1818eab 100644 --- a/utils/health.ts +++ b/utils/health.ts @@ -1,4 +1,5 @@ -import AppleHealthKit, { HealthKitPermissions } from 'react-native-health'; +import type { HealthKitPermissions } from 'react-native-health'; +import AppleHealthKit from 'react-native-health'; const PERMISSIONS: HealthKitPermissions = { permissions: { @@ -17,37 +18,66 @@ export type TodayHealthData = { export async function ensureHealthPermissions(): Promise { return new Promise((resolve) => { + console.log('开始初始化HealthKit...'); + AppleHealthKit.initHealthKit(PERMISSIONS, (error) => { if (error) { - console.warn('HealthKit init failed', error); + console.error('HealthKit初始化失败:', error); + // 常见错误处理 + if (typeof error === 'string') { + if (error.includes('not available')) { + console.warn('HealthKit不可用 - 可能在模拟器上运行或非iOS设备'); + } + } resolve(false); return; } - console.log('HealthKit init success'); + console.log('HealthKit初始化成功'); resolve(true); }); }); } export async function fetchTodayHealthData(): Promise { + console.log('开始获取今日健康数据...'); + const start = new Date(); start.setHours(0, 0, 0, 0); const options = { startDate: start.toISOString() } as any; + console.log('查询选项:', options); + const steps = await new Promise((resolve) => { AppleHealthKit.getStepCount(options, (err, res) => { - if (err || !res) return resolve(0); + if (err) { + console.error('获取步数失败:', err); + return resolve(0); + } + if (!res) { + console.warn('步数数据为空'); + return resolve(0); + } + console.log('步数数据:', res); resolve(res.value || 0); }); }); const calories = await new Promise((resolve) => { AppleHealthKit.getActiveEnergyBurned(options, (err, res) => { - if (err || !res) return resolve(0); + if (err) { + console.error('获取消耗卡路里失败:', err); + return resolve(0); + } + if (!res || !Array.isArray(res) || res.length === 0) { + console.warn('卡路里数据为空或格式错误'); + return resolve(0); + } + console.log('卡路里数据:', res); // library returns value as number in kilocalories - resolve(res[0].value || 0); + resolve(res[0]?.value || 0); }); }); + console.log('今日健康数据获取完成:', { steps, calories }); return { steps, activeEnergyBurned: calories }; } \ No newline at end of file