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",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"react-native-health",
|
||||||
|
{
|
||||||
|
"enableHealthAPI": true,
|
||||||
|
"healthSharePermission": "应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
|
|||||||
@@ -51,23 +51,38 @@ export default function ExploreScreen() {
|
|||||||
// HealthKit: 每次页面聚焦都拉取今日数据
|
// HealthKit: 每次页面聚焦都拉取今日数据
|
||||||
const [stepCount, setStepCount] = useState<number | null>(null);
|
const [stepCount, setStepCount] = useState<number | null>(null);
|
||||||
const [activeCalories, setActiveCalories] = 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(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
let isActive = true;
|
loadHealthData();
|
||||||
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;
|
|
||||||
};
|
|
||||||
}, [])
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -113,6 +128,24 @@ export default function ExploreScreen() {
|
|||||||
{/* 今日报告 标题 */}
|
{/* 今日报告 标题 */}
|
||||||
<Text style={styles.sectionTitle}>今日报告</Text>
|
<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.metricsRow}>
|
||||||
<View style={[styles.trainingCard, styles.metricsLeft]}>
|
<View style={[styles.trainingCard, styles.metricsLeft]}>
|
||||||
@@ -127,7 +160,7 @@ export default function ExploreScreen() {
|
|||||||
<View style={[styles.metricsRightCard, styles.caloriesCard, { minHeight: 88 }]}>
|
<View style={[styles.metricsRightCard, styles.caloriesCard, { minHeight: 88 }]}>
|
||||||
<Text style={styles.cardTitleSecondary}>消耗卡路里</Text>
|
<Text style={styles.cardTitleSecondary}>消耗卡路里</Text>
|
||||||
<Text style={styles.caloriesValue}>
|
<Text style={styles.caloriesValue}>
|
||||||
{activeCalories != null ? `${activeCalories} 千卡` : '——'}
|
{isLoading ? '加载中...' : activeCalories != null ? `${activeCalories} 千卡` : '——'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.metricsRightCard, styles.stepsCard, { minHeight: 88 }]}>
|
<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>
|
<View style={styles.iconSquare}><Ionicons name="footsteps-outline" size={18} color="#192126" /></View>
|
||||||
<Text style={styles.cardTitle}>步数</Text>
|
<Text style={styles.cardTitle}>步数</Text>
|
||||||
</View>
|
</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" />
|
<ProgressBar progress={Math.min(1, Math.max(0, (stepCount ?? 0) / 2000))} height={12} trackColor="#FFEBCB" fillColor="#FFC365" />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -386,5 +419,24 @@ const styles = StyleSheet.create({
|
|||||||
color: '#7A6A42',
|
color: '#7A6A42',
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
marginBottom: 8,
|
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 */
|
/* Begin PBXBuildFile section */
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
||||||
951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; 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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
24A7AF62E3CEAC7DED8D0215 /* libPods-digitalpilates.a in Frameworks */,
|
7F5A026795C1BF28BEBBFA4E /* libPods-digitalpilates.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
||||||
905D0A94B379FF06B1A262B2 /* PrivacyInfo.xcprivacy */,
|
C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */,
|
||||||
);
|
);
|
||||||
name = digitalpilates;
|
name = digitalpilates;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -62,27 +62,19 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||||
05E1F9207C56E5E9A17A80BC /* libPods-digitalpilates.a */,
|
FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
4F7DF3E41466E05C57FED62C /* digitalpilates */ = {
|
7B79F82457416491183F46D6 /* digitalpilates */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
951C0617485F4FEF590D4C5D /* ExpoModulesProvider.swift */,
|
8732A3660B00244505674555 /* ExpoModulesProvider.swift */,
|
||||||
);
|
);
|
||||||
name = digitalpilates;
|
name = digitalpilates;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
5E539D2C9AB8910307FD73AB /* ExpoModulesProviders */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4F7DF3E41466E05C57FED62C /* digitalpilates */,
|
|
||||||
);
|
|
||||||
name = ExpoModulesProviders;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -97,8 +89,8 @@
|
|||||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||||
83CBBA001A601CBA00E9B192 /* Products */,
|
83CBBA001A601CBA00E9B192 /* Products */,
|
||||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||||
E08D8B522D8095FEAE625994 /* Pods */,
|
8D14961AE80832AA51F9FFA3 /* Pods */,
|
||||||
5E539D2C9AB8910307FD73AB /* ExpoModulesProviders */,
|
F7E6B913F90880BFADBE192E /* ExpoModulesProviders */,
|
||||||
);
|
);
|
||||||
indentWidth = 2;
|
indentWidth = 2;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -113,6 +105,16 @@
|
|||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -122,13 +124,12 @@
|
|||||||
path = digitalpilates/Supporting;
|
path = digitalpilates/Supporting;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E08D8B522D8095FEAE625994 /* Pods */ = {
|
F7E6B913F90880BFADBE192E /* ExpoModulesProviders */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B8247601A9965B9A71737DFD /* Pods-digitalpilates.debug.xcconfig */,
|
7B79F82457416491183F46D6 /* digitalpilates */,
|
||||||
E41FEE326E770C4798A5541A /* Pods-digitalpilates.release.xcconfig */,
|
|
||||||
);
|
);
|
||||||
path = Pods;
|
name = ExpoModulesProviders;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
@@ -139,13 +140,13 @@
|
|||||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "digitalpilates" */;
|
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "digitalpilates" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||||
934267B0C231F8AAB5B2FE7D /* [Expo] Configure project */,
|
E959B4C3D4071EECDD504F2E /* [Expo] Configure project */,
|
||||||
13B07F871A680F5B00A75B9A /* Sources */,
|
13B07F871A680F5B00A75B9A /* Sources */,
|
||||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||||
E8A50A64ECE6FF559ED244DD /* [CP] Embed Pods Frameworks */,
|
679509E8E2F87FD68CDC2915 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -195,7 +196,7 @@
|
|||||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
||||||
E243B50C4648FB8A9C9EEF3B /* PrivacyInfo.xcprivacy in Resources */,
|
CBE4D0B57478826DADA47BC9 /* PrivacyInfo.xcprivacy in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
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";
|
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;
|
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 */ = {
|
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -275,7 +294,7 @@
|
|||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-resources.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
934267B0C231F8AAB5B2FE7D /* [Expo] Configure project */ = {
|
E959B4C3D4071EECDD504F2E /* [Expo] Configure project */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -294,24 +313,6 @@
|
|||||||
shellPath = /bin/sh;
|
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";
|
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 */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -320,7 +321,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
|
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
|
||||||
676318C8AE606E8604AB1163 /* ExpoModulesProvider.swift in Sources */,
|
27B482E4BA415859EA8B0372 /* ExpoModulesProvider.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -329,13 +330,12 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = B8247601A9965B9A71737DFD /* Pods-digitalpilates.debug.xcconfig */;
|
baseConfigurationReference = 031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
|
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 756WVXJ6MT;
|
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -354,7 +354,7 @@
|
|||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.digitalpilates;
|
PRODUCT_BUNDLE_IDENTIFIER = "digital-pilates";
|
||||||
PRODUCT_NAME = digitalpilates;
|
PRODUCT_NAME = digitalpilates;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "digitalpilates/digitalpilates-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "digitalpilates/digitalpilates-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -366,13 +366,12 @@
|
|||||||
};
|
};
|
||||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = E41FEE326E770C4798A5541A /* Pods-digitalpilates.release.xcconfig */;
|
baseConfigurationReference = 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
|
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 756WVXJ6MT;
|
|
||||||
INFOPLIST_FILE = digitalpilates/Info.plist;
|
INFOPLIST_FILE = digitalpilates/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -386,7 +385,7 @@
|
|||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.digitalpilates;
|
PRODUCT_BUNDLE_IDENTIFIER = "digital-pilates";
|
||||||
PRODUCT_NAME = digitalpilates;
|
PRODUCT_NAME = digitalpilates;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "digitalpilates/digitalpilates-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "digitalpilates/digitalpilates-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>digitalpilates</string>
|
<string>digitalpilates</string>
|
||||||
<string>com.anonymous.digitalpilates</string>
|
<string>digital-pilates</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
@@ -45,14 +45,14 @@
|
|||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSHealthShareUsageDescription</key>
|
||||||
|
<string>应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。</string>
|
||||||
|
<key>NSHealthUpdateUsageDescription</key>
|
||||||
|
<string>Allow $(PRODUCT_NAME) to update health info</string>
|
||||||
<key>NSUserActivityTypes</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSHealthShareUsageDescription</key>
|
|
||||||
<string>应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。</string>
|
|
||||||
<key>NSHealthUpdateUsageDescription</key>
|
|
||||||
<string>应用需要写入健康数据以提供更准确的统计。</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>SplashScreen</string>
|
<string>SplashScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
|||||||
@@ -4,5 +4,7 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.healthkit</key>
|
<key>com.apple.developer.healthkit</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.developer.healthkit.access</key>
|
||||||
|
<array/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</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 = {
|
const PERMISSIONS: HealthKitPermissions = {
|
||||||
permissions: {
|
permissions: {
|
||||||
@@ -17,37 +18,66 @@ export type TodayHealthData = {
|
|||||||
|
|
||||||
export async function ensureHealthPermissions(): Promise<boolean> {
|
export async function ensureHealthPermissions(): Promise<boolean> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
console.log('开始初始化HealthKit...');
|
||||||
|
|
||||||
AppleHealthKit.initHealthKit(PERMISSIONS, (error) => {
|
AppleHealthKit.initHealthKit(PERMISSIONS, (error) => {
|
||||||
if (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);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('HealthKit init success');
|
console.log('HealthKit初始化成功');
|
||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTodayHealthData(): Promise<TodayHealthData> {
|
export async function fetchTodayHealthData(): Promise<TodayHealthData> {
|
||||||
|
console.log('开始获取今日健康数据...');
|
||||||
|
|
||||||
const start = new Date();
|
const start = new Date();
|
||||||
start.setHours(0, 0, 0, 0);
|
start.setHours(0, 0, 0, 0);
|
||||||
const options = { startDate: start.toISOString() } as any;
|
const options = { startDate: start.toISOString() } as any;
|
||||||
|
|
||||||
|
console.log('查询选项:', options);
|
||||||
|
|
||||||
const steps = await new Promise<number>((resolve) => {
|
const steps = await new Promise<number>((resolve) => {
|
||||||
AppleHealthKit.getStepCount(options, (err, res) => {
|
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);
|
resolve(res.value || 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const calories = await new Promise<number>((resolve) => {
|
const calories = await new Promise<number>((resolve) => {
|
||||||
AppleHealthKit.getActiveEnergyBurned(options, (err, res) => {
|
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
|
// 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 };
|
return { steps, activeEnergyBurned: calories };
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user