diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx index d67adb5..544169b 100644 --- a/app/(tabs)/statistics.tsx +++ b/app/(tabs)/statistics.tsx @@ -18,7 +18,7 @@ import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/mo import { fetchDailyNutritionData, selectNutritionSummaryByDate } from '@/store/nutritionSlice'; import { fetchTodayWaterStats } from '@/store/waterSlice'; import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date'; -import { ensureHealthPermissions, fetchHealthDataForDate, testHRVDataFetch } from '@/utils/health'; +import { fetchHealthDataForDate, testHRVDataFetch } from '@/utils/health'; import { getTestHealthData } from '@/utils/mockHealthData'; import { calculateNutritionGoals } from '@/utils/nutrition'; import dayjs from 'dayjs'; @@ -248,12 +248,12 @@ export default function ExploreScreen() { loadingRef.current.health = true; console.log('=== 开始HealthKit初始化流程 ==='); - const ok = await ensureHealthPermissions(); - if (!ok) { - const errorMsg = '无法获取健康权限,请确保在真实iOS设备上运行并授权应用访问健康数据'; - console.warn(errorMsg); - return; - } + // const ok = await ensureHealthPermissions(); + // if (!ok) { + // const errorMsg = '无法获取健康权限,请确保在真实iOS设备上运行并授权应用访问健康数据'; + // console.warn(errorMsg); + // return; + // } latestRequestKeyRef.current = requestKey; diff --git a/app/_layout.tsx b/app/_layout.tsx index a7ac31f..09bd40f 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -16,6 +16,7 @@ import { WaterRecordSource } from '@/services/waterRecords'; import { store } from '@/store'; import { rehydrateUserSync, setPrivacyAgreed } from '@/store/userSlice'; import { createWaterRecordAction } from '@/store/waterSlice'; +import { ensureHealthPermissions } from '@/utils/health'; import { DailySummaryNotificationHelpers, MoodNotificationHelpers, NutritionNotificationHelpers } from '@/utils/notificationHelpers'; import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync'; import React from 'react'; @@ -41,9 +42,20 @@ function Bootstrapper({ children }: { children: React.ReactNode }) { await dispatch(rehydrateUserSync()); setUserDataLoaded(true); }; + + const initHealthPermissions = async () => { + // 初始化 HealthKit 权限 + try { + console.log('开始请求 HealthKit 权限...'); + await ensureHealthPermissions(); + console.log('HealthKit 权限初始化完成'); + } catch (error) { + console.warn('HealthKit 权限初始化失败,可能在模拟器上运行:', error); + } + } + const initializeNotifications = async () => { try { - await BackgroundTaskManager.getInstance().initialize(); // 初始化通知服务 await notificationService.initialize(); @@ -102,6 +114,7 @@ function Bootstrapper({ children }: { children: React.ReactNode }) { }; loadUserData(); + initHealthPermissions(); initializeNotifications(); // 冷启动时清空 AI 教练会话缓存 clearAiCoachSessionCache(); diff --git a/components/HealthKitTest.tsx b/components/HealthKitTest.tsx new file mode 100644 index 0000000..93f032d --- /dev/null +++ b/components/HealthKitTest.tsx @@ -0,0 +1,383 @@ +/** + * 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 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 ? '获取中...' : '获取睡眠数据(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; \ No newline at end of file diff --git a/docs/healthkit-implementation.md b/docs/healthkit-implementation.md new file mode 100644 index 0000000..6360d66 --- /dev/null +++ b/docs/healthkit-implementation.md @@ -0,0 +1,229 @@ +# HealthKit Native Module 实现文档 + +本文档描述了为React Native应用添加HealthKit支持的完整实现,包括授权和睡眠数据获取功能。 + +## 功能概述 + +这个native module提供了以下功能: +1. **HealthKit授权** - 请求用户授权访问健康数据 +2. **睡眠数据获取** - 从HealthKit读取用户的睡眠分析数据 + +## 文件结构 + +``` +ios/digitalpilates/ +├── HealthKitManager.swift # Swift native module实现 +├── HealthKitManager.m # Objective-C桥接文件 +├── digitalpilates.entitlements # HealthKit权限配置 +└── Info.plist # 权限描述 + +utils/ +├── healthKit.ts # TypeScript接口定义 +├── healthKitExample.ts # 使用示例 +└── health.ts # 现有健康相关工具 +``` + +## 权限配置 + +### 1. Entitlements文件 +`ios/digitalpilates/digitalpilates.entitlements` 已包含: +```xml +com.apple.developer.healthkit + +com.apple.developer.healthkit.background-delivery + +``` + +### 2. Info.plist权限描述 +`ios/digitalpilates/Info.plist` 已包含: +```xml +NSHealthShareUsageDescription +应用需要访问您的健康数据(步数、能量消耗、心率变异性等)以展示运动统计和压力分析。 +NSHealthUpdateUsageDescription +应用需要更新您的健康数据(体重信息)以记录您的健身进度。 +``` + +## Swift实现详情 + +### HealthKitManager.swift +核心功能实现: + +#### 授权方法 +```swift +@objc func requestAuthorization( + _ resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock +) +``` + +请求的权限包括: +- 睡眠分析 (SleepAnalysis) +- 步数 (StepCount) +- 心率 (HeartRate) +- 静息心率 (RestingHeartRate) +- 心率变异性 (HeartRateVariabilitySDNN) +- 活动能量消耗 (ActiveEnergyBurned) +- 体重 (BodyMass) - 写入权限 + +#### 睡眠数据获取方法 +```swift +@objc func getSleepData( + _ options: NSDictionary, + resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock +) +``` + +支持的睡眠阶段: +- `inBed` - 在床上 +- `asleep` - 睡眠(未分类) +- `awake` - 清醒 +- `core` - 核心睡眠 +- `deep` - 深度睡眠 +- `rem` - REM睡眠 + +## TypeScript接口 + +### 主要接口 + +```typescript +interface HealthKitManagerInterface { + requestAuthorization(): Promise; + getSleepData(options?: SleepDataOptions): Promise; +} +``` + +### 数据类型 + +```typescript +interface SleepDataSample { + id: string; + startDate: string; // ISO8601格式 + endDate: string; // ISO8601格式 + value: number; + categoryType: 'inBed' | 'asleep' | 'awake' | 'core' | 'deep' | 'rem' | 'unknown'; + duration: number; // 持续时间(秒) + source: SleepDataSource; + metadata: Record; +} +``` + +## 使用示例 + +### 基本用法 + +```typescript +import HealthKitManager, { HealthKitUtils } from './utils/healthKit'; + +// 1. 检查可用性并请求授权 +const initHealthKit = async () => { + if (!HealthKitUtils.isAvailable()) { + console.log('HealthKit不可用'); + return false; + } + + try { + const result = await HealthKitManager.requestAuthorization(); + console.log('授权结果:', result); + return result.success; + } catch (error) { + console.error('授权失败:', error); + return false; + } +}; + +// 2. 获取睡眠数据 +const getSleepData = async () => { + try { + const result = await HealthKitManager.getSleepData({ + startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), // 7天前 + endDate: new Date().toISOString(), // 现在 + limit: 100 + }); + + console.log(`获取到 ${result.count} 条睡眠记录`); + return result.data; + } catch (error) { + console.error('获取睡眠数据失败:', error); + return []; + } +}; +``` + +### 高级用法 + +使用提供的 `HealthKitService` 类: + +```typescript +import { HealthKitService } from './utils/healthKitExample'; + +// 初始化并获取昨晚睡眠数据 +const checkLastNightSleep = async () => { + const initialized = await HealthKitService.initializeHealthKit(); + if (!initialized) return; + + const sleepData = await HealthKitService.getLastNightSleep(); + if (sleepData.hasData) { + console.log(`睡眠时间: ${sleepData.bedTime} - ${sleepData.wakeTime}`); + console.log(`睡眠时长: ${sleepData.totalDurationFormatted}`); + } +}; + +// 分析一周睡眠质量 +const analyzeSleep = async () => { + const analysis = await HealthKitService.analyzeSleepQuality(7); + if (analysis.hasData) { + console.log(`平均睡眠: ${analysis.summary.averageSleepFormatted}`); + } +}; +``` + +## 工具函数 + +`HealthKitUtils` 类提供了实用的工具方法: + +- `formatDuration(seconds)` - 格式化时长显示 +- `getTotalSleepDuration(samples, date)` - 计算特定日期的总睡眠时长 +- `groupSamplesByDate(samples)` - 按日期分组睡眠数据 +- `getSleepQualityMetrics(samples)` - 分析睡眠质量指标 +- `isAvailable()` - 检查HealthKit是否可用 + +## 注意事项 + +1. **仅iOS支持** - HealthKit仅在iOS设备上可用,Android设备会返回不可用状态 +2. **用户权限** - 用户可以拒绝或部分授权,需要优雅处理权限被拒绝的情况 +3. **数据可用性** - 并非所有用户都有睡眠数据,特别是没有Apple Watch的用户 +4. **隐私保护** - 严格遵循Apple的隐私指南,只请求必要的权限 +5. **后台更新** - 已配置后台HealthKit数据传输权限 + +## 错误处理 + +常见错误类型: +- `HEALTHKIT_NOT_AVAILABLE` - HealthKit不可用 +- `AUTHORIZATION_ERROR` - 授权过程出错 +- `AUTHORIZATION_DENIED` - 用户拒绝授权 +- `NOT_AUTHORIZED` - 未授权访问特定数据类型 +- `QUERY_ERROR` - 数据查询失败 + +## 扩展功能 + +如需添加更多HealthKit数据类型,可以: + +1. 在Swift文件中的 `readTypes` 数组添加新的数据类型 +2. 实现对应的查询方法 +3. 在TypeScript接口中定义新的方法和数据类型 +4. 更新Objective-C桥接文件暴露新方法 + +## 测试建议 + +1. 在真实iOS设备上测试(模拟器不支持HealthKit) +2. 使用不同的授权状态测试 +3. 测试没有睡眠数据的情况 +4. 验证数据格式和时区处理 +5. 测试错误场景的处理 + +## 相关资源 + +- [Apple HealthKit文档](https://developer.apple.com/documentation/healthkit) +- [React Native Native Modules](https://reactnative.dev/docs/native-modules-ios) +- [iOS应用权限指南](https://developer.apple.com/documentation/bundleresources/information_property_list/protected_resources) \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a0c038b..e9ff81e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,14 +3,14 @@ PODS: - DoubleConversion (1.1.6) - EXApplication (7.0.7): - ExpoModulesCore - - EXConstants (18.0.8): + - EXConstants (18.0.9): - ExpoModulesCore - EXImageLoader (6.0.0): - ExpoModulesCore - React-Core - EXNotifications (0.32.11): - ExpoModulesCore - - Expo (54.0.7): + - Expo (54.0.8): - boost - DoubleConversion - ExpoModulesCore @@ -41,7 +41,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - ExpoAppleAuthentication (8.0.6): + - ExpoAppleAuthentication (8.0.7): - ExpoModulesCore - ExpoAsset (12.0.8): - ExpoModulesCore @@ -49,7 +49,7 @@ PODS: - ExpoModulesCore - ExpoBlur (15.0.7): - ExpoModulesCore - - ExpoCamera (17.0.7): + - ExpoCamera (17.0.8): - ExpoModulesCore - ZXingObjC/OneD - ZXingObjC/PDF417 @@ -57,11 +57,11 @@ PODS: - ExpoModulesCore - ExpoFont (14.0.8): - ExpoModulesCore - - ExpoGlassEffect (0.1.3): + - ExpoGlassEffect (0.1.4): - ExpoModulesCore - - ExpoHaptics (15.0.6): + - ExpoHaptics (15.0.7): - ExpoModulesCore - - ExpoHead (6.0.4): + - ExpoHead (6.0.6): - ExpoModulesCore - RNScreens - ExpoImage (3.0.8): @@ -71,15 +71,15 @@ PODS: - SDWebImageAVIFCoder (~> 0.11.0) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.14.6) - - ExpoImagePicker (17.0.7): + - ExpoImagePicker (17.0.8): - ExpoModulesCore - ExpoKeepAwake (15.0.7): - ExpoModulesCore - - ExpoLinearGradient (15.0.6): + - ExpoLinearGradient (15.0.7): - ExpoModulesCore - ExpoLinking (8.0.8): - ExpoModulesCore - - ExpoModulesCore (3.0.15): + - ExpoModulesCore (3.0.16): - boost - DoubleConversion - fast_float @@ -108,19 +108,19 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - ExpoQuickActions (5.0.0): + - ExpoQuickActions (6.0.0): - ExpoModulesCore - - ExpoSplashScreen (31.0.8): + - ExpoSplashScreen (31.0.10): - ExpoModulesCore - ExpoSQLite (16.0.8): - ExpoModulesCore - - ExpoSymbols (1.0.6): + - ExpoSymbols (1.0.7): - ExpoModulesCore - ExpoSystemUI (6.0.7): - ExpoModulesCore - - ExpoUI (0.2.0-beta.2): + - ExpoUI (0.2.0-beta.3): - ExpoModulesCore - - ExpoWebBrowser (15.0.6): + - ExpoWebBrowser (15.0.7): - ExpoModulesCore - EXTaskManager (14.0.7): - ExpoModulesCore @@ -2012,7 +2012,7 @@ PODS: - Yoga - react-native-voice (3.2.4): - React-Core - - react-native-webview (13.15.0): + - react-native-webview (13.16.0): - boost - DoubleConversion - fast_float @@ -2604,7 +2604,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNCPicker (2.11.1): + - RNCPicker (2.11.2): - boost - DoubleConversion - fast_float @@ -2632,7 +2632,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNDateTimePicker (8.4.4): + - RNDateTimePicker (8.4.5): - boost - DoubleConversion - fast_float @@ -2844,7 +2844,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNSentry (6.20.0): + - RNSentry (7.0.1): - boost - DoubleConversion - fast_float @@ -2874,7 +2874,7 @@ PODS: - Sentry/HybridSDK (= 8.53.2) - SocketRocket - Yoga - - RNSVG (15.12.1): + - RNSVG (15.13.0): - boost - DoubleConversion - fast_float @@ -2900,10 +2900,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.12.1) + - RNSVG/common (= 15.13.0) - SocketRocket - Yoga - - RNSVG/common (15.12.1): + - RNSVG/common (15.13.0): - boost - DoubleConversion - fast_float @@ -3020,10 +3020,10 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - SDWebImage (5.21.1): - - SDWebImage/Core (= 5.21.1) - - SDWebImage/Core (5.21.1) - - SDWebImageAVIFCoder (0.11.0): + - SDWebImage (5.21.2): + - SDWebImage/Core (= 5.21.2) + - SDWebImage/Core (5.21.2) + - SDWebImageAVIFCoder (0.11.1): - libavif/core (>= 0.11.0) - SDWebImage (~> 5.10) - SDWebImageSVGCoder (1.7.0): @@ -3425,33 +3425,33 @@ SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb EXApplication: 296622817d459f46b6c5fe8691f4aac44d2b79e7 - EXConstants: 7e4654405af367ff908c863fe77a8a22d60bd37d + EXConstants: a95804601ee4a6aa7800645f9b070d753b1142b3 EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05 EXNotifications: 7a2975f4e282b827a0bc78bb1d232650cb569bbd - Expo: b7d4314594ebd7fe5eefd1a06c3b0d92b718cde0 - ExpoAppleAuthentication: 9eb1ec7213ee9c9797951df89975136db89bf8ac + Expo: f75c4161ba6b82f264daee5f52a50ac2a55d6d67 + ExpoAppleAuthentication: bc9de6e9ff3340604213ab9031d4c4f7f802623e ExpoAsset: 84810d6fed8179f04d4a7a4a6b37028bbd726e26 ExpoBackgroundTask: 22ed53b129d4d5e15c39be9fa68e45d25f6781a1 ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f - ExpoCamera: ae1d6691b05b753261a845536d2b19a9788a8750 + ExpoCamera: e75f6807a2c047f3338bbadd101af4c71a1d13a5 ExpoFileSystem: 4fb06865906e781329eb67166bd64fc4749c3019 ExpoFont: 86ceec09ffed1c99cfee36ceb79ba149074901b5 - ExpoGlassEffect: e48c949ee7dcf2072cca31389bf8fa776c1727a0 - ExpoHaptics: e0912a9cf05ba958eefdc595f1990b8f89aa1f3f - ExpoHead: 2aad68c730f967d2533599dabb64d1d2cd9f765a + ExpoGlassEffect: 744bf0c58c26a1b0212dff92856be07b98d01d8c + ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84 + ExpoHead: 78f14a8573ae5b882123b272c0af20a80bfa58f6 ExpoImage: e88f500585913969b930e13a4be47277eb7c6de8 - ExpoImagePicker: 66195293e95879fa5ee3eb1319f10b5de0ffccbb + ExpoImagePicker: d251aab45a1b1857e4156fed88511b278b4eee1c ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe - ExpoLinearGradient: 74d67832cdb0d2ef91f718d50dd82b273ce2812e + ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27 ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d - ExpoModulesCore: 5d150c790fb491ab10fe431fb794014af841258f - ExpoQuickActions: fdbda7f5874aed3dd2b1d891ec00ab3300dc7541 - ExpoSplashScreen: 1665809071bd907c6fdbfd9c09583ee4d51b41d4 + ExpoModulesCore: 654d2976c18a4a764a528928e73c4a25c8eb0e5a + ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f + ExpoSplashScreen: cbb839de72110dea1851dd3e85080b7923af2540 ExpoSQLite: 7fa091ba5562474093fef09be644161a65e11b3f - ExpoSymbols: 3efee6865b1955fe3805ca88b36e8674ce6970dd + ExpoSymbols: 1ae04ce686de719b9720453b988d8bc5bf776c68 ExpoSystemUI: 6cd74248a2282adf6dec488a75fa532d69dee314 - ExpoUI: 0f109b0549d1ae2fd955d3b8733b290c5cdeec7e - ExpoWebBrowser: 84d4438464d9754a4c1f1eaa97cd747f3752187e + ExpoUI: 68238da1f16a814f77bc64712a269440174ee898 + ExpoWebBrowser: 533bc2a1b188eec1c10e4926decf658f1687b5e5 EXTaskManager: cf225704fab8de8794a6f57f7fa41a90c0e2cd47 fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: 941bef1c8eeabd9fe1f501e30a5220beee913886 @@ -3500,7 +3500,7 @@ SPEC CHECKSUMS: react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616 react-native-voice: 908a0eba96c8c3d643e4f98b7232c6557d0a6f9c - react-native-webview: 4cbb7f05f2c50671a7dcff4012d3e85faad271e4 + react-native-webview: 654f794a7686b47491cf43aa67f7f428bea00eed React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3 React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510 @@ -3535,18 +3535,18 @@ SPEC CHECKSUMS: RNAppleHealthKit: 86ef7ab70f762b802f5c5289372de360cca701f9 RNCAsyncStorage: 29f0230e1a25f36c20b05f65e2eb8958d6526e82 RNCMaskedView: 5ef8c95cbab95334a32763b72896a7b7d07e6299 - RNCPicker: 66c392786945ecee5275242c148e6a4601221d3a - RNDateTimePicker: cda4c045beca864cebb3209ef9cc4094f974864c + RNCPicker: a7e5555ebf53e17e06c1fde62195cf07b685d26c + RNDateTimePicker: 113004837aad399a525cd391ac70b7951219ff2f RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047 RNGestureHandler: 3a73f098d74712952870e948b3d9cf7b6cae9961 RNPurchases: 1bc60e3a69af65d9cfe23967328421dd1df1763c RNReanimated: 9de34f0313c4177a34c079ca9fce6f1f278bff24 RNScreens: 0bbf16c074ae6bb1058a7bf2d1ae017f4306797c - RNSentry: f2c39f1113e22413c9bb6e3faa6b27f110d95eaf - RNSVG: 6f39605a4c4d200b11435c35bd077553c6b5963a + RNSentry: 6c63debc7b22a00cbf7d1c9ed8de43e336216545 + RNSVG: 6c39befcfad06eec55b40c19a030b2d9eca63334 RNWorklets: ad0606bee2a8103c14adb412149789c60b72bfb2 - SDWebImage: f29024626962457f3470184232766516dee8dfea - SDWebImageAVIFCoder: 00310d246aab3232ce77f1d8f0076f8c4b021d90 + SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a + SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj index 488fb74..2efd0dd 100644 --- a/ios/digitalpilates.xcodeproj/project.pbxproj +++ b/ios/digitalpilates.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -14,6 +14,8 @@ 7996A1192E6FB82300371142 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7996A1182E6FB82300371142 /* WidgetKit.framework */; }; 7996A11B2E6FB82300371142 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7996A11A2E6FB82300371142 /* SwiftUI.framework */; }; 7996A12C2E6FB82300371142 /* WaterWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7996A1172E6FB82300371142 /* WaterWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 79B2CB032E7ABBC400B51753 /* HealthKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79B2CB012E7ABBC400B51753 /* HealthKitManager.swift */; }; + 79B2CB042E7ABBC400B51753 /* HealthKitManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 79B2CB022E7ABBC400B51753 /* HealthKitManager.m */; }; BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; DC3BFC72D3A68C7493D5B44A /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D1B5F0EC906D7A2F599549 /* ExpoModulesProvider.swift */; }; F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; @@ -52,6 +54,8 @@ 7996A1182E6FB82300371142 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 7996A11A2E6FB82300371142 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 7996A1322E6FB84A00371142 /* WaterWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WaterWidgetExtension.entitlements; sourceTree = ""; }; + 79B2CB012E7ABBC400B51753 /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HealthKitManager.swift; path = digitalpilates/HealthKitManager.swift; sourceTree = ""; }; + 79B2CB022E7ABBC400B51753 /* HealthKitManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = HealthKitManager.m; path = digitalpilates/HealthKitManager.m; sourceTree = ""; }; 7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = ""; }; 83D1B5F0EC906D7A2F599549 /* 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 = ""; }; @@ -64,7 +68,7 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 7996A1302E6FB82300371142 /* Exceptions for "WaterWidget" folder in "WaterWidgetExtension" target */ = { + 7996A1302E6FB82300371142 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, @@ -74,18 +78,7 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 7996A11C2E6FB82300371142 /* WaterWidget */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 7996A1302E6FB82300371142 /* Exceptions for "WaterWidget" folder in "WaterWidgetExtension" target */, - ); - explicitFileTypes = { - }; - explicitFolders = ( - ); - path = WaterWidget; - sourceTree = ""; - }; + 7996A11C2E6FB82300371142 /* WaterWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (7996A1302E6FB82300371142 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = WaterWidget; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -153,6 +146,8 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 79B2CB022E7ABBC400B51753 /* HealthKitManager.m */, + 79B2CB012E7ABBC400B51753 /* HealthKitManager.swift */, 7996A1322E6FB84A00371142 /* WaterWidgetExtension.entitlements */, 13B07FAE1A68108700A75B9A /* digitalpilates */, 832341AE1AAA6A7D00B99B32 /* Libraries */, @@ -450,6 +445,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 79B2CB032E7ABBC400B51753 /* HealthKitManager.swift in Sources */, + 79B2CB042E7ABBC400B51753 /* HealthKitManager.m in Sources */, F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */, DC3BFC72D3A68C7493D5B44A /* ExpoModulesProvider.swift in Sources */, ); @@ -490,7 +487,7 @@ ); INFOPLIST_FILE = digitalpilates/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Out Live"; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -528,7 +525,7 @@ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; INFOPLIST_FILE = digitalpilates/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Out Live"; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -699,10 +696,7 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -757,10 +751,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/ios/digitalpilates/HealthKitManager.m b/ios/digitalpilates/HealthKitManager.m new file mode 100644 index 0000000..af0e243 --- /dev/null +++ b/ios/digitalpilates/HealthKitManager.m @@ -0,0 +1,22 @@ +// +// HealthKitManager.m +// digitalpilates +// +// React Native bridge for HealthKitManager +// + +#import + +@interface RCT_EXTERN_MODULE(HealthKitManager, NSObject) + +// Authorization method +RCT_EXTERN_METHOD(requestAuthorization:(RCTPromiseResolveBlock)resolver + rejecter:(RCTPromiseRejectBlock)rejecter) + +// Sleep data method +RCT_EXTERN_METHOD(getSleepData:(NSDictionary *)options + resolver:(RCTPromiseResolveBlock)resolver + rejecter:(RCTPromiseRejectBlock)rejecter) + + +@end \ No newline at end of file diff --git a/ios/digitalpilates/HealthKitManager.swift b/ios/digitalpilates/HealthKitManager.swift new file mode 100644 index 0000000..00091e5 --- /dev/null +++ b/ios/digitalpilates/HealthKitManager.swift @@ -0,0 +1,216 @@ +// +// HealthKitManager.swift +// digitalpilates +// +// Native module for HealthKit authorization and sleep data access +// + +import Foundation +import React +import HealthKit + +@objc(HealthKitManager) +class HealthKitManager: NSObject, RCTBridgeModule { + + // HealthKit store instance + private let healthStore = HKHealthStore() + + static func moduleName() -> String! { + return "HealthKitManager" + } + + static func requiresMainQueueSetup() -> Bool { + return true + } + + // MARK: - Authorization + + @objc + func requestAuthorization( + _ resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock + ) { + + // Check if HealthKit is available on the device + guard HKHealthStore.isHealthDataAvailable() else { + rejecter("HEALTHKIT_NOT_AVAILABLE", "HealthKit is not available on this device", nil) + return + } + + // Define the data types we want to read + let readTypes: Set = [ + HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!, + HKObjectType.quantityType(forIdentifier: .stepCount)!, + HKObjectType.quantityType(forIdentifier: .heartRate)!, + HKObjectType.quantityType(forIdentifier: .restingHeartRate)!, + HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!, + HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)! + ] + + // Define the data types we want to write (if any) + let writeTypes: Set = [ + HKObjectType.quantityType(forIdentifier: .bodyMass)! + ] + + // Request authorization + healthStore.requestAuthorization(toShare: writeTypes, read: readTypes) { [weak self] (success, error) in + DispatchQueue.main.async { + if let error = error { + rejecter("AUTHORIZATION_ERROR", "Failed to authorize HealthKit: \(error.localizedDescription)", error) + return + } + + if success { + // Check individual permissions + var permissions: [String: Any] = [:] + + for type in readTypes { + let status = self?.healthStore.authorizationStatus(for: type) + let statusString = self?.authorizationStatusToString(status) + permissions[type.identifier] = statusString + } + + let result: [String: Any] = [ + "success": true, + "permissions": permissions + ] + + resolver(result) + } else { + rejecter("AUTHORIZATION_DENIED", "User denied HealthKit authorization", nil) + } + } + } + } + + // MARK: - Sleep Data + + @objc + func getSleepData( + _ 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 + } + + // Check authorization status for sleep analysis + let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)! + let authStatus = healthStore.authorizationStatus(for: sleepType) + + guard authStatus == .sharingAuthorized else { + rejecter("NOT_AUTHORIZED", "Not authorized to read sleep data", nil) + return + } + + // Parse options + let startDate = parseDate(from: options["startDate"] as? String) ?? Calendar.current.date(byAdding: .day, value: -7, to: Date())! + let endDate = parseDate(from: options["endDate"] as? String) ?? Date() + let limit = options["limit"] as? Int ?? 100 + + // Create predicate for date range + let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate) + + // Create sort descriptor to get latest data first + let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) + + // Create query + let query = HKSampleQuery( + sampleType: sleepType, + predicate: predicate, + limit: limit, + sortDescriptors: [sortDescriptor] + ) { [weak self] (query, samples, error) in + + DispatchQueue.main.async { + if let error = error { + rejecter("QUERY_ERROR", "Failed to query sleep data: \(error.localizedDescription)", error) + return + } + + guard let sleepSamples = samples as? [HKCategorySample] else { + resolver([]) + return + } + + let sleepData = sleepSamples.map { sample in + return [ + "id": sample.uuid.uuidString, + "startDate": self?.dateToISOString(sample.startDate) ?? "", + "endDate": self?.dateToISOString(sample.endDate) ?? "", + "value": sample.value, + "categoryType": self?.sleepValueToString(sample.value) ?? "unknown", + "duration": sample.endDate.timeIntervalSince(sample.startDate), + "source": [ + "name": sample.sourceRevision.source.name, + "bundleIdentifier": sample.sourceRevision.source.bundleIdentifier + ], + "metadata": sample.metadata ?? [:] + ] + } + + let result: [String: Any] = [ + "data": sleepData, + "count": sleepData.count, + "startDate": self?.dateToISOString(startDate) ?? "", + "endDate": self?.dateToISOString(endDate) ?? "" + ] + + resolver(result) + } + } + + healthStore.execute(query) + } + + // MARK: - Helper Methods + + private func authorizationStatusToString(_ status: HKAuthorizationStatus?) -> String { + guard let status = status else { return "notDetermined" } + + switch status { + case .notDetermined: + return "notDetermined" + case .sharingDenied: + return "denied" + case .sharingAuthorized: + return "authorized" + @unknown default: + return "unknown" + } + } + + private func sleepValueToString(_ value: Int) -> String { + switch value { + case HKCategoryValueSleepAnalysis.inBed.rawValue: + return "inBed" + case HKCategoryValueSleepAnalysis.asleepUnspecified.rawValue: + return "asleep" + case HKCategoryValueSleepAnalysis.awake.rawValue: + return "awake" + case HKCategoryValueSleepAnalysis.asleepCore.rawValue: + return "core" + case HKCategoryValueSleepAnalysis.asleepDeep.rawValue: + return "deep" + case HKCategoryValueSleepAnalysis.asleepREM.rawValue: + return "rem" + default: + return "unknown" + } + } + + private func parseDate(from string: String?) -> Date? { + guard let string = string else { return nil } + + let formatter = ISO8601DateFormatter() + return formatter.date(from: string) + } + + private func dateToISOString(_ date: Date) -> String { + let formatter = ISO8601DateFormatter() + return formatter.string(from: date) + } +} \ No newline at end of file diff --git a/ios/digitalpilates/digitalpilates-Bridging-Header.h b/ios/digitalpilates/digitalpilates-Bridging-Header.h index 8361941..6682446 100644 --- a/ios/digitalpilates/digitalpilates-Bridging-Header.h +++ b/ios/digitalpilates/digitalpilates-Bridging-Header.h @@ -1,3 +1,5 @@ // // Use this file to import your target's public headers that you would like to expose to Swift. // +#import +#import \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2c236a0..d06793a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,44 +8,44 @@ "name": "digital-pilates", "version": "1.0.2", "dependencies": { - "@expo/ui": "~0.2.0-beta.2", + "@expo/ui": "~0.2.0-beta.3", "@expo/vector-icons": "^15.0.2", "@react-native-async-storage/async-storage": "^2.2.0", - "@react-native-community/datetimepicker": "^8.4.4", + "@react-native-community/datetimepicker": "8.4.4", "@react-native-masked-view/masked-view": "^0.3.2", - "@react-native-picker/picker": "^2.11.1", + "@react-native-picker/picker": "2.11.1", "@react-native-voice/voice": "^3.2.4", - "@react-navigation/bottom-tabs": "^7.4.7", + "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.4", - "@react-navigation/native": "^7.1.17", + "@react-navigation/native": "^7.1.8", "@reduxjs/toolkit": "^2.9.0", "@sentry/react-native": "~6.20.0", "@types/lodash": "^4.17.20", "cos-js-sdk-v5": "^1.6.0", - "dayjs": "^1.11.13", - "expo": "^54.0.7", - "expo-apple-authentication": "~8.0.6", + "dayjs": "^1.11.18", + "expo": "^54.0.8", + "expo-apple-authentication": "~8.0.7", "expo-background-task": "~1.0.7", "expo-blur": "~15.0.7", - "expo-camera": "~17.0.7", - "expo-constants": "~18.0.8", + "expo-camera": "~17.0.8", + "expo-constants": "~18.0.9", "expo-font": "~14.0.8", - "expo-glass-effect": "^0.1.3", - "expo-haptics": "~15.0.6", + "expo-glass-effect": "^0.1.4", + "expo-haptics": "~15.0.7", "expo-image": "~3.0.8", - "expo-image-picker": "~17.0.7", - "expo-linear-gradient": "~15.0.6", + "expo-image-picker": "~17.0.8", + "expo-linear-gradient": "~15.0.7", "expo-linking": "~8.0.8", "expo-notifications": "~0.32.11", - "expo-quick-actions": "^5.0.0", - "expo-router": "~6.0.4", - "expo-splash-screen": "~31.0.8", + "expo-quick-actions": "^6.0.0", + "expo-router": "~6.0.6", + "expo-splash-screen": "~31.0.10", "expo-sqlite": "^16.0.8", - "expo-status-bar": "~3.0.7", - "expo-symbols": "~1.0.6", + "expo-status-bar": "~3.0.8", + "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.7", "expo-task-manager": "~14.0.6", - "expo-web-browser": "~15.0.6", + "expo-web-browser": "~15.0.7", "lodash": "^4.17.21", "lottie-react-native": "^7.3.4", "react": "19.1.0", @@ -58,22 +58,22 @@ "react-native-markdown-display": "^7.0.2", "react-native-modal-datetime-picker": "^18.0.0", "react-native-popover-view": "^6.1.0", - "react-native-purchases": "^9.4.3", + "react-native-purchases": "^9.2.2", "react-native-reanimated": "~4.1.0", "react-native-render-html": "^6.3.4", - "react-native-safe-area-context": "~5.6.0", + "react-native-safe-area-context": "~5.6.1", "react-native-screens": "~4.16.0", - "react-native-svg": "^15.12.1", + "react-native-svg": "15.12.1", "react-native-toast-message": "^2.3.3", - "react-native-web": "^0.21.0", + "react-native-web": "^0.21.1", "react-native-webview": "13.15.0", "react-native-wheel-picker-expo": "^0.5.4", "react-redux": "^9.2.0" }, "devDependencies": { - "@babel/core": "^7.25.2", - "@types/react": "~19.1.12", - "eslint": "^9.25.0", + "@babel/core": "^7.28.4", + "@types/react": "~19.1.13", + "eslint": "^9.35.0", "eslint-config-expo": "~10.0.0", "typescript": "~5.9.2" } @@ -92,19 +92,6 @@ } } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -129,21 +116,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.4", + "resolved": "https://mirrors.tencent.com/npm/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -292,14 +279,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://mirrors.tencent.com/npm/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -418,13 +405,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", - "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "version": "7.28.4", + "resolved": "https://mirrors.tencent.com/npm/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1630,9 +1617,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://mirrors.tencent.com/npm/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1734,9 +1721,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", - "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "version": "9.35.0", + "resolved": "https://mirrors.tencent.com/npm/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", "dev": true, "license": "MIT", "engines": { @@ -1917,9 +1904,9 @@ } }, "node_modules/@expo/fingerprint": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.15.0.tgz", - "integrity": "sha512-PrLA6fxScZfnLy7OHZ2GHXsDG9YbE7L5DbNhion6j/U/O+FQgz4VbxJarW5C00kMg1ll2u6EghB7ENAvL1T4qg==", + "version": "0.15.1", + "resolved": "https://mirrors.tencent.com/npm/@expo/fingerprint/-/fingerprint-0.15.1.tgz", + "integrity": "sha512-U1S9DwiapCHQjHdHDDyO/oXsl/1oEHSHZRRkWDDrHgXRUDiAVIySw9Unvvcr118Ee6/x4NmKSZY1X0VagrqmFg==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -1940,7 +1927,7 @@ }, "node_modules/@expo/fingerprint/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "resolved": "https://mirrors.tencent.com/npm/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { @@ -1949,7 +1936,7 @@ }, "node_modules/@expo/fingerprint/node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "resolved": "https://mirrors.tencent.com/npm/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { @@ -1964,7 +1951,7 @@ }, "node_modules/@expo/fingerprint/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { @@ -1975,9 +1962,9 @@ } }, "node_modules/@expo/image-utils": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.7.6.tgz", - "integrity": "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw==", + "version": "0.8.7", + "resolved": "https://mirrors.tencent.com/npm/@expo/image-utils/-/image-utils-0.8.7.tgz", + "integrity": "sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -1986,6 +1973,7 @@ "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0", "semver": "^7.6.0", "temp-dir": "~2.0.0", "unique-string": "~2.0.0" @@ -1993,7 +1981,7 @@ }, "node_modules/@expo/image-utils/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { @@ -2022,6 +2010,46 @@ "@babel/highlight": "^7.10.4" } }, + "node_modules/@expo/mcp-tunnel": { + "version": "0.0.7", + "resolved": "https://mirrors.tencent.com/npm/@expo/mcp-tunnel/-/mcp-tunnel-0.0.7.tgz", + "integrity": "sha512-ht8Q1nKtiHobZqkUqt/7awwjW2D59ardP6XDVmGceGjQtoZELVaJDHyMIX+aVG9SZ9aj8+uGlhQYeBi57SZPMA==", + "license": "MIT", + "dependencies": { + "ws": "^8.18.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.24.6" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.13.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@expo/mcp-tunnel/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://mirrors.tencent.com/npm/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@expo/metro": { "version": "0.1.1", "resolved": "https://mirrors.tencent.com/npm/@expo/metro/-/metro-0.1.1.tgz", @@ -2140,9 +2168,9 @@ } }, "node_modules/@expo/package-manager": { - "version": "1.9.7", - "resolved": "https://mirrors.tencent.com/npm/@expo/package-manager/-/package-manager-1.9.7.tgz", - "integrity": "sha512-k3uky8Qzlv21rxuPvP2KUTAy8NI0b/LP7BSXcwJpS/rH7RmiAqUXgzPar3I1OmKGgxpod78Y9Mae//F8d3aiOQ==", + "version": "1.9.8", + "resolved": "https://mirrors.tencent.com/npm/@expo/package-manager/-/package-manager-1.9.8.tgz", + "integrity": "sha512-4/I6OWquKXYnzo38pkISHCOCOXxfeEmu4uDoERq1Ei/9Ur/s9y3kLbAamEkitUkDC7gHk1INxRWEfFNzGbmOrA==", "license": "MIT", "dependencies": { "@expo/json-file": "^10.0.7", @@ -2165,17 +2193,17 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-10.0.8.tgz", - "integrity": "sha512-9ibcRuWngmMnoYe25XXfZEWcPCdx6LiyzqHqpojsvHBI+sMsyZPf4b/5y/zmeJy3PKjR4LSzMRonEitTfUSL/A==", + "version": "54.0.3", + "resolved": "https://mirrors.tencent.com/npm/@expo/prebuild-config/-/prebuild-config-54.0.3.tgz", + "integrity": "sha512-okf6Umaz1VniKmm+pA37QHBzB9XlRHvO1Qh3VbUezy07LTkz87kXUW7uLMmrA319WLavWSVORTXeR0jBRihObA==", "license": "MIT", "dependencies": { - "@expo/config": "~12.0.7", - "@expo/config-plugins": "~11.0.7", - "@expo/config-types": "^54.0.7", - "@expo/image-utils": "^0.8.6", - "@expo/json-file": "^10.0.6", - "@react-native/normalize-colors": "0.81.1", + "@expo/config": "~12.0.9", + "@expo/config-plugins": "~54.0.1", + "@expo/config-types": "^54.0.8", + "@expo/image-utils": "^0.8.7", + "@expo/json-file": "^10.0.7", + "@react-native/normalize-colors": "0.81.4", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", @@ -2185,49 +2213,9 @@ "expo": "*" } }, - "node_modules/@expo/prebuild-config/node_modules/@expo/config-plugins": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-11.0.7.tgz", - "integrity": "sha512-kak5m27fPTzwmzYPbaYL6I67OFnhdrzV0h5JcoljrEC7uM3R18V/RrnEMzv10XQk+s+qmPfMkr0aK9YYgGqR6g==", - "license": "MIT", - "dependencies": { - "@expo/config-types": "^54.0.7", - "@expo/json-file": "~10.0.6", - "@expo/plist": "^0.4.6", - "@expo/sdk-runtime-versions": "^1.0.0", - "chalk": "^4.1.2", - "debug": "^4.3.5", - "getenv": "^2.0.0", - "glob": "^10.4.2", - "resolve-from": "^5.0.0", - "semver": "^7.5.4", - "slash": "^3.0.0", - "slugify": "^1.6.6", - "xcode": "^3.0.1", - "xml2js": "0.6.0" - } - }, - "node_modules/@expo/prebuild-config/node_modules/@expo/image-utils": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.7.tgz", - "integrity": "sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==", - "license": "MIT", - "dependencies": { - "@expo/spawn-async": "^1.7.2", - "chalk": "^4.0.0", - "getenv": "^2.0.0", - "jimp-compact": "0.16.1", - "parse-png": "^2.1.0", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0", - "semver": "^7.6.0", - "temp-dir": "~2.0.0", - "unique-string": "~2.0.0" - } - }, "node_modules/@expo/prebuild-config/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { @@ -2281,9 +2269,9 @@ "license": "MIT" }, "node_modules/@expo/ui": { - "version": "0.2.0-beta.2", - "resolved": "https://mirrors.tencent.com/npm/@expo/ui/-/ui-0.2.0-beta.2.tgz", - "integrity": "sha512-Pf/Nr9k4flJAbMfNMJ1GkaSNAk6VFC90QLH2Ux/e5KOQI5p69ttYANg2xQLw4G40TsBMFhFplAG78sOZfhLmaA==", + "version": "0.2.0-beta.3", + "resolved": "https://mirrors.tencent.com/npm/@expo/ui/-/ui-0.2.0-beta.3.tgz", + "integrity": "sha512-o2gtJm8Y1LZjRzapE1q26C6B6RBhyj4jEeBlfOCIWi5R72W+tOX1N5QAxpV9AwZlWi39Y7BSS2vtocQhFEJWHQ==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -2679,6 +2667,16 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://mirrors.tencent.com/npm/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -3820,9 +3818,9 @@ "license": "MIT" }, "node_modules/@react-native/normalize-colors": { - "version": "0.81.1", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.1.tgz", - "integrity": "sha512-TsaeZlE8OYFy3PSWc+1VBmAzI2T3kInzqxmwXoGU4w1d4XFkQFg271Ja9GmDi9cqV3CnBfqoF9VPwRxVlc/l5g==", + "version": "0.81.4", + "resolved": "https://mirrors.tencent.com/npm/@react-native/normalize-colors/-/normalize-colors-0.81.4.tgz", + "integrity": "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg==", "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { @@ -3996,7 +3994,7 @@ }, "node_modules/@sentry-internal/browser-utils": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz", "integrity": "sha512-ROgqtQfpH/82AQIpESPqPQe0UyWywKJsmVIqi3c5Fh+zkds5LUxnssTj3yNd1x+kxaPDVB023jAP+3ibNgeNDw==", "license": "MIT", "dependencies": { @@ -4008,7 +4006,7 @@ }, "node_modules/@sentry-internal/feedback": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/feedback/-/feedback-8.55.0.tgz", "integrity": "sha512-cP3BD/Q6pquVQ+YL+rwCnorKuTXiS9KXW8HNKu4nmmBAyf7urjs+F6Hr1k9MXP5yQ8W3yK7jRWd09Yu6DHWOiw==", "license": "MIT", "dependencies": { @@ -4020,7 +4018,7 @@ }, "node_modules/@sentry-internal/replay": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/replay/-/replay-8.55.0.tgz", "integrity": "sha512-roCDEGkORwolxBn8xAKedybY+Jlefq3xYmgN2fr3BTnsXjSYOPC7D1/mYqINBat99nDtvgFvNfRcZPiwwZ1hSw==", "license": "MIT", "dependencies": { @@ -4033,7 +4031,7 @@ }, "node_modules/@sentry-internal/replay-canvas": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/replay-canvas/-/replay-canvas-8.55.0.tgz", "integrity": "sha512-nIkfgRWk1091zHdu4NbocQsxZF1rv1f7bbp3tTIlZYbrH62XVZosx5iHAuZG0Zc48AETLE7K4AX9VGjvQj8i9w==", "license": "MIT", "dependencies": { @@ -4046,7 +4044,7 @@ }, "node_modules/@sentry/babel-plugin-component-annotate": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.1.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.1.1.tgz", "integrity": "sha512-HUpqrCK7zDVojTV6KL6BO9ZZiYrEYQqvYQrscyMsq04z+WCupXaH6YEliiNRvreR8DBJgdsG3lBRpebhUGmvfA==", "license": "MIT", "engines": { @@ -4055,7 +4053,7 @@ }, "node_modules/@sentry/browser": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/browser/-/browser-8.55.0.tgz", "integrity": "sha512-1A31mCEWCjaMxJt6qGUK+aDnLDcK6AwLAZnqpSchNysGni1pSn1RWSmk9TBF8qyTds5FH8B31H480uxMPUJ7Cw==", "license": "MIT", "dependencies": { @@ -4071,7 +4069,7 @@ }, "node_modules/@sentry/cli": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli/-/cli-2.51.1.tgz", "integrity": "sha512-FU+54kNcKJABU0+ekvtnoXHM9zVrDe1zXVFbQT7mS0On0m1P0zFRGdzbnWe2XzpzuEAJXtK6aog/W+esRU9AIA==", "hasInstallScript": true, "license": "BSD-3-Clause", @@ -4101,7 +4099,7 @@ }, "node_modules/@sentry/cli-darwin": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-darwin/-/cli-darwin-2.51.1.tgz", "integrity": "sha512-R1u8IQdn/7Rr8sf6bVVr0vJT4OqwCFdYsS44Y3OoWGVJW2aAQTWRJOTlV4ueclVLAyUQzmgBjfR8AtiUhd/M5w==", "license": "BSD-3-Clause", "optional": true, @@ -4114,7 +4112,7 @@ }, "node_modules/@sentry/cli-linux-arm": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-arm/-/cli-linux-arm-2.51.1.tgz", "integrity": "sha512-Klro17OmSSKOOSaxVKBBNPXet2+HrIDZUTSp8NRl4LQsIubdc1S/aQ79cH/g52Muwzpl3aFwPxyXw+46isfEgA==", "cpu": [ "arm" @@ -4132,7 +4130,7 @@ }, "node_modules/@sentry/cli-linux-arm64": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.51.1.tgz", "integrity": "sha512-nvA/hdhsw4bKLhslgbBqqvETjXwN1FVmwHLOrRvRcejDO6zeIKUElDiL5UOjGG0NC+62AxyNw5ri8Wzp/7rg9Q==", "cpu": [ "arm64" @@ -4150,7 +4148,7 @@ }, "node_modules/@sentry/cli-linux-i686": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-i686/-/cli-linux-i686-2.51.1.tgz", "integrity": "sha512-jp4TmR8VXBdT9dLo6mHniQHN0xKnmJoPGVz9h9VDvO2Vp/8o96rBc555D4Am5wJOXmfuPlyjGcmwHlB3+kQRWw==", "cpu": [ "x86", @@ -4169,7 +4167,7 @@ }, "node_modules/@sentry/cli-linux-x64": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-x64/-/cli-linux-x64-2.51.1.tgz", "integrity": "sha512-JuLt0MXM2KHNFmjqXjv23sly56mJmUQzGBWktkpY3r+jE08f5NLKPd5wQ6W/SoLXGIOKnwLz0WoUg7aBVyQdeQ==", "cpu": [ "x64" @@ -4187,7 +4185,7 @@ }, "node_modules/@sentry/cli-win32-arm64": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.51.1.tgz", "integrity": "sha512-PiwjTdIFDazTQCTyDCutiSkt4omggYSKnO3HE1+LDjElsFrWY9pJs4fU3D40WAyE2oKu0MarjNH/WxYGdqEAlg==", "cpu": [ "arm64" @@ -4203,7 +4201,7 @@ }, "node_modules/@sentry/cli-win32-i686": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-i686/-/cli-win32-i686-2.51.1.tgz", "integrity": "sha512-TMvZZpeiI2HmrDFNVQ0uOiTuYKvjEGOZdmUxe3WlhZW82A/2Oka7sQ24ljcOovbmBOj5+fjCHRUMYvLMCWiysA==", "cpu": [ "x86", @@ -4220,7 +4218,7 @@ }, "node_modules/@sentry/cli-win32-x64": { "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.51.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-x64/-/cli-win32-x64-2.51.1.tgz", "integrity": "sha512-v2hreYUPPTNK1/N7+DeX7XBN/zb7p539k+2Osf0HFyVBaoUC3Y3+KBwSf4ASsnmgTAK7HCGR+X0NH1vP+icw4w==", "cpu": [ "x64" @@ -4236,7 +4234,7 @@ }, "node_modules/@sentry/cli/node_modules/agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "resolved": "https://mirrors.tencent.com/npm/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { @@ -4248,7 +4246,7 @@ }, "node_modules/@sentry/cli/node_modules/https-proxy-agent": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "resolved": "https://mirrors.tencent.com/npm/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { @@ -4261,7 +4259,7 @@ }, "node_modules/@sentry/core": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/core/-/core-8.55.0.tgz", "integrity": "sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==", "license": "MIT", "engines": { @@ -4270,7 +4268,7 @@ }, "node_modules/@sentry/react": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/react/-/react-8.55.0.tgz", "integrity": "sha512-/qNBvFLpvSa/Rmia0jpKfJdy16d4YZaAnH/TuKLAtm0BWlsPQzbXCU4h8C5Hsst0Do0zG613MEtEmWpWrVOqWA==", "license": "MIT", "dependencies": { @@ -4287,7 +4285,7 @@ }, "node_modules/@sentry/react-native": { "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-6.20.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/react-native/-/react-native-6.20.0.tgz", "integrity": "sha512-YngSba14Hsb5t/ZNMOyxb/HInmYRL5pQ74BkoMBQ/UBBM5kWHgSILxoO2XkKYtaaJXrkSJj+kBalELHblz9h5g==", "license": "MIT", "dependencies": { @@ -4315,7 +4313,7 @@ }, "node_modules/@sentry/types": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/types/-/types-8.55.0.tgz", "integrity": "sha512-6LRT0+r6NWQ+RtllrUW2yQfodST0cJnkOmdpHA75vONgBUhpKwiJ4H7AmgfoTET8w29pU6AnntaGOe0LJbOmog==", "license": "MIT", "dependencies": { @@ -4327,7 +4325,7 @@ }, "node_modules/@sentry/utils": { "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.55.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/@sentry/utils/-/utils-8.55.0.tgz", "integrity": "sha512-cYcl39+xcOivBpN9d8ZKbALl+DxZKo/8H0nueJZ0PO4JA+MJGhSm6oHakXxLPaiMoNLTX7yor8ndnQIuFg+vmQ==", "license": "MIT", "dependencies": { @@ -4509,9 +4507,9 @@ } }, "node_modules/@types/react": { - "version": "19.1.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", - "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", + "version": "19.1.13", + "resolved": "https://mirrors.tencent.com/npm/@types/react/-/react-19.1.13.tgz", + "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -5338,7 +5336,7 @@ }, "node_modules/arg": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "resolved": "https://mirrors.tencent.com/npm/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, @@ -6339,7 +6337,7 @@ }, "node_modules/commander": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { @@ -6694,9 +6692,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "version": "1.11.18", + "resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", "license": "MIT" }, "node_modules/debug": { @@ -7232,19 +7230,19 @@ } }, "node_modules/eslint": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", - "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "version": "9.35.0", + "resolved": "https://mirrors.tencent.com/npm/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.33.0", + "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -7668,29 +7666,29 @@ "license": "MIT" }, "node_modules/expo": { - "version": "54.0.7", - "resolved": "https://mirrors.tencent.com/npm/expo/-/expo-54.0.7.tgz", - "integrity": "sha512-DftN6nMdpHYUCw5Xnh7+h7wnusjtly4JzQknvuD7MzIvqoyJL9uffQyMQrmZkXrUbgm+cKBm47vtooIz4qj0Qg==", + "version": "54.0.8", + "resolved": "https://mirrors.tencent.com/npm/expo/-/expo-54.0.8.tgz", + "integrity": "sha512-H4nUVvAczd9ZPWrAR3oXxEr/EkLfPxXg5gBvFgZI4WnGNthehqEYB37urXgj9fvgSBxNaRUkySL4uwr9VB2J8Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "54.0.5", + "@expo/cli": "54.0.6", "@expo/config": "~12.0.9", "@expo/config-plugins": "~54.0.1", "@expo/devtools": "0.1.7", - "@expo/fingerprint": "0.15.0", + "@expo/fingerprint": "0.15.1", "@expo/metro": "~0.1.1", "@expo/metro-config": "54.0.3", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~54.0.1", "expo-asset": "~12.0.8", - "expo-constants": "~18.0.8", + "expo-constants": "~18.0.9", "expo-file-system": "~19.0.14", "expo-font": "~14.0.8", "expo-keep-awake": "~15.0.7", - "expo-modules-autolinking": "3.0.10", - "expo-modules-core": "3.0.15", + "expo-modules-autolinking": "3.0.11", + "expo-modules-core": "3.0.16", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-without-unicode": "8.0.0-3" @@ -7720,9 +7718,9 @@ } }, "node_modules/expo-apple-authentication": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/expo-apple-authentication/-/expo-apple-authentication-8.0.6.tgz", - "integrity": "sha512-Jwy/UTnjS88c6rE5wuxvYJKTgGtQyYBwG5kisMOqtiCm8BeHMA1s/4lpvrDXhsgW3RrgcCGo0rRreZvIkiWO+w==", + "version": "8.0.7", + "resolved": "https://mirrors.tencent.com/npm/expo-apple-authentication/-/expo-apple-authentication-8.0.7.tgz", + "integrity": "sha512-KHLKecxwlPm42W/JYEefcFcXu5BW88wlgKSoikOFwRoWpzzryJxsNacMJRqrzAP3lFecPAK+ATgyJYvFkp10kw==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -7753,36 +7751,6 @@ "react-native": "*" } }, - "node_modules/expo-asset/node_modules/@expo/image-utils": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.7.tgz", - "integrity": "sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==", - "license": "MIT", - "dependencies": { - "@expo/spawn-async": "^1.7.2", - "chalk": "^4.0.0", - "getenv": "^2.0.0", - "jimp-compact": "0.16.1", - "parse-png": "^2.1.0", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0", - "semver": "^7.6.0", - "temp-dir": "~2.0.0", - "unique-string": "~2.0.0" - } - }, - "node_modules/expo-asset/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/expo-background-task": { "version": "1.0.7", "resolved": "https://mirrors.tencent.com/npm/expo-background-task/-/expo-background-task-1.0.7.tgz", @@ -7807,9 +7775,9 @@ } }, "node_modules/expo-camera": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-17.0.7.tgz", - "integrity": "sha512-jdZfijfFjlVAuuIkDheA41YKpigPjqsN0juRvgyr7Lcyz+fvwZ3/RP50/n/hvuozH657wHxPfiyVVFa00z8TcQ==", + "version": "17.0.8", + "resolved": "https://mirrors.tencent.com/npm/expo-camera/-/expo-camera-17.0.8.tgz", + "integrity": "sha512-BIGvS+3myaYqMtk2VXWgdcOMrewH+55BttmaYqq9tv9+o5w+RAbH9wlJSt0gdaswikiyzoWT7mOnLDleYClXmw==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -7827,12 +7795,12 @@ } }, "node_modules/expo-constants": { - "version": "18.0.8", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.8.tgz", - "integrity": "sha512-Tetphsx6RVImCTZeBAclRQMy0WOODY3y6qrUoc88YGUBVm8fAKkErCSWxLTCc6nFcJxdoOMYi62LgNIUFjZCLA==", + "version": "18.0.9", + "resolved": "https://mirrors.tencent.com/npm/expo-constants/-/expo-constants-18.0.9.tgz", + "integrity": "sha512-sqoXHAOGDcr+M9NlXzj1tGoZyd3zxYDy215W6E0Z0n8fgBaqce9FAYQE2bu5X4G629AYig5go7U6sQz7Pjcm8A==", "license": "MIT", "dependencies": { - "@expo/config": "~12.0.8", + "@expo/config": "~12.0.9", "@expo/env": "~2.0.7" }, "peerDependencies": { @@ -7865,9 +7833,9 @@ } }, "node_modules/expo-glass-effect": { - "version": "0.1.3", - "resolved": "https://mirrors.tencent.com/npm/expo-glass-effect/-/expo-glass-effect-0.1.3.tgz", - "integrity": "sha512-wGWS8DdenyqwBHpVKwFCishtB08HD4SW6SZjIx9BXw92q/9b9fiygBypFob9dT0Mct6d05g7XRBRZ8Ryw5rYIg==", + "version": "0.1.4", + "resolved": "https://mirrors.tencent.com/npm/expo-glass-effect/-/expo-glass-effect-0.1.4.tgz", + "integrity": "sha512-RW7lf2SWNVSvpJqBxQUlC/VVzeeHFrJeoKoIlNIFfKTFj3HyF7xQKq6/h/bpO8M0ssy0v5KEKw4GggkjTwx7Fw==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -7876,9 +7844,9 @@ } }, "node_modules/expo-haptics": { - "version": "15.0.6", - "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-15.0.6.tgz", - "integrity": "sha512-Unns//atxLq4UkDrgvwNpzOB2pm6hPS1nhZVL8BIfOebU/++JQlKJYPam4sRn7w/vMbRYpJ8BmC96mRs3QR0Tg==", + "version": "15.0.7", + "resolved": "https://mirrors.tencent.com/npm/expo-haptics/-/expo-haptics-15.0.7.tgz", + "integrity": "sha512-7flWsYPrwjJxZ8x82RiJtzsnk1Xp9ahnbd9PhCy3NnsemyMApoWIEUr4waPqFr80DtiLZfhD9VMLL1CKa8AImQ==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -7911,9 +7879,9 @@ } }, "node_modules/expo-image-picker": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-17.0.7.tgz", - "integrity": "sha512-U/xuSqA8RyTHsVqan4xICFGFYCZPnHk4da163VH4eOOhU7f7YideFpLuhGoCpmYU3D7y8rmKse2c2vy8fRxI/g==", + "version": "17.0.8", + "resolved": "https://mirrors.tencent.com/npm/expo-image-picker/-/expo-image-picker-17.0.8.tgz", + "integrity": "sha512-489ByhVs2XPoAu9zodivAKLv7hG4S/FOe8hO/C2U6jVxmRjpAKakKNjMml0IwWjf1+c/RYBqm1XxKaZ+vq/fDQ==", "license": "MIT", "dependencies": { "expo-image-loader": "~6.0.0" @@ -7933,9 +7901,9 @@ } }, "node_modules/expo-linear-gradient": { - "version": "15.0.6", - "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-15.0.6.tgz", - "integrity": "sha512-tpjW7i90buuQx+1XSrWcSfcD5bfP4gJR96tvPzaZYWHnHgLrugnqWcauK4wugMiFJ8LxIwYSk+Y3AH+e4r6kHg==", + "version": "15.0.7", + "resolved": "https://mirrors.tencent.com/npm/expo-linear-gradient/-/expo-linear-gradient-15.0.7.tgz", + "integrity": "sha512-yF+y+9Shpr/OQFfy/wglB/0bykFMbwHBTuMRa5Of/r2P1wbkcacx8rg0JsUWkXH/rn2i2iWdubyqlxSJa3ggZA==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -7958,9 +7926,9 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.10.tgz", - "integrity": "sha512-6pwaz9H7aK/iYraHbX7zjg8QFTUuMfGEs8Vyc6bAoBd8Rovtb91WX955Kq5sazwNrQjs3WePwQ23LEAmls3u5g==", + "version": "3.0.11", + "resolved": "https://mirrors.tencent.com/npm/expo-modules-autolinking/-/expo-modules-autolinking-3.0.11.tgz", + "integrity": "sha512-Sz1ptcSZ4mvWJ7Rf1aB6Pe1fuEeIkACPILg2tmXDo3wwLTxPqugitMOePjbBZyvacBDirtDZlMb2A6LQDPVFOg==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -7975,9 +7943,9 @@ } }, "node_modules/expo-modules-core": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-3.0.15.tgz", - "integrity": "sha512-vGI7osd0/IjprldD08k4bckWSu7ID4HhZNP68l/UtilONQ8XZig8mWJd/Fm7i7KGvE3HyuF+HOXE9l671no42Q==", + "version": "3.0.16", + "resolved": "https://mirrors.tencent.com/npm/expo-modules-core/-/expo-modules-core-3.0.16.tgz", + "integrity": "sha512-rCxzJiTdeSdqLVmDYYnogxqHS3NB65YTd76tAtSACujN2TQco08/toxCCov+9uULq1NGPxDJnfTkrtGaGWfatQ==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -8007,44 +7975,14 @@ "react-native": "*" } }, - "node_modules/expo-notifications/node_modules/@expo/image-utils": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.7.tgz", - "integrity": "sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==", - "license": "MIT", - "dependencies": { - "@expo/spawn-async": "^1.7.2", - "chalk": "^4.0.0", - "getenv": "^2.0.0", - "jimp-compact": "0.16.1", - "parse-png": "^2.1.0", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0", - "semver": "^7.6.0", - "temp-dir": "~2.0.0", - "unique-string": "~2.0.0" - } - }, - "node_modules/expo-notifications/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/expo-quick-actions": { - "version": "5.0.0", - "resolved": "https://mirrors.tencent.com/npm/expo-quick-actions/-/expo-quick-actions-5.0.0.tgz", - "integrity": "sha512-NSsDhfbal11gXsHkJbvYVw7x0QUCKrEth2kBBKZUv03dX4J7ZPADSV89LyEpOVYXCkrw6LuanlEtKavg/BFaRA==", + "version": "6.0.0", + "resolved": "https://mirrors.tencent.com/npm/expo-quick-actions/-/expo-quick-actions-6.0.0.tgz", + "integrity": "sha512-jxfvuZSiBxlRAggMibfw17L6w/1xsleB1AKJUC8QWQmGZq099hET4ycvZK9szaX4hOk5hqbkTxSLxTCk8qmPLw==", "license": "MIT", "dependencies": { - "@expo/image-utils": "~0.7.4", - "schema-utils": "^4.2.0", + "@expo/image-utils": "^0.8.7", + "schema-utils": "^4.3.2", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { @@ -8052,9 +7990,9 @@ } }, "node_modules/expo-router": { - "version": "6.0.4", - "resolved": "https://mirrors.tencent.com/npm/expo-router/-/expo-router-6.0.4.tgz", - "integrity": "sha512-RZRHAhUCCU6QNTnVhoy0PAJxbLy7OF78F4/4fwJna3cHGTtokPvJYUwz8kOZOOub/7l8jqgxnT7eKAk1dw9uXQ==", + "version": "6.0.6", + "resolved": "https://mirrors.tencent.com/npm/expo-router/-/expo-router-6.0.6.tgz", + "integrity": "sha512-uSuKQanivBI9RtwmAznLI7It5aPwQLVL2tVBPAOJ70tv6BzP62SpVCf0I8o0j9PmEzORPRLrU2LbQOL962yBHg==", "license": "MIT", "dependencies": { "@expo/metro-runtime": "^6.1.2", @@ -8086,7 +8024,7 @@ "@react-navigation/drawer": "^7.5.0", "@testing-library/react-native": ">= 12.0.0", "expo": "*", - "expo-constants": "^18.0.8", + "expo-constants": "^18.0.9", "expo-linking": "^8.0.8", "react": "*", "react-dom": "*", @@ -8135,12 +8073,12 @@ } }, "node_modules/expo-splash-screen": { - "version": "31.0.8", - "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-31.0.8.tgz", - "integrity": "sha512-NNgNhrqkuJAt98k9CKJ9JeCInv5g/xRhe1fWbciQrqTQWPXeIGg3tn5T5HNFwfRD1aKr2uOs1Ctly8rE6i5vtQ==", + "version": "31.0.10", + "resolved": "https://mirrors.tencent.com/npm/expo-splash-screen/-/expo-splash-screen-31.0.10.tgz", + "integrity": "sha512-i6g9IK798mae4yvflstQ1HkgahIJ6exzTCTw4vEdxV0J2SwiW3Tj+CwRjf0te7Zsb+7dDQhBTmGZwdv00VER2A==", "license": "MIT", "dependencies": { - "@expo/prebuild-config": "^10.0.8" + "@expo/prebuild-config": "^54.0.3" }, "peerDependencies": { "expo": "*" @@ -8161,9 +8099,9 @@ } }, "node_modules/expo-status-bar": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-3.0.7.tgz", - "integrity": "sha512-8AyNDAsOIR0sAh8uUuOnCNrntr2sM2Q8MW173bQJQIDRNClK7WLVnho1mQhZU3it7MrSWp3QOyUTNU0RcQrL1w==", + "version": "3.0.8", + "resolved": "https://mirrors.tencent.com/npm/expo-status-bar/-/expo-status-bar-3.0.8.tgz", + "integrity": "sha512-L248XKPhum7tvREoS1VfE0H6dPCaGtoUWzRsUv7hGKdiB4cus33Rc0sxkWkoQ77wE8stlnUlL5lvmT0oqZ3ZBw==", "license": "MIT", "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" @@ -8174,9 +8112,9 @@ } }, "node_modules/expo-symbols": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-1.0.6.tgz", - "integrity": "sha512-866OfsPJUjT9OhxP6ol/tnQLUY3h2LGHXGpb2SR9OLbM2M/xv+5Ko1+GBMcTbJ3wqW6uZKEgQBNLLKXUIvi/bg==", + "version": "1.0.7", + "resolved": "https://mirrors.tencent.com/npm/expo-symbols/-/expo-symbols-1.0.7.tgz", + "integrity": "sha512-ZqFUeTXbwO6BrE00n37wTXYfJmsjFrfB446jeB9k9w7aA8a6eugNUIzNsUIUfbFWoOiY4wrGmpLSLPBwk4PH+g==", "license": "MIT", "dependencies": { "sf-symbols-typescript": "^2.0.0" @@ -8206,12 +8144,6 @@ } } }, - "node_modules/expo-system-ui/node_modules/@react-native/normalize-colors": { - "version": "0.81.4", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.4.tgz", - "integrity": "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg==", - "license": "MIT" - }, "node_modules/expo-task-manager": { "version": "14.0.7", "resolved": "https://mirrors.tencent.com/npm/expo-task-manager/-/expo-task-manager-14.0.7.tgz", @@ -8226,9 +8158,9 @@ } }, "node_modules/expo-web-browser": { - "version": "15.0.6", - "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.6.tgz", - "integrity": "sha512-f3LPRwYGHQ6lM2PZX1Fz79mfa90e3s8Zs9UKv2TqSqsM85YmqTiT5pgd5tLHh55HgWjSMeISCCtahe6WKeWgCw==", + "version": "15.0.7", + "resolved": "https://mirrors.tencent.com/npm/expo-web-browser/-/expo-web-browser-15.0.7.tgz", + "integrity": "sha512-eXnfO3FQ2WthTA8uEPNJ7SDRfPaLIU/P2k082HGEYIHAFZMwh2o9Wo+SDVytO3E95TAv1qwhggUjOrczYzxteQ==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -8236,9 +8168,9 @@ } }, "node_modules/expo/node_modules/@expo/cli": { - "version": "54.0.5", - "resolved": "https://mirrors.tencent.com/npm/@expo/cli/-/cli-54.0.5.tgz", - "integrity": "sha512-8MZOZKHfHRHTBQu2/PXBi7eCKc2aF1i1JsZweL/P7aX8nivhrP6KV6An5PtO1/rrdnS9z7pmX2KwMygvvaFNhg==", + "version": "54.0.6", + "resolved": "https://mirrors.tencent.com/npm/@expo/cli/-/cli-54.0.6.tgz", + "integrity": "sha512-BgxJshNqSODb4Rq4q4lHLBVWVL4683Q+PSJ2fd+m3D5Jqd8nu9zGvcq6I/H8AXV/Ux31eIuUgAojPCjW8LRyZA==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.0.8", @@ -8249,10 +8181,11 @@ "@expo/env": "~2.0.7", "@expo/image-utils": "^0.8.7", "@expo/json-file": "^10.0.7", + "@expo/mcp-tunnel": "~0.0.7", "@expo/metro": "~0.1.1", "@expo/metro-config": "~54.0.3", "@expo/osascript": "^2.3.7", - "@expo/package-manager": "^1.9.7", + "@expo/package-manager": "^1.9.8", "@expo/plist": "^0.4.7", "@expo/prebuild-config": "^54.0.3", "@expo/schema-utils": "^0.1.7", @@ -8322,51 +8255,6 @@ } } }, - "node_modules/expo/node_modules/@expo/image-utils": { - "version": "0.8.7", - "resolved": "https://mirrors.tencent.com/npm/@expo/image-utils/-/image-utils-0.8.7.tgz", - "integrity": "sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==", - "license": "MIT", - "dependencies": { - "@expo/spawn-async": "^1.7.2", - "chalk": "^4.0.0", - "getenv": "^2.0.0", - "jimp-compact": "0.16.1", - "parse-png": "^2.1.0", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0", - "semver": "^7.6.0", - "temp-dir": "~2.0.0", - "unique-string": "~2.0.0" - } - }, - "node_modules/expo/node_modules/@expo/prebuild-config": { - "version": "54.0.3", - "resolved": "https://mirrors.tencent.com/npm/@expo/prebuild-config/-/prebuild-config-54.0.3.tgz", - "integrity": "sha512-okf6Umaz1VniKmm+pA37QHBzB9XlRHvO1Qh3VbUezy07LTkz87kXUW7uLMmrA319WLavWSVORTXeR0jBRihObA==", - "license": "MIT", - "dependencies": { - "@expo/config": "~12.0.9", - "@expo/config-plugins": "~54.0.1", - "@expo/config-types": "^54.0.8", - "@expo/image-utils": "^0.8.7", - "@expo/json-file": "^10.0.7", - "@react-native/normalize-colors": "0.81.4", - "debug": "^4.3.1", - "resolve-from": "^5.0.0", - "semver": "^7.6.0", - "xml2js": "0.6.0" - }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo/node_modules/@react-native/normalize-colors": { - "version": "0.81.4", - "resolved": "https://mirrors.tencent.com/npm/@react-native/normalize-colors/-/normalize-colors-0.81.4.tgz", - "integrity": "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg==", - "license": "MIT" - }, "node_modules/expo/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://mirrors.tencent.com/npm/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -12142,7 +12030,7 @@ }, "node_modules/progress": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "resolved": "https://mirrors.tencent.com/npm/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "license": "MIT", "engines": { @@ -12190,7 +12078,7 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, @@ -12829,7 +12717,7 @@ }, "node_modules/react-native-webview": { "version": "13.15.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", + "resolved": "https://mirrors.tencent.com/npm/react-native-webview/-/react-native-webview-13.15.0.tgz", "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", "license": "MIT", "dependencies": { @@ -12891,12 +12779,6 @@ "node": ">=10" } }, - "node_modules/react-native/node_modules/@react-native/normalize-colors": { - "version": "0.81.4", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.4.tgz", - "integrity": "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg==", - "license": "MIT" - }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.81.4", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.4.tgz", @@ -15666,6 +15548,24 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://mirrors.tencent.com/npm/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://mirrors.tencent.com/npm/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 0736536..aab2184 100644 --- a/package.json +++ b/package.json @@ -12,44 +12,44 @@ "lint": "expo lint" }, "dependencies": { - "@expo/ui": "~0.2.0-beta.2", + "@expo/ui": "~0.2.0-beta.3", "@expo/vector-icons": "^15.0.2", "@react-native-async-storage/async-storage": "^2.2.0", - "@react-native-community/datetimepicker": "^8.4.4", + "@react-native-community/datetimepicker": "8.4.4", "@react-native-masked-view/masked-view": "^0.3.2", - "@react-native-picker/picker": "^2.11.1", + "@react-native-picker/picker": "2.11.1", "@react-native-voice/voice": "^3.2.4", - "@react-navigation/bottom-tabs": "^7.4.7", + "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.4", - "@react-navigation/native": "^7.1.17", + "@react-navigation/native": "^7.1.8", "@reduxjs/toolkit": "^2.9.0", "@sentry/react-native": "~6.20.0", "@types/lodash": "^4.17.20", "cos-js-sdk-v5": "^1.6.0", - "dayjs": "^1.11.13", - "expo": "^54.0.7", - "expo-apple-authentication": "~8.0.6", + "dayjs": "^1.11.18", + "expo": "^54.0.8", + "expo-apple-authentication": "~8.0.7", "expo-background-task": "~1.0.7", "expo-blur": "~15.0.7", - "expo-camera": "~17.0.7", - "expo-constants": "~18.0.8", + "expo-camera": "~17.0.8", + "expo-constants": "~18.0.9", "expo-font": "~14.0.8", - "expo-glass-effect": "^0.1.3", - "expo-haptics": "~15.0.6", + "expo-glass-effect": "^0.1.4", + "expo-haptics": "~15.0.7", "expo-image": "~3.0.8", - "expo-image-picker": "~17.0.7", - "expo-linear-gradient": "~15.0.6", + "expo-image-picker": "~17.0.8", + "expo-linear-gradient": "~15.0.7", "expo-linking": "~8.0.8", "expo-notifications": "~0.32.11", - "expo-quick-actions": "^5.0.0", - "expo-router": "~6.0.4", - "expo-splash-screen": "~31.0.8", + "expo-quick-actions": "^6.0.0", + "expo-router": "~6.0.6", + "expo-splash-screen": "~31.0.10", "expo-sqlite": "^16.0.8", - "expo-status-bar": "~3.0.7", - "expo-symbols": "~1.0.6", + "expo-status-bar": "~3.0.8", + "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.7", "expo-task-manager": "~14.0.6", - "expo-web-browser": "~15.0.6", + "expo-web-browser": "~15.0.7", "lodash": "^4.17.21", "lottie-react-native": "^7.3.4", "react": "19.1.0", @@ -62,24 +62,24 @@ "react-native-markdown-display": "^7.0.2", "react-native-modal-datetime-picker": "^18.0.0", "react-native-popover-view": "^6.1.0", - "react-native-purchases": "^9.4.3", + "react-native-purchases": "^9.2.2", "react-native-reanimated": "~4.1.0", "react-native-render-html": "^6.3.4", - "react-native-safe-area-context": "~5.6.0", + "react-native-safe-area-context": "~5.6.1", "react-native-screens": "~4.16.0", - "react-native-svg": "^15.12.1", + "react-native-svg": "15.12.1", "react-native-toast-message": "^2.3.3", - "react-native-web": "^0.21.0", + "react-native-web": "^0.21.1", "react-native-webview": "13.15.0", "react-native-wheel-picker-expo": "^0.5.4", "react-redux": "^9.2.0" }, "devDependencies": { - "@babel/core": "^7.25.2", - "@types/react": "~19.1.12", - "eslint": "^9.25.0", + "@babel/core": "^7.28.4", + "@types/react": "~19.1.13", + "eslint": "^9.35.0", "eslint-config-expo": "~10.0.0", "typescript": "~5.9.2" }, "private": true -} \ No newline at end of file +} diff --git a/utils/health.ts b/utils/health.ts index 99c4bfb..9d632ed 100644 --- a/utils/health.ts +++ b/utils/health.ts @@ -73,7 +73,6 @@ export async function ensureHealthPermissions(): Promise { return new Promise((resolve) => { console.log('开始初始化HealthKit...'); - resolve(true) AppleHealthKit.initHealthKit(PERMISSIONS, (error) => { if (error) { console.error('HealthKit初始化失败:', error); diff --git a/utils/healthKit.ts b/utils/healthKit.ts new file mode 100644 index 0000000..d6e2197 --- /dev/null +++ b/utils/healthKit.ts @@ -0,0 +1,147 @@ +/** + * HealthKit Native Module Interface + * React Native TypeScript bindings for iOS HealthKit access + */ + +import { NativeModules } from 'react-native'; + +export interface HealthKitPermissions { + [key: string]: 'notDetermined' | 'denied' | 'authorized' | 'unknown'; +} + +export interface HealthKitAuthorizationResult { + success: boolean; + permissions: HealthKitPermissions; +} + +export interface SleepDataSource { + name: string; + bundleIdentifier: string; +} + +export interface SleepDataSample { + id: string; + startDate: string; // ISO8601 format + endDate: string; // ISO8601 format + value: number; + categoryType: 'inBed' | 'asleep' | 'awake' | 'core' | 'deep' | 'rem' | 'unknown'; + duration: number; // Duration in seconds + source: SleepDataSource; + metadata: Record; +} + +export interface SleepDataOptions { + startDate?: string; // ISO8601 format, defaults to 7 days ago + endDate?: string; // ISO8601 format, defaults to now + limit?: number; // Maximum number of samples, defaults to 100 +} + +export interface SleepDataResult { + data: SleepDataSample[]; + count: number; + startDate: string; + endDate: string; +} + +export interface HealthKitManagerInterface { + /** + * Request authorization to access HealthKit data + * This will prompt the user for permission to read/write health data + */ + requestAuthorization(): Promise; + + /** + * Get sleep analysis data from HealthKit + * @param options Query options including date range and limit + */ + getSleepData(options?: SleepDataOptions): Promise; +} + +console.log('NativeModules', NativeModules); + + +// Native module interface +const HealthKitManager: HealthKitManagerInterface = NativeModules.HealthKitManager; + +export default HealthKitManager; + +// Utility functions for working with sleep data +export class HealthKitUtils { + /** + * Convert seconds to hours and minutes + */ + static formatDuration(seconds: number): string { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + return `${minutes}m`; + } + + /** + * Get total sleep duration from sleep samples for a specific date + */ + static getTotalSleepDuration(samples: SleepDataSample[], date: Date): number { + const targetDate = date.toISOString().split('T')[0]; + + return samples + .filter(sample => { + const sampleDate = new Date(sample.startDate).toISOString().split('T')[0]; + return sampleDate === targetDate && + ['asleep', 'core', 'deep', 'rem'].includes(sample.categoryType); + }) + .reduce((total, sample) => total + sample.duration, 0); + } + + /** + * Group sleep samples by date + */ + static groupSamplesByDate(samples: SleepDataSample[]): Record { + return samples.reduce((grouped, sample) => { + const date = new Date(sample.startDate).toISOString().split('T')[0]; + if (!grouped[date]) { + grouped[date] = []; + } + grouped[date].push(sample); + return grouped; + }, {} as Record); + } + + /** + * Get sleep quality metrics from samples + */ + static getSleepQualityMetrics(samples: SleepDataSample[]) { + const sleepSamples = samples.filter(s => + ['asleep', 'core', 'deep', 'rem'].includes(s.categoryType) + ); + + if (sleepSamples.length === 0) { + return null; + } + + const totalDuration = sleepSamples.reduce((sum, s) => sum + s.duration, 0); + const deepSleepDuration = sleepSamples + .filter(s => s.categoryType === 'deep') + .reduce((sum, s) => sum + s.duration, 0); + const remSleepDuration = sleepSamples + .filter(s => s.categoryType === 'rem') + .reduce((sum, s) => sum + s.duration, 0); + + return { + totalDuration, + deepSleepDuration, + remSleepDuration, + deepSleepPercentage: totalDuration > 0 ? (deepSleepDuration / totalDuration) * 100 : 0, + remSleepPercentage: totalDuration > 0 ? (remSleepDuration / totalDuration) * 100 : 0, + }; + } + + /** + * Check if HealthKit is available (iOS only) + */ + static isAvailable(): boolean { + return HealthKitManager != null; + } +} \ No newline at end of file diff --git a/utils/healthKitExample.ts b/utils/healthKitExample.ts new file mode 100644 index 0000000..257fd73 --- /dev/null +++ b/utils/healthKitExample.ts @@ -0,0 +1,236 @@ +/** + * HealthKit Native Module Usage Example + * 展示如何使用HealthKit native module的示例代码 + */ + +import HealthKitManager, { HealthKitUtils, SleepDataSample } from './healthKit'; + +export class HealthKitService { + + /** + * 初始化HealthKit并请求权限 + */ + static async initializeHealthKit(): Promise { + try { + // 检查HealthKit是否可用 + if (!HealthKitUtils.isAvailable()) { + console.log('HealthKit不可用,可能运行在Android设备或模拟器上'); + return false; + } + + // 请求授权 + const result = await HealthKitManager.requestAuthorization(); + + if (result.success) { + console.log('HealthKit授权成功'); + console.log('权限状态:', result.permissions); + + // 检查睡眠数据权限 + const sleepPermission = result.permissions['HKCategoryTypeIdentifierSleepAnalysis']; + if (sleepPermission === 'authorized') { + console.log('睡眠数据访问权限已获得'); + return true; + } else { + console.log('睡眠数据访问权限未获得:', sleepPermission); + return false; + } + } else { + console.log('HealthKit授权失败'); + return false; + } + } catch (error) { + console.error('HealthKit初始化失败:', error); + return false; + } + } + + /** + * 获取最近的睡眠数据 + */ + static async getRecentSleepData(days: number = 7): Promise { + try { + const endDate = new Date(); + const startDate = new Date(); + startDate.setDate(endDate.getDate() - days); + + const result = await HealthKitManager.getSleepData({ + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + limit: 100 + }); + + console.log(`获取到 ${result.count} 条睡眠记录`); + return result.data; + } catch (error) { + console.error('获取睡眠数据失败:', error); + return []; + } + } + + /** + * 分析睡眠质量 + */ + static async analyzeSleepQuality(days: number = 7): Promise { + try { + const sleepData = await this.getRecentSleepData(days); + + if (sleepData.length === 0) { + return { + error: '没有找到睡眠数据', + hasData: false + }; + } + + // 按日期分组 + const groupedData = HealthKitUtils.groupSamplesByDate(sleepData); + const dates = Object.keys(groupedData).sort().reverse(); // 最新的日期在前 + + const analysis = dates.slice(0, days).map(date => { + const daySamples = groupedData[date]; + const totalSleepDuration = HealthKitUtils.getTotalSleepDuration(daySamples, new Date(date)); + const qualityMetrics = HealthKitUtils.getSleepQualityMetrics(daySamples); + + return { + date, + totalSleepDuration, + totalSleepFormatted: HealthKitUtils.formatDuration(totalSleepDuration), + qualityMetrics, + samplesCount: daySamples.length + }; + }); + + // 计算平均值 + const validDays = analysis.filter(day => day.totalSleepDuration > 0); + const averageSleepDuration = validDays.length > 0 + ? validDays.reduce((sum, day) => sum + day.totalSleepDuration, 0) / validDays.length + : 0; + + return { + hasData: true, + days: analysis, + summary: { + averageSleepDuration, + averageSleepFormatted: HealthKitUtils.formatDuration(averageSleepDuration), + daysWithData: validDays.length, + totalDaysAnalyzed: days + } + }; + } catch (error) { + console.error('睡眠质量分析失败:', error); + return { + error: error instanceof Error ? error.message : String(error), + hasData: false + }; + } + } + + /** + * 获取昨晚的睡眠数据 + */ + static async getLastNightSleep(): Promise { + try { + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + + // 设置查询范围:昨天下午6点到今天上午12点 + 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: 50 + }); + + if (result.data.length === 0) { + return { + hasData: false, + message: '未找到昨晚的睡眠数据' + }; + } + + const sleepSamples = result.data.filter(sample => + ['asleep', 'core', 'deep', 'rem'].includes(sample.categoryType) + ); + + if (sleepSamples.length === 0) { + return { + hasData: false, + message: '未找到有效的睡眠阶段数据' + }; + } + + // 找到睡眠的开始和结束时间 + 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 qualityMetrics = HealthKitUtils.getSleepQualityMetrics(sleepSamples); + + return { + hasData: true, + sleepStart: sleepStart.toISOString(), + sleepEnd: sleepEnd.toISOString(), + totalDuration, + totalDurationFormatted: HealthKitUtils.formatDuration(totalDuration), + qualityMetrics, + samples: sleepSamples, + bedTime: sleepStart.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }), + wakeTime: sleepEnd.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) + }; + } catch (error) { + console.error('获取昨晚睡眠数据失败:', error); + return { + hasData: false, + error: error instanceof Error ? error.message : String(error) + }; + } + } +} + +// 使用示例 +export const useHealthKitExample = async () => { + console.log('=== HealthKit 使用示例 ==='); + + // 1. 初始化和授权 + const initialized = await HealthKitService.initializeHealthKit(); + if (!initialized) { + console.log('HealthKit初始化失败,无法继续'); + return; + } + + // 2. 获取昨晚的睡眠数据 + console.log('\n--- 昨晚睡眠数据 ---'); + const lastNightSleep = await HealthKitService.getLastNightSleep(); + if (lastNightSleep.hasData) { + console.log(`睡眠时间: ${lastNightSleep.bedTime} - ${lastNightSleep.wakeTime}`); + console.log(`睡眠时长: ${lastNightSleep.totalDurationFormatted}`); + if (lastNightSleep.qualityMetrics) { + console.log(`深睡眠: ${lastNightSleep.qualityMetrics.deepSleepPercentage.toFixed(1)}%`); + console.log(`REM睡眠: ${lastNightSleep.qualityMetrics.remSleepPercentage.toFixed(1)}%`); + } + } else { + console.log(lastNightSleep.message || '未找到睡眠数据'); + } + + // 3. 分析最近一周的睡眠质量 + console.log('\n--- 最近一周睡眠分析 ---'); + const weeklyAnalysis = await HealthKitService.analyzeSleepQuality(7); + if (weeklyAnalysis.hasData) { + console.log(`平均睡眠时长: ${weeklyAnalysis.summary.averageSleepFormatted}`); + console.log(`有数据的天数: ${weeklyAnalysis.summary.daysWithData}/${weeklyAnalysis.summary.totalDaysAnalyzed}`); + + console.log('\n每日睡眠详情:'); + weeklyAnalysis.days.forEach((day: any) => { + if (day.totalSleepDuration > 0) { + console.log(`${day.date}: ${day.totalSleepFormatted}`); + } + }); + } else { + console.log(weeklyAnalysis.error || '睡眠分析失败'); + } +}; \ No newline at end of file diff --git a/utils/sleepHealthKit.ts b/utils/sleepHealthKit.ts index 06e8606..b18c393 100644 --- a/utils/sleepHealthKit.ts +++ b/utils/sleepHealthKit.ts @@ -1,6 +1,5 @@ import dayjs from 'dayjs'; -import HealthKit from 'react-native-health'; -import { ensureHealthPermissions } from './health'; +import HealthKitManager, { HealthKitUtils } from './healthKit'; // 睡眠阶段枚举 export enum SleepStage { @@ -116,54 +115,54 @@ export const fetchSleepSamples = async (date: Date): Promise => { endDate: dateRange.endDate.toISOString(), }; - return new Promise((resolve) => { - HealthKit.getSleepSamples(options, (error: string, results: any[]) => { - if (error) { - console.error('[Sleep] 获取睡眠数据失败:', error); - resolve([]); // 返回空数组而非拒绝,以便于处理 - return; - } + // return new Promise((resolve) => { + // HealthKitManager.(options, (error: string, results: any[]) => { + // if (error) { + // console.error('[Sleep] 获取睡眠数据失败:', error); + // resolve([]); // 返回空数组而非拒绝,以便于处理 + // return; + // } - if (!results || results.length === 0) { - console.warn('[Sleep] 未找到睡眠数据'); - resolve([]); - return; - } + // if (!results || results.length === 0) { + // console.warn('[Sleep] 未找到睡眠数据'); + // resolve([]); + // return; + // } - console.log('[Sleep] 获取到原始睡眠样本:', results.length, '条'); + // console.log('[Sleep] 获取到原始睡眠样本:', results.length, '条'); - // 过滤并转换数据格式 - const sleepSamples: SleepSample[] = results - .filter(sample => { - const sampleStart = new Date(sample.startDate).getTime(); - const sampleEnd = new Date(sample.endDate).getTime(); - const rangeStart = dateRange.startDate.getTime(); - const rangeEnd = dateRange.endDate.getTime(); + // // 过滤并转换数据格式 + // const sleepSamples: SleepSample[] = results + // .filter(sample => { + // const sampleStart = new Date(sample.startDate).getTime(); + // const sampleEnd = new Date(sample.endDate).getTime(); + // const rangeStart = dateRange.startDate.getTime(); + // const rangeEnd = dateRange.endDate.getTime(); - return (sampleStart >= rangeStart && sampleStart < rangeEnd) || - (sampleStart < rangeEnd && sampleEnd > rangeStart); - }) - .map(sample => { - console.log('[Sleep] 原始睡眠样本:', { - startDate: sample.startDate, - endDate: sample.endDate, - value: sample.value, - sourceName: sample.sourceName - }); + // return (sampleStart >= rangeStart && sampleStart < rangeEnd) || + // (sampleStart < rangeEnd && sampleEnd > rangeStart); + // }) + // .map(sample => { + // console.log('[Sleep] 原始睡眠样本:', { + // startDate: sample.startDate, + // endDate: sample.endDate, + // value: sample.value, + // sourceName: sample.sourceName + // }); - return { - startDate: sample.startDate, - endDate: sample.endDate, - value: mapHealthKitSleepValue(sample.value), - sourceName: sample.sourceName, - sourceId: sample.sourceId || sample.uuid - }; - }); + // return { + // startDate: sample.startDate, + // endDate: sample.endDate, + // value: mapHealthKitSleepValue(sample.value), + // sourceName: sample.sourceName, + // sourceId: sample.sourceId || sample.uuid + // }; + // }); - console.log('[Sleep] 过滤后的睡眠样本:', sleepSamples.length, '条'); - resolve(sleepSamples); - }); - }); + // console.log('[Sleep] 过滤后的睡眠样本:', sleepSamples.length, '条'); + // resolve(sleepSamples); + // }); + // }); } catch (error) { console.error('[Sleep] 获取睡眠样本失败:', error); @@ -181,37 +180,37 @@ export const fetchSleepHeartRateData = async (bedtime: string, wakeupTime: strin endDate: wakeupTime, }; - return new Promise((resolve) => { - HealthKit.getHeartRateSamples(options, (error: string, results: any[]) => { - if (error) { - console.error('[Sleep] 获取心率数据失败:', error); - resolve([]); - return; - } + // return new Promise((resolve) => { + // HealthKit.getHeartRateSamples(options, (error: string, results: any[]) => { + // if (error) { + // console.error('[Sleep] 获取心率数据失败:', error); + // resolve([]); + // return; + // } - if (!results || results.length === 0) { - console.log('[Sleep] 未找到心率数据'); - resolve([]); - return; - } + // if (!results || results.length === 0) { + // console.log('[Sleep] 未找到心率数据'); + // resolve([]); + // return; + // } - const heartRateData: HeartRateData[] = results - .filter(sample => { - const sampleTime = new Date(sample.startDate).getTime(); - const bedtimeMs = new Date(bedtime).getTime(); - const wakeupTimeMs = new Date(wakeupTime).getTime(); - return sampleTime >= bedtimeMs && sampleTime <= wakeupTimeMs; - }) - .map(sample => ({ - timestamp: sample.startDate, - value: Math.round(sample.value) - })) - .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + // const heartRateData: HeartRateData[] = results + // .filter(sample => { + // const sampleTime = new Date(sample.startDate).getTime(); + // const bedtimeMs = new Date(bedtime).getTime(); + // const wakeupTimeMs = new Date(wakeupTime).getTime(); + // return sampleTime >= bedtimeMs && sampleTime <= wakeupTimeMs; + // }) + // .map(sample => ({ + // timestamp: sample.startDate, + // value: Math.round(sample.value) + // })) + // .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); - console.log('[Sleep] 获取到睡眠心率数据:', heartRateData.length, '个样本'); - resolve(heartRateData); - }); - }); + // console.log('[Sleep] 获取到睡眠心率数据:', heartRateData.length, '个样本'); + // resolve(heartRateData); + // }); + // }); } catch (error) { console.error('[Sleep] 获取睡眠心率数据失败:', error); @@ -369,8 +368,14 @@ export const getSleepQualityInfo = (sleepScore: number): { description: string; export const fetchCompleteSleepData = async (date: Date): Promise => { try { console.log('[Sleep] 开始获取完整睡眠数据...', dayjs(date).format('YYYY-MM-DD')); + // 检查HealthKit是否可用 + if (!HealthKitUtils.isAvailable()) { + console.log('HealthKit不可用,可能运行在Android设备或模拟器上'); + return null; + } - await ensureHealthPermissions() + await HealthKitManager.requestAuthorization() + // await ensureHealthPermissions() // 获取睡眠样本 const sleepSamples = await fetchSleepSamples(date);