feat: 集成健康数据功能优化
- 在 app.json 中添加 react-native-health 的配置以启用健康 API - 在 Explore 页面中重构健康数据加载逻辑,增加加载状态提示 - 更新健康数据获取函数,增强错误处理和日志输出 - 修改 iOS 权限设置,确保健康数据访问权限的正确配置 - 更新 Info.plist 中的健康数据使用说明
This commit is contained in:
7
app.json
7
app.json
@@ -35,6 +35,13 @@
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
],
|
||||
[
|
||||
"react-native-health",
|
||||
{
|
||||
"enableHealthAPI": true,
|
||||
"healthSharePermission": "应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。"
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
|
||||
@@ -51,23 +51,38 @@ export default function ExploreScreen() {
|
||||
// HealthKit: 每次页面聚焦都拉取今日数据
|
||||
const [stepCount, setStepCount] = useState<number | null>(null);
|
||||
const [activeCalories, setActiveCalories] = useState<number | null>(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() {
|
||||
{/* 今日报告 标题 */}
|
||||
<Text style={styles.sectionTitle}>今日报告</Text>
|
||||
|
||||
{/* 健康数据错误提示 */}
|
||||
{isLoading && (
|
||||
<View style={styles.errorContainer}>
|
||||
<Ionicons name="warning-outline" size={20} color="#E54D4D" />
|
||||
<Text style={styles.errorText}>加载中...</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.retryButton}
|
||||
onPress={loadHealthData} disabled={isLoading}
|
||||
>
|
||||
<Ionicons
|
||||
name="refresh-outline"
|
||||
size={16}
|
||||
color={isLoading ? '#9AA3AE' : '#E54D4D'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 指标行:左大卡(训练时间),右两小卡(消耗卡路里、步数) */}
|
||||
<View style={styles.metricsRow}>
|
||||
<View style={[styles.trainingCard, styles.metricsLeft]}>
|
||||
@@ -127,7 +160,7 @@ export default function ExploreScreen() {
|
||||
<View style={[styles.metricsRightCard, styles.caloriesCard, { minHeight: 88 }]}>
|
||||
<Text style={styles.cardTitleSecondary}>消耗卡路里</Text>
|
||||
<Text style={styles.caloriesValue}>
|
||||
{activeCalories != null ? `${activeCalories} 千卡` : '——'}
|
||||
{isLoading ? '加载中...' : activeCalories != null ? `${activeCalories} 千卡` : '——'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.metricsRightCard, styles.stepsCard, { minHeight: 88 }]}>
|
||||
@@ -135,7 +168,7 @@ export default function ExploreScreen() {
|
||||
<View style={styles.iconSquare}><Ionicons name="footsteps-outline" size={18} color="#192126" /></View>
|
||||
<Text style={styles.cardTitle}>步数</Text>
|
||||
</View>
|
||||
<Text style={styles.stepsValue}>{stepCount != null ? `${stepCount}/2000` : '——/2000'}</Text>
|
||||
<Text style={styles.stepsValue}>{isLoading ? '加载中.../2000' : stepCount != null ? `${stepCount}/2000` : '——/2000'}</Text>
|
||||
<ProgressBar progress={Math.min(1, Math.max(0, (stepCount ?? 0) / 2000))} height={12} trackColor="#FFEBCB" fillColor="#FFC365" />
|
||||
</View>
|
||||
</View>
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = digitalpilates/Info.plist; sourceTree = "<group>"; };
|
||||
905D0A94B379FF06B1A262B2 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
8732A3660B00244505674555 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = digitalpilates/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
F11748442D0722820044C1D9 /* digitalpilates-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "digitalpilates-Bridging-Header.h"; path = "digitalpilates/digitalpilates-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
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 = "<group>";
|
||||
@@ -62,27 +62,19 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
05E1F9207C56E5E9A17A80BC /* libPods-digitalpilates.a */,
|
||||
FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4F7DF3E41466E05C57FED62C /* digitalpilates */ = {
|
||||
7B79F82457416491183F46D6 /* digitalpilates */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */,
|
||||
8732A3660B00244505674555 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = digitalpilates;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5E539D2C9AB8910307FD73AB /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4F7DF3E41466E05C57FED62C /* digitalpilates */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@@ -113,6 +105,16 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8D14961AE80832AA51F9FFA3 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */,
|
||||
7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -122,13 +124,12 @@
|
||||
path = digitalpilates/Supporting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E08D8B522D8095FEAE625994 /* Pods */ = {
|
||||
F7E6B913F90880BFADBE192E /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B8247601A9965B9A71737DFD /* Pods-digitalpilates.debug.xcconfig */,
|
||||
E41FEE326E770C4798A5541A /* Pods-digitalpilates.release.xcconfig */,
|
||||
7B79F82457416491183F46D6 /* digitalpilates */,
|
||||
);
|
||||
path = Pods;
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>digitalpilates</string>
|
||||
<string>com.anonymous.digitalpilates</string>
|
||||
<string>digital-pilates</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -45,14 +45,14 @@
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to update health info</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
</array>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>应用需要写入健康数据以提供更准确的统计。</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.developer.healthkit</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.healthkit.access</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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<boolean> {
|
||||
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<TodayHealthData> {
|
||||
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<number>((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<number>((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 };
|
||||
}
|
||||
Reference in New Issue
Block a user