/** * HealthKit测试组件 * 用于测试和演示HealthKit native module的功能 */ import React, { useEffect, useState } from 'react'; import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import HealthKitManager, { HealthKitUtils, SleepDataSample } from '../utils/healthKit'; interface HealthKitTestState { isAvailable: boolean; isAuthorized: boolean; sleepData: SleepDataSample[]; lastNightSleep: any; loading: boolean; error: string | null; } const HealthKitTest: React.FC = () => { const [state, setState] = useState({ isAvailable: false, isAuthorized: false, sleepData: [], lastNightSleep: null, loading: false, error: null, }); useEffect(() => { // 检查HealthKit可用性 const available = HealthKitUtils.isAvailable(); setState(prev => ({ ...prev, isAvailable: available })); if (!available && Platform.OS === 'ios') { Alert.alert('提示', 'HealthKit在当前设备上不可用,可能是因为运行在模拟器上。'); } }, []); const handleRequestAuthorization = async () => { if (!state.isAvailable) { Alert.alert('错误', 'HealthKit不可用'); return; } setState(prev => ({ ...prev, loading: true, error: null })); try { const result = await HealthKitManager.requestAuthorization(); if (result.success) { const sleepPermission = result.permissions['HKCategoryTypeIdentifierSleepAnalysis']; const authorized = sleepPermission === 'authorized'; setState(prev => ({ ...prev, isAuthorized: authorized, loading: false })); Alert.alert( '授权结果', authorized ? '已获得睡眠数据访问权限' : `睡眠数据权限状态: ${sleepPermission}`, [{ text: '确定' }] ); } else { setState(prev => ({ ...prev, loading: false })); Alert.alert('授权失败', '用户拒绝了HealthKit权限请求'); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); setState(prev => ({ ...prev, loading: false, error: errorMessage })); Alert.alert('错误', `授权失败: ${errorMessage}`); } }; const handleCheckAuthorizationStatus = async () => { if (!state.isAvailable) { Alert.alert('错误', 'HealthKit不可用'); return; } setState(prev => ({ ...prev, loading: true, error: null })); try { const result = await HealthKitManager.getAuthorizationStatus(); if (result.success) { const permissions = result.permissions; const sleepPermission = permissions['HKCategoryTypeIdentifierSleepAnalysis']; const authorized = sleepPermission === 'authorized'; setState(prev => ({ ...prev, isAuthorized: authorized, loading: false })); const permissionDetails = Object.entries(permissions) .map(([key, value]) => `${key}: ${value}`) .join('\n'); Alert.alert( '权限状态', `当前权限状态:\n${permissionDetails}`, [{ text: '确定' }] ); } else { setState(prev => ({ ...prev, loading: false })); Alert.alert('查询失败', '无法获取权限状态'); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); setState(prev => ({ ...prev, loading: false, error: errorMessage })); Alert.alert('错误', `查询权限状态失败: ${errorMessage}`); } }; const handleGetSleepData = async () => { if (!state.isAuthorized) { Alert.alert('错误', '请先获取HealthKit授权'); return; } setState(prev => ({ ...prev, loading: true, error: null })); try { const endDate = new Date(); const startDate = new Date(); startDate.setDate(endDate.getDate() - 7); // 获取最近7天的数据 const result = await HealthKitManager.getSleepData({ startDate: startDate.toISOString(), endDate: endDate.toISOString(), limit: 50, }); setState(prev => ({ ...prev, sleepData: result.data, loading: false, })); Alert.alert('成功', `获取到 ${result.count} 条睡眠记录`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); setState(prev => ({ ...prev, loading: false, error: errorMessage })); Alert.alert('错误', `获取睡眠数据失败: ${errorMessage}`); } }; const handleGetLastNightSleep = async () => { if (!state.isAuthorized) { Alert.alert('错误', '请先获取HealthKit授权'); return; } setState(prev => ({ ...prev, loading: true, error: null })); try { const today = new Date(); const yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); const startDate = new Date(yesterday); startDate.setHours(18, 0, 0, 0); const endDate = new Date(today); endDate.setHours(12, 0, 0, 0); const result = await HealthKitManager.getSleepData({ startDate: startDate.toISOString(), endDate: endDate.toISOString(), limit: 20, }); const sleepSamples = result.data.filter(sample => ['asleep', 'core', 'deep', 'rem'].includes(sample.categoryType) ); if (sleepSamples.length > 0) { const sleepStart = new Date(Math.min(...sleepSamples.map(s => new Date(s.startDate).getTime()))); const sleepEnd = new Date(Math.max(...sleepSamples.map(s => new Date(s.endDate).getTime()))); const totalDuration = sleepSamples.reduce((sum, s) => sum + s.duration, 0); const lastNightData = { hasData: true, sleepStart: sleepStart.toISOString(), sleepEnd: sleepEnd.toISOString(), totalDuration, totalDurationFormatted: HealthKitUtils.formatDuration(totalDuration), samples: sleepSamples, bedTime: sleepStart.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }), wakeTime: sleepEnd.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }), }; setState(prev => ({ ...prev, lastNightSleep: lastNightData, loading: false })); Alert.alert('昨晚睡眠', `睡眠时间: ${lastNightData.bedTime} - ${lastNightData.wakeTime}\n睡眠时长: ${lastNightData.totalDurationFormatted}`); } else { setState(prev => ({ ...prev, lastNightSleep: { hasData: false, message: '未找到昨晚的睡眠数据' }, loading: false })); Alert.alert('提示', '未找到昨晚的睡眠数据'); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); setState(prev => ({ ...prev, loading: false, error: errorMessage })); Alert.alert('错误', `获取昨晚睡眠数据失败: ${errorMessage}`); } }; const renderSleepSample = (sample: SleepDataSample, index: number) => ( 样本 #{index + 1} 类型: {sample.categoryType} 时长: {HealthKitUtils.formatDuration(sample.duration)} 时间: {new Date(sample.startDate).toLocaleString('zh-CN')} - {new Date(sample.endDate).toLocaleTimeString('zh-CN')} 来源: {sample.source.name} ); return ( HealthKit 测试 {/* 状态显示 */} 状态信息 平台: {Platform.OS} HealthKit可用: {state.isAvailable ? '是' : '否'} 已授权: {state.isAuthorized ? '是' : '否'} 睡眠数据条数: {state.sleepData.length} {state.error && 错误: {state.error}} {/* 操作按钮 */} {state.loading ? '请求中...' : '请求HealthKit授权'} {state.loading ? '查询中...' : '检查权限状态'} {state.loading ? '获取中...' : '获取睡眠数据(7天)'} {state.loading ? '获取中...' : '获取昨晚睡眠'} {/* 昨晚睡眠数据 */} {state.lastNightSleep?.hasData && ( 昨晚睡眠数据 睡眠时间: {state.lastNightSleep.bedTime} - {state.lastNightSleep.wakeTime} 睡眠时长: {state.lastNightSleep.totalDurationFormatted} 样本数量: {state.lastNightSleep.samples.length} )} {/* 睡眠数据列表 */} {state.sleepData.length > 0 && ( 睡眠数据 (最近{state.sleepData.length}条) {state.sleepData.slice(0, 10).map(renderSleepSample)} {state.sleepData.length > 10 && ( 还有 {state.sleepData.length - 10} 条数据... )} )} ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#f5f5f5', }, title: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', marginBottom: 20, color: '#333', }, statusContainer: { backgroundColor: 'white', padding: 16, borderRadius: 8, marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, statusTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 8, color: '#333', }, statusText: { fontSize: 14, marginBottom: 4, color: '#666', }, errorText: { fontSize: 14, color: '#e74c3c', marginTop: 8, }, buttonContainer: { marginBottom: 16, }, button: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, marginBottom: 12, alignItems: 'center', }, buttonDisabled: { backgroundColor: '#ccc', }, buttonText: { color: 'white', fontSize: 16, fontWeight: 'bold', }, resultContainer: { backgroundColor: 'white', padding: 16, borderRadius: 8, marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, resultTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 8, color: '#333', }, resultText: { fontSize: 14, marginBottom: 4, color: '#666', }, dataContainer: { backgroundColor: 'white', padding: 16, borderRadius: 8, marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, dataTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12, color: '#333', }, sampleItem: { backgroundColor: '#f8f9fa', padding: 12, borderRadius: 6, marginBottom: 8, borderLeftWidth: 3, borderLeftColor: '#007AFF', }, sampleTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 4, color: '#333', }, sampleText: { fontSize: 12, marginBottom: 2, color: '#666', }, moreText: { textAlign: 'center', fontSize: 14, color: '#999', fontStyle: 'italic', marginTop: 8, }, }); export default HealthKitTest;