diff --git a/.kilocode/rules/memory-bank/context.md b/.kilocode/rules/memory-bank/context.md
index aaaa371..37a7f9e 100644
--- a/.kilocode/rules/memory-bank/context.md
+++ b/.kilocode/rules/memory-bank/context.md
@@ -1,6 +1,7 @@
# 项目当前状态
## 应用基本信息
+
- **应用名称**: Out Live(超越生命)
- **版本**: 1.0.19
- **Bundle ID**: com.anonymous.digitalpilates
@@ -9,8 +10,9 @@
- **架构**: Expo Prebuild 后的 React Native 应用
## 当前开发状态
+
- **开发阶段**: 生产就绪版本
-- **最后更新**: 2025年10月
+- **最后更新**: 2025 年 11 月
- **主要功能**: 已完成核心健康数据追踪、AI 教练、目标管理、轻断食等功能
- **状态管理**: 使用 Redux Toolkit 进行状态管理
- **数据存储**: 本地使用 expo-sqlite/kv-store,远程 API 集成
@@ -18,41 +20,48 @@
## 核心功能实现状态
### 健康数据追踪 ✅
+
- HealthKit 集成完成,支持步数、心率、HRV、睡眠等数据
- 活动圆环显示(活动卡路里、锻炼分钟、站立小时)
- 实时健康数据监控和历史数据查看
- 健康权限管理系统
### 营养管理 ✅
+
- 饮食记录功能(文字、语音、拍照识别)
- 营养成分分析和卡路里计算
- 食物库和自定义食物功能
- 营养标签识别
### 目标与习惯管理 ✅
+
- 目标创建、编辑、删除功能
- 任务分解和进度追踪
- 智能提醒系统
- 目标完成统计和分析
### 轻断食功能 ✅
-- 多种预设断食方案(16:8、18:6等)
+
+- 多种预设断食方案(16:8、18:6 等)
- 实时断食进度显示
- 断食提醒和通知
- 断食历史记录
### AI 教练系统 ✅
+
- AI 对话功能(流式响应)
- 体态评估(照片分析)
- 个性化健康建议
- 情绪分析(基于 HRV)
### 社区与挑战 ✅
+
- 挑战赛参与和排行榜
- 成就系统
- 社交分享功能
### 训练计划 ✅
+
- 个性化训练计划生成
- 运动库和动作指导
- 训练进度记录
@@ -60,6 +69,7 @@
## 技术架构状态
### 前端架构 ✅
+
- React Native 0.81.4 + Expo 54
- TypeScript 全面覆盖
- Expo Router 6.0 用于路由管理
@@ -67,12 +77,14 @@
- Liquid Glass 设计风格实现
### 后端集成 ✅
+
- RESTful API 集成(API 基础地址:https://pilate.richarjiang.com)
- 用户认证和授权
- 数据同步和备份
- 推送通知服务
### 原生功能 ✅
+
- HealthKit 深度集成
- 推送通知(本地和远程)
- 快捷动作(Quick Actions)
@@ -82,32 +94,39 @@
## 当前开发重点
### 近期更新
-1. **性能优化**: 优化健康数据加载和图表渲染性能
-2. **用户体验**: 改进 Liquid Glass 设计效果和交互动画
-3. **数据同步**: 增强离线功能和数据同步稳定性
-4. **AI 功能**: 扩展 AI 教练对话能力和分析精度
+
+1. **多语言支持**: 完善挑战页面的多语言翻译支持,建立翻译最佳实践指南
+2. **性能优化**: 优化健康数据加载和图表渲染性能
+3. **用户体验**: 改进 Liquid Glass 设计效果和交互动画
+4. **数据同步**: 增强离线功能和数据同步稳定性
+5. **AI 功能**: 扩展 AI 教练对话能力和分析精度
### 待解决问题
-1. **测试覆盖**: 自动化测试覆盖率需要提升
-2. **错误监控**: 需要集成更完善的错误监控和分析
-3. **性能监控**: 应用性能监控和分析工具集成
-4. **文档完善**: API 文档和组件文档需要进一步完善
+
+1. **多语言覆盖**: 其他页面的多语言翻译支持需要逐步完善
+2. **测试覆盖**: 自动化测试覆盖率需要提升
+3. **错误监控**: 需要集成更完善的错误监控和分析
+4. **性能监控**: 应用性能监控和分析工具集成
+5. **文档完善**: API 文档和组件文档需要进一步完善
## 代码质量状态
### 代码规范 ✅
+
- ESLint 配置完善(eslint-config-expo)
- Prettier 代码格式化
- TypeScript 严格模式
- 组件和函数命名规范
### 项目结构 ✅
+
- 清晰的目录结构(app/、components/、services/、store/、utils/)
- 功能模块化组织
- 类型定义完整
- 常量和配置集中管理
### 状态管理 ✅
+
- Redux Toolkit 标准实现
- 异步操作处理规范
- 数据持久化策略
@@ -116,12 +135,14 @@
## 部署和发布
### 构建配置 ✅
+
- Expo Prebuild 配置
- iOS 证书和配置文件
- App Store 发布配置
- 自动化构建流程
### 发布状态 ✅
+
- App Store 已发布版本
- 支持 OTA 更新
- 崩溃监控和分析
@@ -130,12 +151,14 @@
## 团队协作
### 开发工具 ✅
+
- Git 版本控制
- VS Code 开发环境
- Expo 开发者工具
- iOS 模拟器和真机调试
### 文档状态 🔄
+
- API 文档部分完成
- 组件文档需要补充
- 部署文档完善
@@ -143,20 +166,24 @@
## 下一步计划
-### 短期目标(1-2个月)
-1. 完善自动化测试覆盖
-2. 优化应用启动性能
-3. 增强错误监控和分析
-4. 改进用户引导流程
+### 短期目标(1-2 个月)
+
+1. 完善所有核心页面的多语言翻译支持
+2. 完善自动化测试覆盖
+3. 优化应用启动性能
+4. 增强错误监控和分析
+5. 改进用户引导流程
+
+### 中期目标(3-6 个月)
-### 中期目标(3-6个月)
1. 扩展 AI 教练功能
2. 增加更多健康指标追踪
3. 优化数据同步策略
4. 增强社交功能
-### 长期目标(6个月以上)
+### 长期目标(6 个月以上)
+
1. 支持 Apple Watch 应用
2. 集成更多第三方健康设备
3. 开发 Web 端管理界面
-4. 扩展企业健康解决方案
\ No newline at end of file
+4. 扩展企业健康解决方案
diff --git a/.kilocode/rules/memory-bank/tasks.md b/.kilocode/rules/memory-bank/tasks.md
index dc720d0..cabba5b 100644
--- a/.kilocode/rules/memory-bank/tasks.md
+++ b/.kilocode/rules/memory-bank/tasks.md
@@ -5,26 +5,31 @@
**最后更新**: 2025-10-24
### 重要规则
+
**项目中不允许使用 MaterialIcons**,所有图标必须使用 Ionicons 以保持图标库的一致性。
### 问题描述
+
在项目中发现使用 MaterialIcons 的情况,需要将所有 MaterialIcons 替换为 Ionicons,以保持图标库的一致性。
### 解决方案
+
将所有 MaterialIcons 导入和使用替换为对应的 Ionicons。
### 实现模式
#### 1. 替换导入语句
+
```typescript
// ❌ 禁止使用
-import { MaterialIcons } from '@expo/vector-icons';
+import { MaterialIcons } from "@expo/vector-icons";
// ✅ 正确写法
-import { Ionicons } from '@expo/vector-icons';
+import { Ionicons } from "@expo/vector-icons";
```
#### 2. 替换图标名称和属性
+
```typescript
// ❌ 禁止使用
@@ -34,6 +39,7 @@ import { Ionicons } from '@expo/vector-icons';
```
#### 3. 常见图标映射
+
- `arrow-back-ios` → `chevron-back` (返回按钮)
- `auto-awesome` → `star` (星星/自动推荐)
- `tips-and-updates` → `bulb` (提示/建议)
@@ -42,6 +48,7 @@ import { Ionicons } from '@expo/vector-icons';
- `remove` → `remove` (移除/删除,名称相同)
### 重要注意事项
+
1. **图标大小调整**:Ionicons 和 MaterialIcons 的默认大小可能不同,需要适当调整
2. **图标名称差异**:两个图标库的图标名称不同,需要找到对应的功能图标
3. **样式一致性**:确保替换后的图标在视觉上与原设计保持一致
@@ -49,6 +56,7 @@ import { Ionicons } from '@expo/vector-icons';
5. **代码审查**:在代码审查中需要特别检查是否使用了 MaterialIcons
### 参考实现
+
- `components/ui/HeaderBar.tsx` - 返回按钮的标准实现
- `components/model/MembershipModal.tsx` - 完整的 MaterialIcons 替换示例
@@ -57,21 +65,25 @@ import { Ionicons } from '@expo/vector-icons';
**最后更新**: 2025-10-24
### 重要原则
+
**所有按钮组件都需要尝试兼容 Liquid Glass**,这是项目的设计要求。
### 实现模式
#### 1. 导入必要的组件
+
```typescript
-import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
+import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
```
#### 2. 检查设备支持情况
+
```typescript
const isGlassAvailable = isLiquidGlassAvailable();
```
#### 3. 实现条件渲染的按钮
+
```typescript
@@ -96,26 +108,28 @@ const isGlassAvailable = isLiquidGlassAvailable();
```
#### 4. 定义样式
+
```typescript
const styles = StyleSheet.create({
button: {
width: 40,
height: 40,
- borderRadius: 20, // 圆形按钮
- alignItems: 'center',
- justifyContent: 'center',
- overflow: 'hidden', // 保证玻璃边界圆角效果
+ borderRadius: 20, // 圆形按钮
+ alignItems: "center",
+ justifyContent: "center",
+ overflow: "hidden", // 保证玻璃边界圆角效果
// 其他通用样式...
},
fallbackButton: {
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
borderWidth: 1,
- borderColor: 'rgba(255, 255, 255, 0.3)',
+ borderColor: "rgba(255, 255, 255, 0.3)",
},
});
```
### 重要注意事项
+
1. **兼容性检查**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况
2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果
3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
@@ -124,6 +138,7 @@ const styles = StyleSheet.create({
6. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题
### 常用配置
+
- **glassEffectStyle**: "clear"(透明)或 "regular"(常规)
- **tintColor**: 根据按钮功能选择合适的颜色
- 返回/导航操作:白色系 `rgba(255, 255, 255, 0.3)`
@@ -132,6 +147,7 @@ const styles = StyleSheet.create({
- 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)`
### 参考实现
+
- `components/model/MembershipModal.tsx` - 悬浮返回按钮
- `components/glass/button.tsx` - 通用 Glass 按钮组件
- `app/(tabs)/_layout.tsx` - 标签栏按钮实现
@@ -141,24 +157,29 @@ const styles = StyleSheet.create({
**最后更新**: 2025-10-16
### 问题描述
+
当使用 HeaderBar 组件时,需要正确处理内容区域的顶部距离,确保内容不会被状态栏或刘海屏遮挡。
### 解决方案
+
使用 `useSafeAreaTop` hook 获取安全区域顶部距离,并应用到内容容器的样式中。
### 实现模式
#### 1. 导入必要的 hook
+
```typescript
-import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
+import { useSafeAreaTop } from "@/hooks/useSafeAreaWithPadding";
```
#### 2. 在组件中获取 safeAreaTop
+
```typescript
-const safeAreaTop = useSafeAreaTop()
+const safeAreaTop = useSafeAreaTop();
```
#### 3. 应用到内容容器
+
```typescript
// 方式1: 直接应用到 View 组件
@@ -175,11 +196,13 @@ const safeAreaTop = useSafeAreaTop()
```
### 重要注意事项
+
1. **不要在 StyleSheet 中使用变量**:不能在 `StyleSheet.create()` 中直接使用 `safeAreaTop` 变量
2. **使用动态样式**:必须通过内联样式或数组样式的方式动态应用 `safeAreaTop`
3. **不需要额外偏移**:通常只需要 `safeAreaTop`,不需要添加额外的固定像素值
### 示例代码
+
```typescript
// ❌ 错误写法 - 在 StyleSheet 中使用变量
const styles = StyleSheet.create({
@@ -193,6 +216,7 @@ const styles = StyleSheet.create({
```
### 参考页面
+
- `app/steps/detail.tsx`
- `app/water/detail.tsx`
- `app/profile/goals.tsx`
@@ -204,24 +228,29 @@ const styles = StyleSheet.create({
**最后更新**: 2025-10-16
### 问题描述
+
在应用中实现符合 Liquid Glass 设计风格的图标按钮,需要考虑毛玻璃效果和兼容性处理。
### 解决方案
+
使用 `GlassView` 组件实现毛玻璃效果,并提供不支持 Liquid Glass 的设备的降级方案。
### 实现模式
#### 1. 导入必要的组件和函数
+
```typescript
-import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
+import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
```
#### 2. 检查设备支持情况
+
```typescript
const isGlassAvailable = isLiquidGlassAvailable();
```
#### 3. 实现条件渲染的按钮
+
```typescript
@@ -246,25 +275,27 @@ const isGlassAvailable = isLiquidGlassAvailable();
```
#### 4. 定义样式
+
```typescript
const styles = StyleSheet.create({
glassButton: {
width: 36,
height: 36,
- borderRadius: 18, // 圆形按钮
- alignItems: 'center',
- justifyContent: 'center',
- overflow: 'hidden', // 保证玻璃边界圆角效果
+ borderRadius: 18, // 圆形按钮
+ alignItems: "center",
+ justifyContent: "center",
+ overflow: "hidden", // 保证玻璃边界圆角效果
},
fallbackButton: {
borderWidth: 1,
- borderColor: 'rgba(244, 67, 54, 0.3)',
- backgroundColor: 'rgba(244, 67, 54, 0.1)',
+ borderColor: "rgba(244, 67, 54, 0.3)",
+ backgroundColor: "rgba(244, 67, 54, 0.1)",
},
});
```
### 重要注意事项
+
1. **兼容性处理**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况
2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果
3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
@@ -272,6 +303,7 @@ const styles = StyleSheet.create({
5. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题
### 常用配置
+
- **glassEffectStyle**: "clear"(透明)或 "regular"(常规)
- **tintColor**: 根据按钮功能选择合适的颜色
- 删除操作:红色系 `rgba(244, 67, 54, 0.2)`
@@ -279,6 +311,7 @@ const styles = StyleSheet.create({
- 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)`
### 参考实现
+
- `app/food/nutrition-analysis-history.tsx` - 删除按钮实现
- `components/glass/button.tsx` - 通用 Glass 按钮组件
- `app/(tabs)/_layout.tsx` - 标签栏按钮实现
@@ -288,27 +321,33 @@ const styles = StyleSheet.create({
**最后更新**: 2025-10-16
### 问题描述
+
在应用中实现需要登录才能访问的功能时,需要判断用户是否已登录,未登录时先跳转到登录页面。
### 解决方案
+
使用 `useAuthGuard` hook 中的 `pushIfAuthedElseLogin` 方法处理需要登录验证的导航操作,使用 `ensureLoggedIn` 方法处理需要登录验证的功能实现。
### 权限校验原则
+
**重要**: 功能实现如果包含服务端接口的调用,需要使用 `ensureLoggedIn` 来判断用户是否登录。
### 实现模式
#### 1. 导入必要的 hook
+
```typescript
-import { useAuthGuard } from '@/hooks/useAuthGuard';
+import { useAuthGuard } from "@/hooks/useAuthGuard";
```
#### 2. 在组件中获取方法
+
```typescript
const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
```
#### 3. 替换导航操作
+
```typescript
// ❌ 原来的写法 - 没有登录验证
pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
- activeOpacity={0.7}
- >
- pushIfAuthedElseLogin("/food/nutrition-analysis-history")}
+ activeOpacity={0.7}
+ >
+
+
+
+
+ ) : (
+ pushIfAuthedElseLogin("/food/nutrition-analysis-history")}
+ style={[styles.historyButton, styles.fallbackBackground]}
+ activeOpacity={0.7}
>
-
-
-) : (
- pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
- style={[styles.historyButton, styles.fallbackBackground]}
- activeOpacity={0.7}
- >
-
-
-)}
+
+ );
+}
```
### 重要注意事项
+
1. **统一体验**:使用 `pushIfAuthedElseLogin` 可以确保登录后自动跳转到目标页面
2. **参数传递**:该方法支持传递路由参数,格式为 `pushIfAuthedElseLogin('/path', { param: value })`
3. **登录重定向**:登录页面会接收 `redirectTo` 和 `redirectParams` 参数用于登录后跳转
@@ -382,16 +426,19 @@ const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
6. **异步处理**:`ensureLoggedIn` 是异步函数,需要使用 `await` 等待结果
### 其他可用方法
+
- `ensureLoggedIn()` - 检查登录状态,未登录时跳转到登录页面,返回布尔值表示是否已登录
- `guardHandler(fn, options)` - 包装一个函数,在执行前确保用户已登录
- `isLoggedIn` - 布尔值,表示当前用户是否已登录
### 使用场景选择
+
- **页面导航**:使用 `pushIfAuthedElseLogin` 处理页面跳转
- **服务端接口调用**:使用 `ensureLoggedIn` 验证登录状态后再执行功能
- **函数包装**:使用 `guardHandler` 包装需要登录验证的函数
### 参考实现
+
- `app/food/nutrition-label-analysis.tsx` - 成分表分析功能登录验证
- `app/(tabs)/personal.tsx` - 个人中心编辑按钮
- `hooks/useAuthGuard.ts` - 完整的认证守卫实现
@@ -401,39 +448,44 @@ const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
**最后更新**: 2025-10-16
### 问题描述
+
在应用开发中,所有路由路径都应该使用常量定义,而不是硬编码字符串。这样可以确保路由的一致性,便于维护和重构。
### 解决方案
+
将所有路由路径定义在 `constants/Routes.ts` 文件中,并在组件中使用这些常量。
### 实现模式
#### 1. 添加新路由常量
+
在 `constants/Routes.ts` 文件中添加新的路由常量:
```typescript
export const ROUTES = {
// 现有路由...
-
+
// 新增路由
- FOOD_CAMERA: '/food/camera',
+ FOOD_CAMERA: "/food/camera",
} as const;
```
#### 2. 在组件中使用路由常量
+
导入并使用路由常量,而不是硬编码路径:
```typescript
-import { ROUTES } from '@/constants/Routes';
+import { ROUTES } from "@/constants/Routes";
// ❌ 错误写法 - 硬编码路径
-router.push('/food/camera?mealType=dinner');
+router.push("/food/camera?mealType=dinner");
// ✅ 正确写法 - 使用路由常量
router.push(`${ROUTES.FOOD_CAMERA}?mealType=dinner`);
```
#### 3. 结合登录验证使用
+
对于需要登录验证的路由,结合 `pushIfAuthedElseLogin` 使用:
```typescript
@@ -450,6 +502,7 @@ const { pushIfAuthedElseLogin } = useAuthGuard();
```
### 重要注意事项
+
1. **统一管理**:所有路由路径都必须在 `constants/Routes.ts` 中定义
2. **命名规范**:使用大写字母和下划线,如 `FOOD_CAMERA`
3. **路径一致性**:常量名应该清晰表达路由的用途
@@ -457,26 +510,244 @@ const { pushIfAuthedElseLogin } = useAuthGuard();
5. **类型安全**:使用 `as const` 确保类型推导
### 路由分类
+
按照功能模块对路由进行分组:
```typescript
export const ROUTES = {
// Tab路由
- TAB_EXPLORE: '/explore',
- TAB_COACH: '/coach',
-
+ TAB_EXPLORE: "/explore",
+ TAB_COACH: "/coach",
+
// 营养相关路由
- NUTRITION_RECORDS: '/nutrition/records',
- FOOD_LIBRARY: '/food-library',
- FOOD_CAMERA: '/food/camera',
-
+ NUTRITION_RECORDS: "/nutrition/records",
+ FOOD_LIBRARY: "/food-library",
+ FOOD_CAMERA: "/food/camera",
+
// 用户相关路由
- AUTH_LOGIN: '/auth/login',
- PROFILE_EDIT: '/profile/edit',
+ AUTH_LOGIN: "/auth/login",
+ PROFILE_EDIT: "/profile/edit",
} as const;
```
### 参考实现
+
- `constants/Routes.ts` - 路由常量定义
- `components/NutritionRadarCard.tsx` - 使用路由常量和登录验证
-- `app/food/camera.tsx` - 食物拍照页面实现
\ No newline at end of file
+- `app/food/camera.tsx` - 食物拍照页面实现
+
+## 多语言翻译实现规范
+
+**最后更新**: 2025-11-26
+
+### 重要原则
+
+**所有用户可见的文本都必须支持多语言翻译**,这是项目的基本要求。不允许在代码中硬编码任何用户可见的中文或英文文本。
+
+### 问题描述
+
+在开发新功能或修改现有功能时,所有用户界面文本都需要支持多语言切换,确保应用能够为不同语言用户提供本地化体验。
+
+### 解决方案
+
+使用项目集成的 i18next 翻译系统,在 `i18n/index.ts` 中定义翻译资源,在组件中使用 `useI18n` hook 获取翻译文本。
+
+### 实现模式
+
+#### 1. 导入必要的 hook
+
+```typescript
+import { useI18n } from "@/hooks/useI18n";
+```
+
+#### 2. 在组件中获取翻译函数
+
+```typescript
+const { t } = useI18n();
+```
+
+#### 3. 添加翻译资源
+
+在 `i18n/index.ts` 中为新的功能模块添加翻译资源:
+
+```typescript
+// 中文翻译
+const newFeatureResources = {
+ title: "新功能标题",
+ subtitle: "新功能描述",
+ button: "按钮文本",
+ loading: "加载中...",
+ error: "操作失败,请稍后重试",
+ success: "操作成功",
+};
+
+// 英文翻译
+const newFeatureResourcesEn = {
+ title: "New Feature Title",
+ subtitle: "New feature description",
+ button: "Button Text",
+ loading: "Loading...",
+ error: "Operation failed, please try again later",
+ success: "Operation successful",
+};
+
+// 添加到资源对象中
+resources = {
+ zh: {
+ translation: {
+ // 现有翻译...
+ newFeature: newFeatureResources,
+ },
+ },
+ en: {
+ translation: {
+ // 现有翻译...
+ newFeature: newFeatureResourcesEn,
+ },
+ },
+};
+```
+
+#### 4. 在组件中使用翻译
+
+```typescript
+// ❌ 错误写法 - 硬编码文本
+加载中...
+操作失败,请稍后重试
+
+// ✅ 正确写法 - 使用翻译函数
+{t('newFeature.loading')}
+{t('newFeature.error')}
+```
+
+#### 5. 动态参数翻译
+
+对于包含动态参数的文本,使用插值语法:
+
+```typescript
+// 翻译资源中
+welcome: '欢迎,{{name}}!'
+itemsCount: '共 {{count}} 个项目'
+
+// 组件中使用
+{t('newFeature.welcome', { name: userName })}
+{t('newFeature.itemsCount', { count: items.length })}
+```
+
+#### 6. 嵌套翻译键
+
+对于复杂功能,使用嵌套的翻译键结构:
+
+```typescript
+// 翻译资源
+modal: {
+ title: '确认操作',
+ description: '确定要执行此操作吗?',
+ buttons: {
+ confirm: '确认',
+ cancel: '取消',
+ },
+}
+
+// 组件中使用
+{t('newFeature.modal.title')}
+{t('newFeature.modal.buttons.confirm')}
+```
+
+### 重要注意事项
+
+1. **禁止硬编码**:所有用户可见的文本都必须通过翻译函数获取
+2. **完整翻译**:中文和英文翻译都必须提供,保持翻译完整性
+3. **语义化命名**:翻译键应该清晰表达文本的用途和含义
+4. **参数化文本**:包含动态内容的文本应该使用插值参数
+5. **一致性**:相同功能的文本应该使用相同的翻译键
+6. **Toast 消息**:Toast 提示消息也需要翻译支持
+7. **错误消息**:错误提示信息必须支持多语言
+8. **表单验证**:表单验证错误信息需要翻译
+
+### 常见翻译模式
+
+#### 1. 状态文本
+
+```typescript
+status: {
+ loading: '加载中...',
+ success: '操作成功',
+ error: '操作失败',
+ empty: '暂无数据',
+}
+```
+
+#### 2. 按钮文本
+
+```typescript
+buttons: {
+ confirm: '确认',
+ cancel: '取消',
+ save: '保存',
+ delete: '删除',
+ edit: '编辑',
+ add: '添加',
+}
+```
+
+#### 3. 表单相关
+
+```typescript
+form: {
+ placeholders: {
+ email: '请输入邮箱地址',
+ password: '请输入密码',
+ },
+ errors: {
+ required: '此字段为必填项',
+ invalid: '格式不正确',
+ },
+}
+```
+
+#### 4. 列表和表格
+
+```typescript
+list: {
+ empty: '暂无数据',
+ loading: '加载中...',
+ loadMore: '加载更多',
+ refresh: '刷新',
+}
+```
+
+### 翻译键命名规范
+
+1. **使用小写字母和点号分隔**:`feature.section.item`
+2. **按功能模块分组**:`challenges.title`, `challenges.subtitle`
+3. **语义化命名**:`buttons.confirm`, `errors.network`
+4. **避免缩写**:使用 `description` 而不是 `desc`
+
+### 参考实现
+
+- `app/(tabs)/challenges.tsx` - 完整的多语言翻译实现示例
+- `i18n/index.ts` - 翻译资源配置
+- `hooks/useI18n.ts` - 翻译 hook 实现
+- `app/(tabs)/personal.tsx` - 个人中心页面翻译实现
+- `app/food/nutrition-label-analysis.tsx` - 营养分析页面翻译实现
+
+### 检查清单
+
+在开发新功能时,请确保:
+
+- [ ] 所有用户可见的文本都使用了翻译函数
+- [ ] 在 `i18n/index.ts` 中添加了对应的中文和英文翻译
+- [ ] Toast 消息支持多语言
+- [ ] 错误提示信息支持多语言
+- [ ] 表单验证错误信息支持多语言
+- [ ] 动态参数文本使用了插值语法
+- [ ] 翻译键命名符合规范
+- [ ] 测试了语言切换功能
+
+### 最佳实践
+
+1. **开发时即考虑多语言**:在编写组件时就使用翻译函数,而不是事后添加
+2. **保持翻译一致性**:相同含义的文本使用相同的翻译键
+3. **定期审查**:定期检查是否有硬编码文本遗漏
+4. **测试验证**:在开发完成后测试语言切换功能是否正常
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index f487ad2..332387c 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -99,7 +99,7 @@ export default function TabLayout() {
color: colorTokens.tabIconSelected,
fontSize: 12,
fontWeight: '600',
- marginLeft: 6,
+ marginLeft: 6
}}
numberOfLines={1}
>
diff --git a/app/(tabs)/challenges.tsx b/app/(tabs)/challenges.tsx
index 456b383..5f6f7d4 100644
--- a/app/(tabs)/challenges.tsx
+++ b/app/(tabs)/challenges.tsx
@@ -1,21 +1,32 @@
import dayjs from 'dayjs';
import ChallengeProgressCard from '@/components/challenges/ChallengeProgressCard';
+import { ConfirmationSheet } from '@/components/ui/ConfirmationSheet';
import { Colors } from '@/constants/Colors';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useColorScheme } from '@/hooks/useColorScheme';
+import { useI18n } from '@/hooks/useI18n';
import {
fetchChallenges,
+ joinChallengeByCode,
+ resetJoinByCodeState,
selectChallengeCards,
selectChallengesListError,
selectChallengesListStatus,
+ selectCustomChallengeCards,
+ selectJoinByCodeError,
+ selectJoinByCodeStatus,
+ selectOfficialChallengeCards,
type ChallengeCardViewModel,
} from '@/store/challengesSlice';
+import { Toast } from '@/utils/toast.utils';
+import { Ionicons } from '@expo/vector-icons';
+import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useMemo, useRef } from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
ActivityIndicator,
Animated,
@@ -23,6 +34,7 @@ import {
ScrollView,
StyleSheet,
Text,
+ TextInput,
TouchableOpacity,
View,
useWindowDimensions
@@ -32,11 +44,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
const AVATAR_SIZE = 36;
const CARD_IMAGE_WIDTH = 132;
const CARD_IMAGE_HEIGHT = 96;
-const STATUS_LABELS: Record<'upcoming' | 'ongoing' | 'expired', string> = {
- upcoming: '即将开始',
- ongoing: '进行中',
- expired: '已结束',
-};
const CAROUSEL_ITEM_SPACING = 16;
const MIN_CAROUSEL_CARD_WIDTH = 280;
@@ -45,18 +52,32 @@ const DOT_BASE_SIZE = 6;
export default function ChallengesScreen() {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const insets = useSafeAreaInsets();
+ const { t } = useI18n();
- const { isLoggedIn } = useAuthGuard()
+ const { ensureLoggedIn } = useAuthGuard();
const colorTokens = Colors[theme];
const router = useRouter();
const dispatch = useAppDispatch();
- const challenges = useAppSelector(selectChallengeCards);
+ const glassAvailable = isLiquidGlassAvailable();
+ const allChallenges = useAppSelector(selectChallengeCards);
+ const customChallenges = useAppSelector(selectCustomChallengeCards);
+
+
+ const officialChallenges = useAppSelector(selectOfficialChallengeCards);
+ const joinedCustomChallenges = useMemo(
+ () => customChallenges.filter((item) => item.isJoined),
+ [customChallenges]
+ );
const listStatus = useAppSelector(selectChallengesListStatus);
const listError = useAppSelector(selectChallengesListError);
+ const joinByCodeStatus = useAppSelector(selectJoinByCodeStatus);
+ const joinByCodeError = useAppSelector(selectJoinByCodeError);
+ const [joinModalVisible, setJoinModalVisible] = useState(false);
+ const [shareCodeInput, setShareCodeInput] = useState('');
const ongoingChallenges = useMemo(() => {
const now = dayjs();
- return challenges.filter((challenge) => {
+ return allChallenges.filter((challenge) => {
if (challenge.status !== 'ongoing' || !challenge.isJoined || !challenge.progress) {
return false;
}
@@ -70,7 +91,7 @@ export default function ChallengesScreen() {
return true;
});
- }, [challenges]);
+ }, [allChallenges]);
const progressTrackColor = theme === 'dark' ? 'rgba(255, 255, 255, 0.08)' : '#eceffa';
const progressInactiveColor = theme === 'dark' ? 'rgba(255, 255, 255, 0.24)' : '#dfe4f6';
@@ -85,53 +106,132 @@ export default function ChallengesScreen() {
? ['#1f2230', '#10131e']
: [colorTokens.backgroundGradientStart, colorTokens.backgroundGradientEnd];
+ useEffect(() => {
+ if (!joinModalVisible) {
+ dispatch(resetJoinByCodeState());
+ setShareCodeInput('');
+ }
+ }, [dispatch, joinModalVisible]);
+
+ const handleCreatePress = useCallback(async () => {
+ const ok = await ensureLoggedIn();
+ if (!ok) return;
+ router.push('/challenges/create-custom');
+ }, [ensureLoggedIn, router]);
+
+ const handleOpenJoin = useCallback(async () => {
+ const ok = await ensureLoggedIn();
+ if (!ok) return;
+ setJoinModalVisible(true);
+ }, [ensureLoggedIn]);
+
+ const isJoiningByCode = joinByCodeStatus === 'loading';
+
+ const handleSubmitShareCode = useCallback(async () => {
+ if (isJoiningByCode) return;
+ const ok = await ensureLoggedIn();
+ if (!ok) return;
+ if (!shareCodeInput.trim()) {
+ Toast.warning(t('challenges.invalidInviteCode'));
+ return;
+ }
+ const formatted = shareCodeInput.trim().toUpperCase();
+ try {
+ const result = await dispatch(joinChallengeByCode(formatted)).unwrap();
+ await dispatch(fetchChallenges());
+ setJoinModalVisible(false);
+ Toast.success(t('challenges.joinSuccess'));
+ router.push({ pathname: '/challenges/[id]', params: { id: result.challenge.id } });
+ } catch (error) {
+ const message = typeof error === 'string' ? error : t('challenges.joinFailed');
+ Toast.error(message);
+ }
+ }, [dispatch, ensureLoggedIn, isJoiningByCode, router, shareCodeInput]);
+
const renderChallenges = () => {
- if (listStatus === 'loading' && challenges.length === 0) {
+ if (listStatus === 'loading' && allChallenges.length === 0) {
return (
- 加载挑战中…
+ {t('challenges.loading')}
);
}
- if (listStatus === 'failed' && challenges.length === 0) {
+ if (listStatus === 'failed' && allChallenges.length === 0) {
return (
- {listError ?? '加载挑战失败,请稍后重试'}
+ {listError ?? t('challenges.loadFailed')}
dispatch(fetchChallenges())}
>
- 重新加载
+ {t('challenges.retry')}
);
}
- if (challenges.length === 0) {
+ if (customChallenges.length === 0 && officialChallenges.length === 0) {
return (
- 暂无挑战,稍后再来探索。
+ {t('challenges.empty')}
);
}
- return challenges.map((challenge) => (
-
- router.push({ pathname: '/challenges/[id]', params: { id: challenge.id } })
- }
- />
- ));
+ return (
+
+ {joinedCustomChallenges.length ? (
+ <>
+
+ {t('challenges.customChallenges')}
+
+
+ {joinedCustomChallenges.map((challenge) => (
+
+ router.push({ pathname: '/challenges/[id]', params: { id: challenge.id } })
+ }
+ />
+ ))}
+
+ >
+ ) : null}
+
+
+ {t('challenges.officialChallengesTitle')}
+
+ {officialChallenges.length ? (
+
+ {officialChallenges.map((challenge) => (
+
+ router.push({ pathname: '/challenges/[id]', params: { id: challenge.id } })
+ }
+ />
+ ))}
+
+ ) : (
+
+ {t('challenges.officialChallenges')}
+
+ )}
+
+ );
};
return (
@@ -146,19 +246,42 @@ export default function ChallengesScreen() {
>
- 挑战
- 参与精选活动,保持每日动力
+ {t('challenges.title')}
+ {t('challenges.subtitle')}
+
+
+
+ {glassAvailable ? (
+
+ {t('challenges.join')}
+
+ ) : (
+
+ {t('challenges.join')}
+
+ )}
+
+
+ {glassAvailable ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
- {/*
-
-
-
- */}
{ongoingChallenges.length ? (
@@ -175,6 +298,34 @@ export default function ChallengesScreen() {
{renderChallenges()}
+ setJoinModalVisible(false)}
+ onConfirm={handleSubmitShareCode}
+ title={t('challenges.joinModal.title')}
+ description={t('challenges.joinModal.description')}
+ confirmText={isJoiningByCode ? t('challenges.joinModal.joining') : t('challenges.joinModal.confirm')}
+ cancelText={t('challenges.joinModal.cancel')}
+ loading={isJoiningByCode}
+ content={
+
+ setShareCodeInput(text.toUpperCase())}
+ autoCapitalize="characters"
+ autoCorrect={false}
+ keyboardType="default"
+ maxLength={12}
+ />
+ {joinByCodeError && joinModalVisible ? (
+ {joinByCodeError}
+ ) : null}
+
+ }
+ />
);
}
@@ -188,7 +339,8 @@ type ChallengeCardProps = {
};
function ChallengeCard({ challenge, surfaceColor, textColor, mutedColor, onPress }: ChallengeCardProps) {
- const statusLabel = STATUS_LABELS[challenge.status] ?? challenge.status;
+ const { t } = useI18n();
+ const statusLabel = t(`challenges.statusLabels.${challenge.status}`) ?? challenge.status;
return (
{challenge.participantsLabel}
- {challenge.isJoined ? ' · 已加入' : ''}
+ {challenge.isJoined ? ` · ${t('challenges.joined')}` : ''}
{challenge.avatars.length ? (
@@ -328,7 +480,7 @@ function OngoingChallengesCarousel({
>
{
+ const progressRecord = challenge?.progress as { lastProgressAt?: string; last_progress_at?: string } | undefined;
+ return progressRecord?.lastProgressAt ?? progressRecord?.last_progress_at;
+ }, [challenge?.progress]);
+ const hasCheckedInToday = useMemo(() => {
+ if (!challenge?.progress) {
+ return false;
+ }
+ if (lastProgressAt) {
+ const lastDate = dayjs(lastProgressAt);
+ if (lastDate.isValid() && lastDate.isSame(dayjs(), 'day')) {
+ return true;
+ }
+ }
+ return challenge.progress.checkedInToday ?? false;
+ }, [challenge?.progress, lastProgressAt]);
const rankingData = useMemo(() => {
const source = rankingList?.items ?? challenge?.rankings ?? [];
@@ -165,6 +189,7 @@ export default function ChallengeDetailScreen() {
() => rankingData.filter((item) => item.avatar).map((item) => item.avatar as string).slice(0, 6),
[rankingData],
);
+ const showShareCode = isJoined && Boolean(challenge?.shareCode);
const handleViewAllRanking = () => {
if (!id) {
@@ -192,7 +217,7 @@ export default function ChallengeDetailScreen() {
try {
Toast.show({
type: 'info',
- text1: '正在生成分享卡片...',
+ text1: t('challengeDetail.share.generating'),
});
// 捕获分享卡片视图
@@ -203,8 +228,8 @@ export default function ChallengeDetailScreen() {
// 分享图片
const shareMessage = isJoined && progress
- ? `我正在参与「${challenge.title}」挑战,已完成 ${progress.completed}/${progress.target} 天!一起加入吧!`
- : `发现一个很棒的挑战「${challenge.title}」,一起来参与吧!`;
+ ? t('challengeDetail.share.messageJoined', { title: challenge.title, completed: progress.completed, target: progress.target })
+ : t('challengeDetail.share.messageNotJoined', { title: challenge.title });
await Share.share({
title: challenge.title,
@@ -213,7 +238,7 @@ export default function ChallengeDetailScreen() {
});
} catch (error) {
console.warn('分享失败', error);
- Toast.error('分享失败,请稍后重试');
+ Toast.error(t('challengeDetail.share.failed'));
}
};
@@ -234,7 +259,7 @@ export default function ChallengeDetailScreen() {
await dispatch(fetchChallengeRankings({ id }));
setShowCelebration(true)
} catch (error) {
- Toast.error('加入挑战失败')
+ Toast.error(t('challengeDetail.alert.joinFailed'))
}
};
@@ -246,7 +271,7 @@ export default function ChallengeDetailScreen() {
await dispatch(leaveChallenge(id)).unwrap();
await dispatch(fetchChallengeDetail(id)).unwrap();
} catch (error) {
- Toast.error('退出挑战失败');
+ Toast.error(t('challengeDetail.alert.leaveFailed'));
}
};
@@ -254,34 +279,76 @@ export default function ChallengeDetailScreen() {
if (!id || leaveStatus === 'loading') {
return;
}
- Alert.alert('确认退出挑战?', '退出后需要重新加入才能继续坚持。', [
- { text: '取消', style: 'cancel' },
- {
- text: '退出挑战',
- style: 'destructive',
- onPress: () => {
- void handleLeave();
+ Alert.alert(
+ t('challengeDetail.alert.leaveConfirm.title'),
+ t('challengeDetail.alert.leaveConfirm.message'),
+ [
+ { text: t('challengeDetail.alert.leaveConfirm.cancel'), style: 'cancel' },
+ {
+ text: t('challengeDetail.alert.leaveConfirm.confirm'),
+ style: 'destructive',
+ onPress: () => {
+ void handleLeave();
+ },
},
- },
- ]);
+ ]
+ );
};
- const handleProgressReport = () => {
+ const handleProgressReport = async () => {
if (!id || progressStatus === 'loading') {
return;
}
- dispatch(reportChallengeProgress({ id }));
+
+ if (hasCheckedInToday) {
+ Toast.info(t('challengeDetail.checkIn.toast.alreadyChecked'));
+ return;
+ }
+
+ if (challenge?.status === 'upcoming') {
+ Toast.info(t('challengeDetail.checkIn.toast.notStarted'));
+ return;
+ }
+
+ if (challenge?.status === 'expired') {
+ Toast.info(t('challengeDetail.checkIn.toast.expired'));
+ return;
+ }
+
+ const isLoggedIn = await ensureLoggedIn();
+ if (!isLoggedIn) {
+ return;
+ }
+
+ if (!isJoined) {
+ Toast.info(t('challengeDetail.checkIn.toast.mustJoin'));
+ return;
+ }
+
+ try {
+ await dispatch(reportChallengeProgress({ id, value: 1 })).unwrap();
+ Toast.success(t('challengeDetail.checkIn.toast.success'));
+ } catch (error) {
+ Toast.error(t('challengeDetail.checkIn.toast.failed'));
+ }
+ };
+
+ const handleCopyShareCode = async () => {
+ if (!challenge?.shareCode) return;
+ await Clipboard.setStringAsync(challenge.shareCode);
+ // 添加震动反馈
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
+ Toast.success(t('challengeDetail.shareCode.copied'));
};
- const isJoined = challenge?.isJoined ?? false;
const isLoadingInitial = detailStatus === 'loading' && !challenge;
if (!id) {
return (
- router.back()} withSafeTop transparent={false} />
+ router.back()} withSafeTop transparent={false} />
- 未找到该挑战,稍后再试试吧。
+ {t('challengeDetail.notFound')}
);
@@ -290,10 +357,10 @@ export default function ChallengeDetailScreen() {
if (isLoadingInitial) {
return (
- router.back()} withSafeTop transparent={false} />
+ router.back()} withSafeTop transparent={false} />
- 加载挑战详情中…
+ {t('challengeDetail.loading')}
);
@@ -302,43 +369,43 @@ export default function ChallengeDetailScreen() {
if (!challenge) {
return (
- router.back()} withSafeTop transparent={false} />
+ router.back()} withSafeTop transparent={false} />
- {detailError ?? '未找到该挑战,稍后再试试吧。'}
+ {detailError ?? t('challengeDetail.notFound')}
dispatch(fetchChallengeDetail(id))}
>
- 重新加载
+ {t('challengeDetail.retry')}
);
}
- const highlightTitle = challenge.highlightTitle ?? '立即加入挑战';
- const highlightSubtitle = challenge.highlightSubtitle ?? '邀请好友一起坚持,更容易收获成果';
- const joinCtaLabel = joinStatus === 'loading' ? '加入中…' : challenge.ctaLabel ?? '立即加入挑战';
+ const highlightTitle = challenge.highlightTitle ?? t('challengeDetail.highlight.join.title');
+ const highlightSubtitle = challenge.highlightSubtitle ?? t('challengeDetail.highlight.join.subtitle');
+ const joinCtaLabel = joinStatus === 'loading' ? t('challengeDetail.cta.joining') : challenge.ctaLabel ?? t('challengeDetail.cta.join');
const isUpcoming = challenge.status === 'upcoming';
const isExpired = challenge.status === 'expired';
const upcomingStartLabel = formatMonthDay(challenge.startAt);
- const upcomingHighlightTitle = '挑战即将开始';
+ const upcomingHighlightTitle = t('challengeDetail.highlight.upcoming.title');
const upcomingHighlightSubtitle = upcomingStartLabel
- ? `${upcomingStartLabel} 开始,敬请期待`
- : '挑战即将开启,敬请期待';
- const upcomingCtaLabel = '挑战即将开始';
+ ? t('challengeDetail.highlight.upcoming.subtitle', { date: upcomingStartLabel })
+ : t('challengeDetail.highlight.upcoming.subtitleFallback');
+ const upcomingCtaLabel = t('challengeDetail.cta.upcoming');
const expiredEndLabel = formatMonthDay(challenge.endAt);
- const expiredHighlightTitle = '挑战已结束';
+ const expiredHighlightTitle = t('challengeDetail.highlight.expired.title');
const expiredHighlightSubtitle = expiredEndLabel
- ? `${expiredEndLabel} 已截止,期待下一次挑战`
- : '本轮挑战已结束,期待下一次挑战';
- const expiredCtaLabel = '挑战已结束';
- const leaveHighlightTitle = '先别急着离开';
- const leaveHighlightSubtitle = '再坚持一下,下一个里程碑就要出现了';
- const leaveCtaLabel = leaveStatus === 'loading' ? '退出中…' : '退出挑战';
+ ? t('challengeDetail.highlight.expired.subtitle', { date: expiredEndLabel })
+ : t('challengeDetail.highlight.expired.subtitleFallback');
+ const expiredCtaLabel = t('challengeDetail.cta.expired');
+ const leaveHighlightTitle = t('challengeDetail.highlight.leave.title');
+ const leaveHighlightSubtitle = t('challengeDetail.highlight.leave.subtitle');
+ const leaveCtaLabel = leaveStatus === 'loading' ? t('challengeDetail.cta.leaving') : t('challengeDetail.cta.leave');
let floatingHighlightTitle = highlightTitle;
let floatingHighlightSubtitle = highlightSubtitle;
@@ -349,8 +416,10 @@ export default function ChallengeDetailScreen() {
let isDisabledButtonState = false;
if (isJoined) {
- floatingHighlightTitle = leaveHighlightTitle;
- floatingHighlightSubtitle = leaveHighlightSubtitle;
+ floatingHighlightTitle = showShareCode
+ ? `分享码 ${challenge?.shareCode ?? ''}`
+ : leaveHighlightTitle;
+ floatingHighlightSubtitle = showShareCode ? '' : leaveHighlightSubtitle;
floatingCtaLabel = leaveCtaLabel;
floatingOnPress = handleLeaveConfirm;
floatingDisabled = leaveStatus === 'loading';
@@ -380,6 +449,23 @@ export default function ChallengeDetailScreen() {
const participantsLabel = formatParticipantsLabel(challenge.participantsCount);
const inlineErrorMessage = detailStatus === 'failed' && detailError ? detailError : undefined;
+ const checkInDisabled =
+ progressStatus === 'loading' || hasCheckedInToday || !isJoined || isUpcoming || isExpired;
+ const checkInButtonLabel =
+ progressStatus === 'loading'
+ ? t('challengeDetail.checkIn.button.checking')
+ : hasCheckedInToday
+ ? t('challengeDetail.checkIn.button.checked')
+ : !isJoined
+ ? t('challengeDetail.checkIn.button.notJoined')
+ : isUpcoming
+ ? t('challengeDetail.checkIn.button.upcoming')
+ : isExpired
+ ? t('challengeDetail.checkIn.button.expired')
+ : t('challengeDetail.checkIn.button.checkIn');
+ const checkInSubtitle = hasCheckedInToday
+ ? t('challengeDetail.checkIn.subtitleChecked')
+ : t('challengeDetail.checkIn.subtitle');
return (
@@ -411,9 +497,9 @@ export default function ChallengeDetailScreen() {
// 已加入:显示个人进度
- 我的坚持进度
+ {t('challengeDetail.shareCard.progress.label')}
- {progress.completed} / {progress.target} 天
+ {t('challengeDetail.shareCard.progress.days', { completed: progress.completed, target: progress.target })}
@@ -429,8 +515,8 @@ export default function ChallengeDetailScreen() {
{progress.completed === progress.target
- ? '🎉 已完成挑战!'
- : `还差 ${progress.target - progress.completed} 天完成挑战`}
+ ? t('challengeDetail.shareCard.progress.completed')
+ : t('challengeDetail.shareCard.progress.remaining', { remaining: progress.target - progress.completed })}
) : (
@@ -454,7 +540,7 @@ export default function ChallengeDetailScreen() {
{challenge.requirementLabel}
- 按日打卡自动累计
+ {t('challengeDetail.shareCard.info.checkInDaily')}
@@ -464,7 +550,7 @@ export default function ChallengeDetailScreen() {
{participantsLabel}
- 快来一起坚持吧
+ {t('challengeDetail.shareCard.info.joinUs')}
@@ -472,7 +558,7 @@ export default function ChallengeDetailScreen() {
{/* 底部标识 */}
- Out Live · 超越生命
+ {t('challengeDetail.shareCard.footer')}
@@ -568,7 +654,7 @@ export default function ChallengeDetailScreen() {
{challenge.requirementLabel}
- 按日打卡自动累计
+ {t('challengeDetail.detail.requirement')}
@@ -590,19 +676,50 @@ export default function ChallengeDetailScreen() {
))}
{challenge.participantsCount && challenge.participantsCount > participantAvatars.length ? (
- 更多
+ {t('challengeDetail.participants.more')}
) : null}
) : null}
+
+ {isCustomChallenge ? (
+
+
+ {hasCheckedInToday ? t('challengeDetail.checkIn.todayChecked') : t('challengeDetail.checkIn.title')}
+ {checkInSubtitle}
+
+
+
+
+ {checkInButtonLabel}
+
+
+
+
+ ) : null}
- 排行榜
+ {t('challengeDetail.ranking.title')}
- 查看全部
+ {t('challengeDetail.detail.viewAllRanking')}
@@ -623,7 +740,7 @@ export default function ChallengeDetailScreen() {
))
) : (
- 榜单即将开启,快来抢占席位。
+ {t('challengeDetail.ranking.empty')}
)}
@@ -632,11 +749,30 @@ export default function ChallengeDetailScreen() {
-
- {floatingHighlightTitle}
- {floatingHighlightSubtitle}
- {floatingError ? {floatingError} : null}
-
+ {showShareCode ? (
+
+
+ {floatingHighlightTitle}
+
+
+
+
+ {floatingHighlightSubtitle ? (
+ {floatingHighlightSubtitle}
+ ) : null}
+ {floatingError ? {floatingError} : null}
+
+ ) : (
+
+ {floatingHighlightTitle}
+ {floatingHighlightSubtitle}
+ {floatingError ? {floatingError} : null}
+
+ )}
dayjs().startOf('day').toDate(), []);
+ const defaultEnd = useMemo(() => dayjs().add(21, 'day').startOf('day').toDate(), []);
+
+ const [title, setTitle] = useState('');
+ const [image, setImage] = useState(FALLBACK_IMAGE);
+ const [imagePreview, setImagePreview] = useState(null);
+ const { upload, uploading } = useCosUpload({ prefix: 'images/challenges' });
+ const [type, setType] = useState(ChallengeType.WATER);
+ const [startDate, setStartDate] = useState(today);
+ const [endDate, setEndDate] = useState(defaultEnd);
+ const [targetValue, setTargetValue] = useState('');
+ const [minimumCheckInDays, setMinimumCheckInDays] = useState('');
+ const [requirementLabel, setRequirementLabel] = useState('');
+ const [summary, setSummary] = useState('');
+ const [progressUnit] = useState('天');
+ const [periodLabel, setPeriodLabel] = useState('');
+ const [periodEdited, setPeriodEdited] = useState(false);
+ const [rankingDescription] = useState('连续打卡榜');
+ const [isPublic, setIsPublic] = useState(true);
+ const [maxParticipants, setMaxParticipants] = useState('100');
+ const [minimumEdited, setMinimumEdited] = useState(false);
+
+ const [shareCode, setShareCode] = useState(null);
+ const [shareModalVisible, setShareModalVisible] = useState(false);
+ const [createdChallengeId, setCreatedChallengeId] = useState(null);
+
+ const [pickerType, setPickerType] = useState(null);
+
+ const durationDays = useMemo(
+ () =>
+ Math.max(
+ 1,
+ dayjs(endDate).startOf('day').diff(dayjs(startDate).startOf('day'), 'day') + 1
+ ),
+ [startDate, endDate]
+ );
+ const durationLabel = useMemo(() => `持续${durationDays}天`, [durationDays]);
+
+ useEffect(() => {
+ if (!periodEdited) {
+ setPeriodLabel(`${durationDays}天挑战`);
+ }
+ if (!minimumEdited) {
+ setMinimumCheckInDays(String(durationDays));
+ }
+ }, [durationDays, minimumEdited, periodEdited]);
+
+ const handleConfirmDate = (date: Date) => {
+ if (!pickerType) return;
+ const normalized = dayjs(date).startOf('day');
+ if (pickerType === 'start') {
+ const nextStart = normalized.isAfter(dayjs(), 'day')
+ ? normalized
+ : dayjs().add(1, 'day').startOf('day');
+ setStartDate(nextStart.toDate());
+ if (dayjs(endDate).isSameOrBefore(nextStart)) {
+ const nextEnd = nextStart.add(20, 'day').toDate();
+ setEndDate(nextEnd);
+ }
+ } else {
+ const minEnd = dayjs(startDate).add(1, 'day').startOf('day');
+ const nextEnd = normalized.isAfter(minEnd) ? normalized : minEnd;
+ setEndDate(nextEnd.toDate());
+ }
+ setPickerType(null);
+ };
+
+ const handleSubmit = async () => {
+ if (isCreating) return;
+ if (!title.trim()) {
+ Toast.warning('请填写挑战标题');
+ return;
+ }
+
+ if (!requirementLabel.trim()) {
+ Toast.warning('请填写挑战要求说明');
+ return;
+ }
+
+ const startTimestamp = dayjs(startDate).valueOf();
+ const endTimestamp = dayjs(endDate).valueOf();
+ if (endTimestamp <= startTimestamp) {
+ Toast.warning('结束时间需要晚于开始时间');
+ return;
+ }
+
+ const target = Number(targetValue);
+ if (!Number.isFinite(target) || target < 1 || target > 1000) {
+ Toast.warning('每日目标值需在 1-1000 之间');
+ return;
+ }
+
+ const minDays = Number(minimumCheckInDays) || durationDays;
+ if (!Number.isFinite(minDays) || minDays < 1 || minDays > 365) {
+ Toast.warning('最少打卡天数需在 1-365 之间');
+ return;
+ }
+ if (minDays > durationDays) {
+ Toast.warning('最少打卡天数不能超过持续天数');
+ return;
+ }
+
+ const maxP = maxParticipants ? Number(maxParticipants) : null;
+ if (maxP !== null && (!Number.isFinite(maxP) || maxP < 2 || maxP > 10000)) {
+ Toast.warning('参与人数需在 2-10000 之间,或留空表示无限制');
+ return;
+ }
+
+ const safeTitle = title.trim() || '自定义挑战';
+ const payload: CreateCustomChallengePayload = {
+ title: safeTitle,
+ type,
+ image: image?.trim() || undefined,
+ startAt: startTimestamp,
+ endAt: endTimestamp,
+ targetValue: target,
+ minimumCheckInDays: minDays,
+ durationLabel,
+ requirementLabel: requirementLabel.trim() || '请填写挑战要求',
+ summary: summary.trim() || undefined,
+ progressUnit: progressUnit.trim() || '天',
+ periodLabel: periodLabel.trim() || undefined,
+ rankingDescription: rankingDescription.trim() || undefined,
+ isPublic,
+ maxParticipants: maxP,
+ };
+
+ try {
+ const created = await dispatch(createCustomChallengeThunk(payload)).unwrap();
+ setShareCode(created.shareCode ?? null);
+ setCreatedChallengeId(created.id);
+ setShareModalVisible(true);
+ Toast.success('自定义挑战已创建');
+ dispatch(fetchChallenges());
+ } catch (error) {
+ const message = typeof error === 'string' ? error : '创建失败,请稍后再试';
+ Toast.error(message);
+ }
+ };
+
+ const handleCopyShareCode = async () => {
+ if (!shareCode) return;
+ await Clipboard.setStringAsync(shareCode);
+ Toast.success('邀请码已复制');
+ };
+
+ const handleTargetInputChange = (value: string) => {
+ const digits = value.replace(/\D/g, '');
+ if (!digits) {
+ setTargetValue('');
+ return;
+ }
+ const num = Math.min(1000, parseInt(digits, 10));
+ setTargetValue(String(num));
+ };
+
+ const handleMinimumDaysChange = (value: string) => {
+ const digits = value.replace(/\D/g, '');
+ if (!digits) {
+ setMinimumCheckInDays('');
+ setMinimumEdited(true);
+ return;
+ }
+ const num = Math.max(1, Math.min(365, parseInt(digits, 10)));
+ if (num > durationDays) {
+ setMinimumCheckInDays(String(durationDays));
+ setMinimumEdited(true);
+ return;
+ }
+ setMinimumEdited(true);
+ setMinimumCheckInDays(String(num));
+ };
+
+ const handlePickImage = useCallback(() => {
+ Alert.alert(
+ '选择封面图',
+ '请选择封面来源',
+ [
+ {
+ text: '拍照',
+ onPress: async () => {
+ try {
+ const permission = await ImagePicker.requestCameraPermissionsAsync();
+ if (permission.status !== 'granted') {
+ Alert.alert('权限不足', '需要相机权限以拍摄封面');
+ return;
+ }
+ const result = await ImagePicker.launchCameraAsync({
+ allowsEditing: true,
+ quality: 0.6,
+ aspect: [16, 9],
+ });
+ if (result.canceled || !result.assets?.length) return;
+ const asset = result.assets[0];
+ setImagePreview(asset.uri);
+ setImage(undefined);
+ try {
+ const { url } = await upload(
+ {
+ uri: asset.uri,
+ name: asset.fileName ?? `challenge-${Date.now()}.jpg`,
+ type: asset.mimeType ?? 'image/jpeg',
+ },
+ { prefix: 'images/challenges' }
+ );
+ setImage(url);
+ setImagePreview(null);
+ } catch (error) {
+ console.error('[CHALLENGE] 封面上传失败', error);
+ Alert.alert('上传失败', '封面上传失败,请稍后重试');
+ }
+ } catch (error) {
+ console.error('[CHALLENGE] 拍照失败', error);
+ Alert.alert('拍照失败', '无法打开相机,请稍后再试');
+ }
+ },
+ },
+ {
+ text: '从相册选择',
+ onPress: async () => {
+ try {
+ const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
+ if (permission.status !== 'granted') {
+ Alert.alert('权限不足', '需要相册权限以选择封面');
+ return;
+ }
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ['images'],
+ quality: 0.9,
+ });
+ if (result.canceled || !result.assets?.length) return;
+ const asset = result.assets[0];
+ setImagePreview(asset.uri);
+ setImage(undefined);
+ try {
+ const { url } = await upload(
+ {
+ uri: asset.uri,
+ name: asset.fileName ?? `challenge-${Date.now()}.jpg`,
+ type: asset.mimeType ?? 'image/jpeg',
+ },
+ { prefix: 'images/challenges' }
+ );
+ setImage(url);
+ setImagePreview(null);
+ } catch (error) {
+ console.error('[CHALLENGE] 封面上传失败', error);
+ Alert.alert('上传失败', '封面上传失败,请稍后重试');
+ }
+ } catch (error) {
+ console.error('[CHALLENGE] 选择封面失败', error);
+ Alert.alert('选择失败', '无法打开相册,请稍后再试');
+ }
+ },
+ },
+ { text: '取消', style: 'cancel' },
+ ],
+ { cancelable: true }
+ );
+ }, [upload]);
+
+ const handleViewChallenge = () => {
+ setShareModalVisible(false);
+ if (createdChallengeId) {
+ router.replace({ pathname: '/challenges/[id]', params: { id: createdChallengeId } });
+ }
+ };
+
+ const renderField = (
+ label: string,
+ value: string,
+ onChange: (val: string) => void,
+ placeholder?: string,
+ keyboardType: 'default' | 'numeric' = 'default',
+ onFocus?: () => void
+ ) => (
+
+ {label}
+
+
+ );
+
+ const renderTextarea = (
+ label: string,
+ value: string,
+ onChange: (val: string) => void,
+ placeholder?: string
+ ) => (
+
+ {label}
+
+
+ );
+
+ const progressMeta = `${durationDays} 天 · ${progressUnit || '天'}`;
+ const heroImageSource = imagePreview || image || FALLBACK_IMAGE;
+
+ return (
+
+
+
+
+
+
+
+
+
+ 自定义挑战
+ {title || '你的专属挑战'}
+ {progressMeta}
+
+
+
+
+
+ 基础信息
+ {createError ? {createError} : null}
+
+ {renderField('标题', title, setTitle, '挑战标题(最多100字)')}
+
+ 封面图
+
+
+ {uploading ? '上传中…' : '上传封面'}
+
+ {image || imagePreview ? (
+ {
+ setImagePreview(null);
+ setImage(undefined);
+ }}
+ >
+ 清除
+
+ ) : null}
+
+ 建议比例 16:9,清晰展示挑战氛围
+
+ {renderTextarea('挑战说明', summary, setSummary, '简单介绍这个挑战的目标与要求')}
+
+
+
+ 挑战设置
+
+
+ 挑战类型
+
+ {typeOptions.map((option) => {
+ const active = option.value === type;
+ return (
+ setType(option.value)}
+ style={[
+ styles.chip,
+ active && { backgroundColor: `${option.accent}1A`, borderColor: option.accent },
+ ]}
+ >
+
+ {option.label}
+
+
+ );
+ })}
+
+
+
+
+ 时间范围
+
+ setPickerType('start')}
+ >
+ 开始
+ {dayjs(startDate).format('YYYY.MM.DD')}
+
+ setPickerType('end')}
+ >
+ 结束
+ {dayjs(endDate).format('YYYY.MM.DD')}
+
+
+
+
+
+
+ 持续时间
+
+ {durationLabel}
+
+
+ {renderField('周期标签', periodLabel, (v) => {
+ setPeriodEdited(true);
+ setPeriodLabel(v);
+ }, '如:21天挑战')}
+
+
+
+ {renderField('每日目标值', targetValue, handleTargetInputChange, '如:8', 'numeric')}
+
+ 进度单位
+
+ {progressUnit}
+
+
+
+
+ {renderField('最少打卡天数', minimumCheckInDays, handleMinimumDaysChange, '至少1天', 'numeric')}
+
+ {renderField('挑战要求说明', requirementLabel, setRequirementLabel, '例如:每日完成 30 分钟运动')}
+
+
+
+ 展示&互动
+
+ {renderField('参与人数上限', maxParticipants, (v) => {
+ const digits = v.replace(/\D/g, '');
+ if (!digits) {
+ setMaxParticipants('');
+ return;
+ }
+ setMaxParticipants(String(parseInt(digits, 10)));
+ }, '留空表示无限制', 'numeric')}
+
+
+
+ 是否公开
+ 公开后其他用户可通过邀请码加入
+
+
+
+
+
+
+
+
+
+
+
+ 生成自定义挑战
+ 自动创建分享码,邀请好友一起挑战
+
+
+
+
+ {isCreating ? '创建中…' : '创建并生成邀请码'}
+
+
+
+
+
+
+
+ setPickerType(null)}
+ />
+
+ setShareModalVisible(false)}
+ >
+
+
+ 邀请码已生成
+ 分享给好友即可加入挑战
+
+ {shareCode ?? '获取中…'}
+
+
+
+ 复制邀请码
+
+
+
+ 查看挑战
+
+
+
+ setShareModalVisible(false)}
+ >
+ 稍后再说
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ },
+ scrollContent: {
+ paddingBottom: 160,
+ },
+ heroContainer: {
+ height: 260,
+ width: '100%',
+ overflow: 'hidden',
+ },
+ heroImage: {
+ width: '100%',
+ height: '100%',
+ },
+ heroOverlay: {
+ position: 'absolute',
+ bottom: 22,
+ left: 20,
+ right: 20,
+ },
+ heroKicker: {
+ color: '#f8fafc',
+ fontSize: 13,
+ letterSpacing: 1.2,
+ fontWeight: '700',
+ },
+ heroTitle: {
+ marginTop: 8,
+ fontSize: 26,
+ fontWeight: '800',
+ color: '#ffffff',
+ textShadowColor: 'rgba(0,0,0,0.25)',
+ textShadowOffset: { width: 0, height: 2 },
+ textShadowRadius: 6,
+ },
+ heroMeta: {
+ marginTop: 6,
+ fontSize: 14,
+ color: '#e2e8f0',
+ fontWeight: '600',
+ },
+ formCard: {
+ marginTop: 14,
+ marginHorizontal: 20,
+ padding: 18,
+ borderRadius: 22,
+ backgroundColor: '#ffffff',
+ shadowColor: 'rgba(30, 41, 59, 0.12)',
+ shadowOpacity: 0.2,
+ shadowRadius: 20,
+ shadowOffset: { width: 0, height: 12 },
+ elevation: 8,
+ gap: 10,
+ },
+ sectionTitle: {
+ fontSize: 18,
+ fontWeight: '800',
+ color: '#0f172a',
+ },
+ fieldBlock: {
+ gap: 6,
+ },
+ fieldLabel: {
+ fontSize: 14,
+ fontWeight: '700',
+ color: '#0f172a',
+ },
+ input: {
+ paddingHorizontal: 14,
+ paddingVertical: 12,
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#e5e7eb',
+ backgroundColor: '#f8fafc',
+ fontSize: 15,
+ color: '#111827',
+ },
+ textarea: {
+ minHeight: 90,
+ },
+ chipRow: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 10,
+ },
+ chip: {
+ paddingHorizontal: 14,
+ paddingVertical: 10,
+ borderRadius: 14,
+ backgroundColor: '#f8fafc',
+ borderWidth: 1,
+ borderColor: '#e5e7eb',
+ },
+ chipLabel: {
+ fontSize: 13,
+ color: '#334155',
+ },
+ uploadRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 12,
+ },
+ uploadButton: {
+ paddingHorizontal: 14,
+ paddingVertical: 10,
+ borderRadius: 12,
+ backgroundColor: '#EEF1FF',
+ borderWidth: 1,
+ borderColor: '#d1d5db',
+ },
+ uploadButtonDisabled: {
+ opacity: 0.7,
+ },
+ uploadButtonLabel: {
+ fontSize: 14,
+ fontWeight: '700',
+ color: '#4F5BD5',
+ },
+ clearUpload: {
+ fontSize: 13,
+ fontWeight: '600',
+ color: '#9ca3af',
+ },
+ helperText: {
+ marginTop: 6,
+ fontSize: 12,
+ color: '#6b7280',
+ },
+ dateRow: {
+ flexDirection: 'row',
+ gap: 12,
+ },
+ datePill: {
+ flex: 1,
+ padding: 12,
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#e5e7eb',
+ backgroundColor: '#f8fafc',
+ },
+ dateLabel: {
+ fontSize: 12,
+ color: '#6b7280',
+ },
+ dateValue: {
+ marginTop: 4,
+ fontSize: 15,
+ fontWeight: '700',
+ color: '#0f172a',
+ },
+ readonlyPill: {
+ marginTop: 6,
+ paddingHorizontal: 14,
+ paddingVertical: 10,
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#e5e7eb',
+ backgroundColor: '#f8fafc',
+ },
+ readonlyText: {
+ fontSize: 15,
+ fontWeight: '700',
+ color: '#0f172a',
+ },
+ inlineFields: {
+ gap: 12,
+ },
+ switchRow: {
+ marginTop: 6,
+ padding: 12,
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#e5e7eb',
+ backgroundColor: '#f8fafc',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ switchHint: {
+ marginTop: 4,
+ fontSize: 12,
+ color: '#6b7280',
+ },
+ formHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ inlineError: {
+ fontSize: 12,
+ color: '#ef4444',
+ },
+ floatingCTA: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ bottom: 0,
+ paddingHorizontal: 16,
+ paddingTop: 10,
+ },
+ floatingBlur: {
+ borderRadius: 24,
+ overflow: 'hidden',
+ borderWidth: 1,
+ borderColor: 'rgba(255,255,255,0.6)',
+ backgroundColor: 'rgba(243, 244, 251, 0.9)',
+ },
+ floatingContent: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ gap: 12,
+ paddingHorizontal: 16,
+ paddingVertical: 14,
+ },
+ floatingCopy: {
+ flex: 1,
+ },
+ floatingTitle: {
+ fontSize: 15,
+ fontWeight: '800',
+ color: '#0f172a',
+ },
+ floatingSubtitle: {
+ marginTop: 4,
+ fontSize: 12,
+ color: '#6b7280',
+ },
+ floatingButton: {
+ borderRadius: 16,
+ overflow: 'hidden',
+ },
+ floatingButtonBackground: {
+ paddingHorizontal: 18,
+ paddingVertical: 12,
+ borderRadius: 16,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ floatingButtonLabel: {
+ fontSize: 14,
+ fontWeight: '800',
+ color: '#ffffff',
+ },
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0,0,0,0.35)',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 20,
+ },
+ shareCard: {
+ width: '100%',
+ padding: 20,
+ borderRadius: 22,
+ backgroundColor: '#ffffff',
+ shadowColor: 'rgba(15, 23, 42, 0.18)',
+ shadowOffset: { width: 0, height: 12 },
+ shadowOpacity: 0.25,
+ shadowRadius: 20,
+ elevation: 12,
+ alignItems: 'center',
+ gap: 10,
+ },
+ shareTitle: {
+ fontSize: 18,
+ fontWeight: '800',
+ color: '#0f172a',
+ },
+ shareSubtitle: {
+ fontSize: 13,
+ color: '#6b7280',
+ },
+ shareCodeBadge: {
+ marginTop: 10,
+ paddingHorizontal: 18,
+ paddingVertical: 12,
+ borderRadius: 16,
+ backgroundColor: '#EEF1FF',
+ },
+ shareCode: {
+ fontSize: 22,
+ fontWeight: '800',
+ color: '#4F5BD5',
+ letterSpacing: 2,
+ },
+ shareActions: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ gap: 12,
+ width: '100%',
+ marginTop: 8,
+ },
+ shareButtonGhost: {
+ flex: 1,
+ paddingVertical: 12,
+ borderRadius: 14,
+ borderWidth: 1,
+ borderColor: '#d1d5db',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: '#f8fafc',
+ },
+ shareButtonGhostLabel: {
+ fontSize: 14,
+ fontWeight: '700',
+ color: '#475569',
+ },
+ shareButtonPrimary: {
+ flex: 1,
+ borderRadius: 14,
+ overflow: 'hidden',
+ },
+ shareButtonPrimaryLabel: {
+ textAlign: 'center',
+ fontSize: 14,
+ fontWeight: '800',
+ color: '#ffffff',
+ paddingVertical: 12,
+ },
+ shareClose: {
+ marginTop: 8,
+ paddingVertical: 10,
+ paddingHorizontal: 12,
+ },
+ shareCloseLabel: {
+ fontSize: 13,
+ color: '#6b7280',
+ },
+});
diff --git a/app/index.tsx b/app/index.tsx
index be993cb..9a4a1e0 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -2,7 +2,8 @@ import { ThemedView } from '@/components/ThemedView';
import { ROUTES } from '@/constants/Routes';
import { usePushNotifications } from '@/hooks/usePushNotifications';
import { useThemeColor } from '@/hooks/useThemeColor';
-import { preloadUserData } from '@/store/userSlice';
+import { STORAGE_KEYS } from '@/services/api';
+import AsyncStorage from '@/utils/kvStore';
import { router } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, View } from 'react-native';
@@ -19,10 +20,11 @@ export default function SplashScreen() {
const checkOnboardingStatus = async () => {
try {
- // 先预加载用户数据,包括 onboarding 状态
- console.log('开始预加载用户数据(包含 onboarding 状态)...');
- const userData = await preloadUserData();
- console.log('用户数据预加载完成,onboarding 状态:', userData.onboardingCompleted);
+ // 直接读取 onboarding 状态
+ console.log('检查 onboarding 状态...');
+ const onboardingCompletedStr = await AsyncStorage.getItem(STORAGE_KEYS.onboardingCompleted);
+ const onboardingCompleted = onboardingCompletedStr === 'true';
+ console.log('Onboarding 状态:', onboardingCompleted);
// 初始化推送通知(不阻塞应用启动,且不会请求权限)
console.log('开始初始化推送通知基础服务...');
@@ -30,8 +32,8 @@ export default function SplashScreen() {
console.warn('推送通知初始化失败,但不影响应用正常使用:', error);
});
- // 根据预加载的状态决定跳转
- if (userData.onboardingCompleted) {
+ // 根据状态决定跳转
+ if (onboardingCompleted) {
console.log('用户已完成引导,跳转到统计页面');
router.replace(ROUTES.TAB_STATISTICS);
} else {
@@ -39,7 +41,7 @@ export default function SplashScreen() {
router.replace(ROUTES.ONBOARDING);
}
} catch (error) {
- console.error('检查引导状态或预加载用户数据失败:', error);
+ console.error('检查引导状态失败:', error);
// 如果出现错误,默认进入主应用(假设已完成引导)
router.replace(ROUTES.TAB_STATISTICS);
}
diff --git a/assets/fonts/ali-bold.ttf b/assets/fonts/ali-bold.ttf
new file mode 100644
index 0000000..860a48a
Binary files /dev/null and b/assets/fonts/ali-bold.ttf differ
diff --git a/assets/fonts/ali-regular.ttf b/assets/fonts/ali-regular.ttf
new file mode 100644
index 0000000..0a05b19
Binary files /dev/null and b/assets/fonts/ali-regular.ttf differ
diff --git a/components/challenges/ChallengeProgressCard.tsx b/components/challenges/ChallengeProgressCard.tsx
index 6a6623f..2e3861b 100644
--- a/components/challenges/ChallengeProgressCard.tsx
+++ b/components/challenges/ChallengeProgressCard.tsx
@@ -215,11 +215,13 @@ const styles = StyleSheet.create({
title: {
fontSize: 18,
fontWeight: '700',
+ fontFamily: 'AliBold'
},
remaining: {
fontSize: 11,
fontWeight: '600',
alignSelf: 'flex-start',
+ fontFamily: 'AliRegular'
},
metaRow: {
marginTop: 12,
@@ -227,10 +229,12 @@ const styles = StyleSheet.create({
metaValue: {
fontSize: 14,
fontWeight: '700',
+ fontFamily: 'AliBold'
},
metaSuffix: {
fontSize: 13,
fontWeight: '500',
+ fontFamily: 'AliBold'
},
track: {
marginTop: 12,
diff --git a/components/ui/ConfirmationSheet.tsx b/components/ui/ConfirmationSheet.tsx
index ae34582..d970644 100644
--- a/components/ui/ConfirmationSheet.tsx
+++ b/components/ui/ConfirmationSheet.tsx
@@ -1,10 +1,13 @@
+import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
import * as Haptics from 'expo-haptics';
import React, { useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Animated,
Dimensions,
+ KeyboardAvoidingView,
Modal,
+ Platform,
StyleSheet,
Text,
TouchableOpacity,
@@ -24,6 +27,7 @@ interface ConfirmationSheetProps {
cancelText?: string;
destructive?: boolean;
loading?: boolean;
+ content?: React.ReactNode;
}
export function ConfirmationSheet({
@@ -36,11 +40,13 @@ export function ConfirmationSheet({
cancelText = '取消',
destructive = false,
loading = false,
+ content,
}: ConfirmationSheetProps) {
const insets = useSafeAreaInsets();
const translateY = useRef(new Animated.Value(screenHeight)).current;
const backdropOpacity = useRef(new Animated.Value(0)).current;
const [modalVisible, setModalVisible] = useState(visible);
+ const isGlassAvailable = isLiquidGlassAvailable();
useEffect(() => {
if (visible) {
@@ -116,7 +122,10 @@ export function ConfirmationSheet({
onRequestClose={onClose}
statusBarTranslucent
>
-
+
{title}
{description ? {description} : null}
+ {content}
- {cancelText}
+ {isGlassAvailable ? (
+
+ {cancelText}
+
+ ) : (
+
+ {cancelText}
+
+ )}
- {loading ? (
-
+ {isGlassAvailable ? (
+
+ {loading ? (
+
+ ) : (
+ {confirmText}
+ )}
+
) : (
- {confirmText}
+
+ {loading ? (
+
+ ) : (
+ {confirmText}
+ )}
+
)}
-
+
);
}
@@ -221,8 +262,17 @@ const styles = StyleSheet.create({
gap: 12,
marginTop: 8,
},
- cancelButton: {
+ buttonContainer: {
flex: 1,
+ },
+ glassButton: {
+ height: 56,
+ borderRadius: 18,
+ alignItems: 'center',
+ justifyContent: 'center',
+ overflow: 'hidden',
+ },
+ cancelButton: {
height: 56,
borderRadius: 18,
borderWidth: 1,
@@ -237,7 +287,6 @@ const styles = StyleSheet.create({
color: '#111827',
},
confirmButton: {
- flex: 1,
height: 56,
borderRadius: 18,
alignItems: 'center',
diff --git a/hooks/useAuthGuard.ts b/hooks/useAuthGuard.ts
index 620e9b3..f8e603d 100644
--- a/hooks/useAuthGuard.ts
+++ b/hooks/useAuthGuard.ts
@@ -22,7 +22,9 @@ export function useAuthGuard() {
const currentPath = usePathname();
const user = useAppSelector(state => state.user);
- const isLoggedIn = !!user?.profile?.id;
+ // 判断登录状态:优先使用 token,因为 token 是登录的根本凭证
+ // profile.id 可能在初始化时还未加载,但 token 已经从 AsyncStorage 恢复
+ const isLoggedIn = !!user?.token;
const ensureLoggedIn = useCallback(async (options?: EnsureOptions): Promise => {
if (isLoggedIn) return true;
diff --git a/i18n/index.ts b/i18n/index.ts
index 295ccd6..2af1178 100644
--- a/i18n/index.ts
+++ b/i18n/index.ts
@@ -851,6 +851,222 @@ const medicationsResources = {
},
};
+const challengeDetailResources = {
+ title: '挑战详情',
+ notFound: '未找到该挑战,稍后再试试吧。',
+ loading: '加载挑战详情中…',
+ retry: '重新加载',
+ share: {
+ generating: '正在生成分享卡片...',
+ failed: '分享失败,请稍后重试',
+ messageJoined: '我正在参与「{{title}}」挑战,已完成 {{completed}}/{{target}} 天!一起加入吧!',
+ messageNotJoined: '发现一个很棒的挑战「{{title}}」,一起来参与吧!',
+ },
+ dateRange: {
+ format: '{{start}} - {{end}}',
+ monthDay: '{{month}}月{{day}}日',
+ ongoing: '持续更新中',
+ },
+ participants: {
+ count: '{{count}} 人正在参与',
+ ongoing: '持续更新中',
+ more: '更多',
+ },
+ detail: {
+ requirement: '按日打卡自动累计',
+ viewAllRanking: '查看全部',
+ },
+ checkIn: {
+ title: '挑战打卡',
+ todayChecked: '今日已打卡',
+ subtitle: '每日打卡会累计进度,达成目标天数',
+ subtitleChecked: '已记录今日进度,明天继续保持',
+ button: {
+ checkIn: '立即打卡',
+ checking: '打卡中…',
+ checked: '今日已打卡',
+ notJoined: '加入后打卡',
+ upcoming: '挑战未开始',
+ expired: '挑战已结束',
+ },
+ toast: {
+ alreadyChecked: '今日已打卡',
+ notStarted: '挑战未开始,开始后再来打卡',
+ expired: '挑战已结束,无法打卡',
+ mustJoin: '加入挑战后才能打卡',
+ success: '打卡成功,继续坚持!',
+ failed: '打卡失败,请稍后再试',
+ },
+ },
+ cta: {
+ join: '立即加入挑战',
+ joining: '加入中…',
+ leave: '退出挑战',
+ leaving: '退出中…',
+ upcoming: '挑战即将开始',
+ expired: '挑战已结束',
+ },
+ highlight: {
+ join: {
+ title: '立即加入挑战',
+ subtitle: '邀请好友一起坚持,更容易收获成果',
+ },
+ leave: {
+ title: '先别急着离开',
+ subtitle: '再坚持一下,下一个里程碑就要出现了',
+ },
+ upcoming: {
+ title: '挑战即将开始',
+ subtitle: '{{date}} 开始,敬请期待',
+ subtitleFallback: '挑战即将开启,敬请期待',
+ },
+ expired: {
+ title: '挑战已结束',
+ subtitle: '{{date}} 已截止,期待下一次挑战',
+ subtitleFallback: '本轮挑战已结束,期待下一次挑战',
+ },
+ },
+ alert: {
+ leaveConfirm: {
+ title: '确认退出挑战?',
+ message: '退出后需要重新加入才能继续坚持。',
+ cancel: '取消',
+ confirm: '退出挑战',
+ },
+ joinFailed: '加入挑战失败',
+ leaveFailed: '退出挑战失败',
+ },
+ ranking: {
+ title: '排行榜',
+ description: '',
+ empty: '榜单即将开启,快来抢占席位。',
+ },
+ shareCard: {
+ footer: 'Out Live · 超越生命',
+ progress: {
+ label: '我的坚持进度',
+ days: '{{completed}} / {{target}} 天',
+ completed: '🎉 已完成挑战!',
+ remaining: '还差 {{remaining}} 天完成挑战',
+ },
+ info: {
+ checkInDaily: '按日打卡',
+ joinUs: '快来一起坚持吧',
+ },
+ shareCode: {
+ copied: '分享码已复制',
+ },
+ },
+};
+
+const challengeDetailResourcesEn = {
+ title: 'Challenge Details',
+ notFound: 'Challenge not found, please try again later.',
+ loading: 'Loading challenge details…',
+ retry: 'Reload',
+ share: {
+ generating: 'Generating share card...',
+ failed: 'Share failed, please try again later',
+ messageJoined: 'I\'m participating in "{{title}}" challenge, completed {{completed}}/{{target}} days! Join me!',
+ messageNotJoined: 'Found an amazing challenge "{{title}}", let\'s join together!',
+ },
+ dateRange: {
+ format: '{{start}} - {{end}}',
+ monthDay: 'Month {{month}} Day {{day}}',
+ ongoing: 'Ongoing updates',
+ },
+ participants: {
+ count: '{{count}} participants',
+ ongoing: 'Ongoing updates',
+ more: 'More',
+ },
+ detail: {
+ requirement: 'Daily check-in auto accumulates',
+ viewAllRanking: 'View All',
+ },
+ checkIn: {
+ title: 'Challenge Check-in',
+ todayChecked: 'Checked in today',
+ subtitle: 'Daily check-ins accumulate progress towards goal',
+ subtitleChecked: 'Today\'s progress recorded, keep it up tomorrow',
+ button: {
+ checkIn: 'Check In Now',
+ checking: 'Checking in…',
+ checked: 'Checked in today',
+ notJoined: 'Join to check in',
+ upcoming: 'Not started yet',
+ expired: 'Challenge ended',
+ },
+ toast: {
+ alreadyChecked: 'Already checked in today',
+ notStarted: 'Challenge not started yet, check in after it begins',
+ expired: 'Challenge has ended, cannot check in',
+ mustJoin: 'Join the challenge to check in',
+ success: 'Check-in successful, keep going!',
+ failed: 'Check-in failed, please try again',
+ },
+ },
+ cta: {
+ join: 'Join Challenge',
+ joining: 'Joining…',
+ leave: 'Leave Challenge',
+ leaving: 'Leaving…',
+ upcoming: 'Starting Soon',
+ expired: 'Challenge Ended',
+ },
+ highlight: {
+ join: {
+ title: 'Join Challenge Now',
+ subtitle: 'Invite friends to persist together, achieve more easily',
+ },
+ leave: {
+ title: 'Don\'t leave just yet',
+ subtitle: 'Keep going, the next milestone is around the corner',
+ },
+ upcoming: {
+ title: 'Challenge Starting Soon',
+ subtitle: 'Starts on {{date}}, stay tuned',
+ subtitleFallback: 'Challenge coming soon, stay tuned',
+ },
+ expired: {
+ title: 'Challenge Ended',
+ subtitle: 'Ended on {{date}}, look forward to the next one',
+ subtitleFallback: 'This round has ended, look forward to the next challenge',
+ },
+ },
+ alert: {
+ leaveConfirm: {
+ title: 'Confirm leaving challenge?',
+ message: 'You will need to rejoin to continue.',
+ cancel: 'Cancel',
+ confirm: 'Leave Challenge',
+ },
+ joinFailed: 'Failed to join challenge',
+ leaveFailed: 'Failed to leave challenge',
+ },
+ ranking: {
+ title: 'Leaderboard',
+ description: '',
+ empty: 'Leaderboard opening soon, grab your spot.',
+ },
+ shareCard: {
+ footer: 'Out Live · Beyond Life',
+ progress: {
+ label: 'My Progress',
+ days: '{{completed}} / {{target}} days',
+ completed: '🎉 Challenge Completed!',
+ remaining: '{{remaining}} days to complete',
+ },
+ info: {
+ checkInDaily: 'Daily check-in',
+ joinUs: 'Join us!',
+ },
+ shareCode: {
+ copied: 'Share code copied',
+ },
+ },
+};
+
const notificationSettingsResources = {
title: '通知设置',
loading: '加载中...',
@@ -926,6 +1142,37 @@ const resources = {
statistics: statisticsResources,
medications: medicationsResources,
notificationSettings: notificationSettingsResources,
+ challengeDetail: challengeDetailResources,
+ challenges: {
+ title: '挑战',
+ subtitle: '参与精选活动,保持每日动力',
+ loading: '加载挑战中…',
+ loadFailed: '加载挑战失败,请稍后重试',
+ retry: '重新加载',
+ empty: '暂无挑战,稍后再来探索。',
+ customChallenges: '自定义挑战',
+ officialChallenges: '暂无官方挑战,稍后再来探索。',
+ officialChallengesTitle: '官方挑战',
+ join: '加入',
+ create: '创建',
+ joined: '已加入',
+ invalidInviteCode: '请输入有效的邀请码',
+ joinSuccess: '加入挑战成功',
+ joinFailed: '加入失败,请稍后再试',
+ joinModal: {
+ title: '加入自定义挑战',
+ description: '输入 6-12 位邀请码,加入好友的挑战',
+ placeholder: '如:A3K9P2',
+ confirm: '确认加入',
+ cancel: '取消',
+ joining: '加入中…',
+ },
+ statusLabels: {
+ upcoming: '即将开始',
+ ongoing: '进行中',
+ expired: '已结束',
+ },
+ },
},
},
en: {
@@ -1753,6 +2000,37 @@ const resources = {
},
resetSuccess: 'Settings reset to default',
},
+ challengeDetail: challengeDetailResourcesEn,
+ challenges: {
+ title: 'Challenges',
+ subtitle: 'Join curated activities, stay motivated daily',
+ loading: 'Loading challenges…',
+ loadFailed: 'Failed to load challenges, please try again later',
+ retry: 'Retry',
+ empty: 'No challenges available, check back later.',
+ customChallenges: 'Custom Challenges',
+ officialChallenges: 'No official challenges available, check back later.',
+ officialChallengesTitle: 'Official Challenges',
+ join: 'Join',
+ create: 'Create',
+ joined: 'Joined',
+ invalidInviteCode: 'Please enter a valid invite code',
+ joinSuccess: 'Successfully joined challenge',
+ joinFailed: 'Failed to join challenge, please try again later',
+ joinModal: {
+ title: 'Join Custom Challenge',
+ description: 'Enter 6-12 digit invite code to join friend\'s challenge',
+ placeholder: 'e.g., A3K9P2',
+ confirm: 'Confirm Join',
+ cancel: 'Cancel',
+ joining: 'Joining…',
+ },
+ statusLabels: {
+ upcoming: 'Upcoming',
+ ongoing: 'Ongoing',
+ expired: 'Expired',
+ },
+ },
},
},
};
diff --git a/ios/OutLive.xcodeproj/project.pbxproj b/ios/OutLive.xcodeproj/project.pbxproj
index bb8d61c..cc76989 100644
--- a/ios/OutLive.xcodeproj/project.pbxproj
+++ b/ios/OutLive.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 70;
+ objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
@@ -91,7 +91,7 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
- 79E80BBB2EC5D92B004425BE /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
+ 79E80BBB2EC5D92B004425BE /* Exceptions for "medicine" folder in "medicineExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@@ -101,7 +101,18 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
- 79E80BA72EC5D92A004425BE /* medicine */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (79E80BBB2EC5D92B004425BE /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = medicine; sourceTree = ""; };
+ 79E80BA72EC5D92A004425BE /* medicine */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 79E80BBB2EC5D92B004425BE /* Exceptions for "medicine" folder in "medicineExtension" target */,
+ );
+ explicitFileTypes = {
+ };
+ explicitFolders = (
+ );
+ path = medicine;
+ sourceTree = "";
+ };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
diff --git a/ios/OutLive/Info.plist b/ios/OutLive/Info.plist
index aa7fa3b..7455418 100644
--- a/ios/OutLive/Info.plist
+++ b/ios/OutLive/Info.plist
@@ -27,7 +27,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0.30
+ 1.1.1
CFBundleSignature
????
CFBundleURLTypes
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 4ed097c..7846eff 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -8,7 +8,7 @@ PODS:
- React-Core
- EXNotifications (0.32.12):
- ExpoModulesCore
- - Expo (54.0.21):
+ - Expo (54.0.25):
- ExpoModulesCore
- hermes-engine
- RCTRequired
@@ -35,25 +35,27 @@ PODS:
- Yoga
- ExpoAppleAuthentication (8.0.7):
- ExpoModulesCore
- - ExpoAsset (12.0.9):
+ - ExpoAsset (12.0.10):
- ExpoModulesCore
- ExpoBackgroundTask (1.0.8):
- ExpoModulesCore
- ExpoBlur (15.0.7):
- ExpoModulesCore
- - ExpoCamera (17.0.8):
+ - ExpoCamera (17.0.9):
- ExpoModulesCore
- ZXingObjC/OneD
- ZXingObjC/PDF417
- - ExpoFileSystem (19.0.17):
+ - ExpoClipboard (8.0.7):
+ - ExpoModulesCore
+ - ExpoFileSystem (19.0.19):
- ExpoModulesCore
- ExpoFont (14.0.9):
- ExpoModulesCore
- - ExpoGlassEffect (0.1.5):
+ - ExpoGlassEffect (0.1.7):
- ExpoModulesCore
- ExpoHaptics (15.0.7):
- ExpoModulesCore
- - ExpoHead (6.0.14):
+ - ExpoHead (6.0.15):
- ExpoModulesCore
- RNScreens
- ExpoImage (3.0.10):
@@ -69,14 +71,14 @@ PODS:
- ExpoModulesCore
- ExpoLinearGradient (15.0.7):
- ExpoModulesCore
- - ExpoLinking (8.0.8):
+ - ExpoLinking (8.0.9):
- ExpoModulesCore
- ExpoLocalization (17.0.7):
- ExpoModulesCore
- ExpoMediaLibrary (18.2.0):
- ExpoModulesCore
- React-Core
- - ExpoModulesCore (3.0.23):
+ - ExpoModulesCore (3.0.26):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -101,7 +103,7 @@ PODS:
- Yoga
- ExpoQuickActions (6.0.0):
- ExpoModulesCore
- - ExpoSplashScreen (31.0.10):
+ - ExpoSplashScreen (31.0.11):
- ExpoModulesCore
- ExpoSQLite (16.0.8):
- ExpoModulesCore
@@ -161,8 +163,8 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - PurchasesHybridCommon (17.10.0):
- - RevenueCat (= 5.43.0)
+ - PurchasesHybridCommon (17.19.1):
+ - RevenueCat (= 5.48.0)
- RCTDeprecation (0.81.5)
- RCTRequired (0.81.5)
- RCTTypeSafety (0.81.5):
@@ -1909,7 +1911,7 @@ PODS:
- React-utils (= 0.81.5)
- ReactNativeDependencies
- ReactNativeDependencies (0.81.5)
- - RevenueCat (5.43.0)
+ - RevenueCat (5.48.0)
- RNCAsyncStorage (2.2.0):
- hermes-engine
- RCTRequired
@@ -1954,7 +1956,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - RNCPicker (2.11.1):
+ - RNCPicker (2.11.4):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -1976,7 +1978,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - RNDateTimePicker (8.4.4):
+ - RNDateTimePicker (8.5.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2022,10 +2024,10 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - RNPurchases (9.5.4):
- - PurchasesHybridCommon (= 17.10.0)
+ - RNPurchases (9.6.7):
+ - PurchasesHybridCommon (= 17.19.1)
- React-Core
- - RNReanimated (4.1.3):
+ - RNReanimated (4.1.5):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2047,10 +2049,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- - RNReanimated/reanimated (= 4.1.3)
+ - RNReanimated/reanimated (= 4.1.5)
- RNWorklets
- Yoga
- - RNReanimated/reanimated (4.1.3):
+ - RNReanimated/reanimated (4.1.5):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2072,10 +2074,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- - RNReanimated/reanimated/apple (= 4.1.3)
+ - RNReanimated/reanimated/apple (= 4.1.5)
- RNWorklets
- Yoga
- - RNReanimated/reanimated/apple (4.1.3):
+ - RNReanimated/reanimated/apple (4.1.5):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2146,7 +2148,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - RNSentry (7.2.0):
+ - RNSentry (7.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2168,7 +2170,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- - Sentry/HybridSDK (= 8.56.1)
+ - Sentry/HybridSDK (= 8.57.3)
- Yoga
- RNSVG (15.12.1):
- hermes-engine
@@ -2297,7 +2299,7 @@ PODS:
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- - Sentry/HybridSDK (8.56.1)
+ - Sentry/HybridSDK (8.57.3)
- UMAppLoader (6.0.7)
- Yoga (0.0.0)
- ZXingObjC/Core (3.6.9)
@@ -2317,6 +2319,7 @@ DEPENDENCIES:
- ExpoBackgroundTask (from `../node_modules/expo-background-task/ios`)
- ExpoBlur (from `../node_modules/expo-blur/ios`)
- ExpoCamera (from `../node_modules/expo-camera/ios`)
+ - ExpoClipboard (from `../node_modules/expo-clipboard/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
- ExpoFont (from `../node_modules/expo-font/ios`)
- ExpoGlassEffect (from `../node_modules/expo-glass-effect/ios`)
@@ -2462,6 +2465,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-blur/ios"
ExpoCamera:
:path: "../node_modules/expo-camera/ios"
+ ExpoClipboard:
+ :path: "../node_modules/expo-clipboard/ios"
ExpoFileSystem:
:path: "../node_modules/expo-file-system/ios"
ExpoFont:
@@ -2683,27 +2688,28 @@ SPEC CHECKSUMS:
EXConstants: fd688cef4e401dcf798a021cfb5d87c890c30ba3
EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05
EXNotifications: 7cff475adb5d7a255a9ea46bbd2589cb3b454506
- Expo: 27ae59be9be4feab2b1c1ae06550752c524ca558
+ Expo: 111394d38f32be09385d4c7f70cc96d2da438d0d
ExpoAppleAuthentication: bc9de6e9ff3340604213ab9031d4c4f7f802623e
- ExpoAsset: 9ba6fbd677fb8e241a3899ac00fa735bc911eadf
+ ExpoAsset: d839c8eae8124470332408427327e8f88beb2dfd
ExpoBackgroundTask: e0d201d38539c571efc5f9cb661fae8ab36ed61b
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f
- ExpoCamera: e75f6807a2c047f3338bbadd101af4c71a1d13a5
- ExpoFileSystem: b79eadbda7b7f285f378f95f959cc9313a1c9c61
+ ExpoCamera: 2a87c210f8955350ea5c70f1d539520b2fc5d940
+ ExpoClipboard: af650d14765f19c60ce2a1eaf9dfe6445eff7365
+ ExpoFileSystem: 77157a101e03150a4ea4f854b4dd44883c93ae0a
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961
- ExpoGlassEffect: 779c46bd04ea47ba4726efb73267b5bcc6abd664
+ ExpoGlassEffect: 265fa3d75b46bc58262e4dfa513135fa9dfe4aac
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84
- ExpoHead: e317214fa14edeaf17748d39ec9e550a3d1194fb
+ ExpoHead: 95a6ee0be1142320bccf07961d6a1502ded5d6ac
ExpoImage: 9c3428921c536ab29e5c6721d001ad5c1f469566
ExpoImagePicker: d251aab45a1b1857e4156fed88511b278b4eee1c
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27
- ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d
+ ExpoLinking: 77455aa013e9b6a3601de03ecfab09858ee1b031
ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca
ExpoMediaLibrary: 641a6952299b395159ccd459bd8f5f6764bf55fe
- ExpoModulesCore: 5f20603cf25698682d7c43c05fbba8c748b189d2
+ ExpoModulesCore: e8ec7f8727caf51a49d495598303dd420ca994bf
ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f
- ExpoSplashScreen: cbb839de72110dea1851dd3e85080b7923af2540
+ ExpoSplashScreen: 268b2f128dc04284c21010540a6c4dd9f95003e3
ExpoSQLite: 7fa091ba5562474093fef09be644161a65e11b3f
ExpoSymbols: 1ae04ce686de719b9720453b988d8bc5bf776c68
ExpoSystemUI: 2761aa6875849af83286364811d46e8ed8ea64c7
@@ -2717,7 +2723,7 @@ SPEC CHECKSUMS:
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
- PurchasesHybridCommon: b7b4eafb55fbaaac19b4c36d4082657a3f0d8490
+ PurchasesHybridCommon: a4837eebc889b973668af685d6c23b89a038461d
RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990
RCTRequired: 8f3cfc90cc25cf6e420ddb3e7caaaabc57df6043
RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c
@@ -2787,24 +2793,24 @@ SPEC CHECKSUMS:
ReactCodegen: 7d4593f7591f002d137fe40cef3f6c11f13c88cc
ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8
ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a
- RevenueCat: a51003d4cb33820cc504cf177c627832b462a98e
+ RevenueCat: 1e61140a343a77dc286f171b3ffab99ca09a4b57
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
RNCMaskedView: d2578d41c59b936db122b2798ba37e4722d21035
- RNCPicker: a7170edbcbf8288de8edb2502e08e7fc757fa755
- RNDateTimePicker: be0e44bcb9ed0607c7c5f47dbedd88cf091f6791
+ RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
+ RNDateTimePicker: 19ffa303c4524ec0a2dfdee2658198451c16b7f1
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
- RNPurchases: 2569675abdc1dbc739f2eec0fa564a112cf860de
- RNReanimated: 3895a29fdf77bbe2a627e1ed599a5e5d1df76c29
+ RNPurchases: 5f3cd4fea5ef2b3914c925b2201dd5cecd31922f
+ RNReanimated: 1442a577e066e662f0ce1cd1864a65c8e547aee0
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
- RNSentry: 41979b419908128847ef662cc130a400b7576fa9
+ RNSentry: 1d7b9fdae7a01ad8f9053335b5d44e75c39a955e
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
- Sentry: b3ec44d01708fce73f99b544beb57e890eca4406
+ Sentry: c643eb180df401dd8c734c5036ddd9dd9218daa6
UMAppLoader: e1234c45d2b7da239e9e90fc4bbeacee12afd5b6
Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
diff --git a/package-lock.json b/package-lock.json
index 0540456..bb967ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,27 +10,28 @@
"dependencies": {
"@expo/metro-runtime": "~6.1.2",
"@expo/ui": "~0.2.0-beta.7",
- "@expo/vector-icons": "^15.0.2",
+ "@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "^2.2.0",
- "@react-native-community/datetimepicker": "8.4.4",
+ "@react-native-community/datetimepicker": "8.5.1",
"@react-native-masked-view/masked-view": "^0.3.2",
- "@react-native-picker/picker": "2.11.1",
+ "@react-native-picker/picker": "2.11.4",
"@react-native-voice/voice": "^3.2.4",
- "@react-navigation/bottom-tabs": "^7.4.0",
- "@react-navigation/elements": "^2.6.4",
- "@react-navigation/native": "^7.1.8",
- "@reduxjs/toolkit": "^2.9.0",
- "@sentry/react-native": "~7.2.0",
- "@types/lodash": "^4.17.20",
- "dayjs": "^1.11.18",
- "expo": "54.0.21",
+ "@react-navigation/bottom-tabs": "^7.8.6",
+ "@react-navigation/elements": "^2.8.3",
+ "@react-navigation/native": "^7.1.21",
+ "@reduxjs/toolkit": "^2.11.0",
+ "@sentry/react-native": "~7.7.0",
+ "@types/lodash": "^4.17.21",
+ "dayjs": "^1.11.19",
+ "expo": "54.0.25",
"expo-apple-authentication": "~8.0.7",
"expo-background-task": "~1.0.8",
"expo-blur": "~15.0.7",
- "expo-camera": "~17.0.8",
- "expo-constants": "~18.0.9",
+ "expo-camera": "~17.0.9",
+ "expo-clipboard": "~8.0.7",
+ "expo-constants": "~18.0.10",
"expo-font": "~14.0.9",
- "expo-glass-effect": "~0.1.5",
+ "expo-glass-effect": "~0.1.7",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.10",
"expo-image-picker": "~17.0.8",
@@ -40,8 +41,8 @@
"expo-media-library": "^18.2.0",
"expo-notifications": "~0.32.12",
"expo-quick-actions": "^6.0.0",
- "expo-router": "~6.0.14",
- "expo-splash-screen": "~31.0.10",
+ "expo-router": "~6.0.15",
+ "expo-splash-screen": "~31.0.11",
"expo-sqlite": "^16.0.8",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
@@ -168,7 +169,7 @@
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"license": "MIT",
"dependencies": {
@@ -195,17 +196,17 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
- "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
+ "version": "7.28.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz",
+ "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
- "@babel/traverse": "^7.28.3",
+ "@babel/traverse": "^7.28.5",
"semver": "^6.3.1"
},
"engines": {
@@ -216,13 +217,13 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
- "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==",
+ "version": "7.28.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz",
+ "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.1",
- "regexpu-core": "^6.2.0",
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "regexpu-core": "^6.3.1",
"semver": "^6.3.1"
},
"engines": {
@@ -258,13 +259,13 @@
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
- "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
+ "version": "7.28.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
+ "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -302,7 +303,7 @@
},
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
"license": "MIT",
"dependencies": {
@@ -340,7 +341,7 @@
},
"node_modules/@babel/helper-replace-supers": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
"integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
"license": "MIT",
"dependencies": {
@@ -357,7 +358,7 @@
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
"license": "MIT",
"dependencies": {
@@ -527,6 +528,7 @@
"version": "7.28.0",
"resolved": "https://mirrors.tencent.com/npm/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz",
"integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==",
+ "license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
@@ -703,7 +705,7 @@
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
"integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
"license": "MIT",
"dependencies": {
@@ -820,7 +822,7 @@
},
"node_modules/@babel/plugin-syntax-typescript": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
"integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
"license": "MIT",
"dependencies": {
@@ -835,7 +837,7 @@
},
"node_modules/@babel/plugin-transform-arrow-functions": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
"integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
"license": "MIT",
"dependencies": {
@@ -899,7 +901,7 @@
},
"node_modules/@babel/plugin-transform-class-properties": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
"integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
"license": "MIT",
"dependencies": {
@@ -930,9 +932,9 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz",
- "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==",
+ "version": "7.28.4",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
+ "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
@@ -940,7 +942,7 @@
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
- "@babel/traverse": "^7.28.0"
+ "@babel/traverse": "^7.28.4"
},
"engines": {
"node": ">=6.9.0"
@@ -1077,7 +1079,7 @@
},
"node_modules/@babel/plugin-transform-modules-commonjs": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
"integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
"license": "MIT",
"dependencies": {
@@ -1109,7 +1111,7 @@
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
"integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
"license": "MIT",
"dependencies": {
@@ -1172,9 +1174,9 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
- "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
+ "version": "7.28.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
+ "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
@@ -1367,7 +1369,7 @@
},
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
"integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
"license": "MIT",
"dependencies": {
@@ -1428,13 +1430,13 @@
}
},
"node_modules/@babel/plugin-transform-typescript": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz",
- "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==",
+ "version": "7.28.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz",
+ "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-create-class-features-plugin": "^7.28.5",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/plugin-syntax-typescript": "^7.27.1"
@@ -1448,7 +1450,7 @@
},
"node_modules/@babel/plugin-transform-unicode-regex": {
"version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
"integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
"license": "MIT",
"dependencies": {
@@ -1483,16 +1485,16 @@
}
},
"node_modules/@babel/preset-typescript": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
- "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
+ "version": "7.28.5",
+ "resolved": "https://mirrors.tencent.com/npm/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
+ "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
- "@babel/plugin-transform-typescript": "^7.27.1"
+ "@babel/plugin-transform-typescript": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1876,9 +1878,9 @@
}
},
"node_modules/@expo/fingerprint": {
- "version": "0.15.2",
- "resolved": "https://mirrors.tencent.com/npm/@expo/fingerprint/-/fingerprint-0.15.2.tgz",
- "integrity": "sha512-mA3weHEOd9B3mbDLNDKmAcFWo3kqsAJqPne7uMJndheKXPbRw15bV+ajAGBYZh2SS37xixLJ5eDpuc+Wr6jJtw==",
+ "version": "0.15.3",
+ "resolved": "https://mirrors.tencent.com/npm/@expo/fingerprint/-/fingerprint-0.15.3.tgz",
+ "integrity": "sha512-8YPJpEYlmV171fi+t+cSLMX1nC5ngY9j2FiN70dHldLpd6Ct6ouGhk96svJ4BQZwsqwII2pokwzrDAwqo4Z0FQ==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
@@ -1924,6 +1926,7 @@
"version": "7.7.3",
"resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -1981,9 +1984,9 @@
}
},
"node_modules/@expo/mcp-tunnel": {
- "version": "0.0.8",
- "resolved": "https://mirrors.tencent.com/npm/@expo/mcp-tunnel/-/mcp-tunnel-0.0.8.tgz",
- "integrity": "sha512-6261obzt6h9TQb6clET7Fw4Ig4AY2hfTNKI3gBt0gcTNxZipwMg8wER7ssDYieA9feD/FfPTuCPYFcR280aaWA==",
+ "version": "0.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/@expo/mcp-tunnel/-/mcp-tunnel-0.1.0.tgz",
+ "integrity": "sha512-rJ6hl0GnIZj9+ssaJvFsC7fwyrmndcGz+RGFzu+0gnlm78X01957yjtHgjcmnQAgL5hWEOR6pkT0ijY5nU5AWw==",
"license": "MIT",
"dependencies": {
"ws": "^8.18.3",
@@ -2041,9 +2044,9 @@
}
},
"node_modules/@expo/metro-config": {
- "version": "54.0.8",
- "resolved": "https://mirrors.tencent.com/npm/@expo/metro-config/-/metro-config-54.0.8.tgz",
- "integrity": "sha512-rCkDQ8IT6sgcGNy48O2cTE4NlazCAgAIsD5qBsNPJLZSS0XbaILvAgGsFt/4nrx0GMGj6iQcOn5ifwV4NssTmw==",
+ "version": "54.0.9",
+ "resolved": "https://mirrors.tencent.com/npm/@expo/metro-config/-/metro-config-54.0.9.tgz",
+ "integrity": "sha512-CRI4WgFXrQ2Owyr8q0liEBJveUIF9DcYAKadMRsJV7NxGNBdrIIKzKvqreDfsGiRqivbLsw6UoNb3UE7/SvPfg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
@@ -2576,6 +2579,7 @@
"version": "7.10.4",
"resolved": "https://mirrors.tencent.com/npm/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "license": "MIT",
"dependencies": {
"@babel/highlight": "^7.10.4"
}
@@ -3333,9 +3337,9 @@
}
},
"node_modules/@react-native-community/datetimepicker": {
- "version": "8.4.4",
- "resolved": "https://mirrors.tencent.com/npm/@react-native-community/datetimepicker/-/datetimepicker-8.4.4.tgz",
- "integrity": "sha512-bc4ZixEHxZC9/qf5gbdYvIJiLZ5CLmEsC3j+Yhe1D1KC/3QhaIfGDVdUcid0PdlSoGOSEq4VlB93AWyetEyBSQ==",
+ "version": "8.5.1",
+ "resolved": "https://mirrors.tencent.com/npm/@react-native-community/datetimepicker/-/datetimepicker-8.5.1.tgz",
+ "integrity": "sha512-TuwM1ORbxCjOp1GOtONj0QnpDpVfq0F4UlfKZYPxL/vmriaLHt2Kgvw63Bv0Bpep4eOkslVVSS1IRfRI6d392g==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
@@ -3366,9 +3370,9 @@
}
},
"node_modules/@react-native-picker/picker": {
- "version": "2.11.1",
- "resolved": "https://mirrors.tencent.com/npm/@react-native-picker/picker/-/picker-2.11.1.tgz",
- "integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==",
+ "version": "2.11.4",
+ "resolved": "https://mirrors.tencent.com/npm/@react-native-picker/picker/-/picker-2.11.4.tgz",
+ "integrity": "sha512-Kf8h1AMnBo54b1fdiVylP2P/iFcZqzpMYcglC28EEFB1DEnOjsNr6Ucqc+3R9e91vHxEDnhZFbYDmAe79P2gjA==",
"license": "MIT",
"workspaces": [
"example"
@@ -3787,16 +3791,17 @@
}
},
"node_modules/@react-navigation/bottom-tabs": {
- "version": "7.4.8",
- "resolved": "https://mirrors.tencent.com/npm/@react-navigation/bottom-tabs/-/bottom-tabs-7.4.8.tgz",
- "integrity": "sha512-W85T9f5sPA2zNnkxBO0PF0Jg9CRAMYqD9hY20dAhuVM5I+qiCqhW7qLveK59mlbtdXuGmieit6FK3inKmXzL7A==",
+ "version": "7.8.6",
+ "resolved": "https://mirrors.tencent.com/npm/@react-navigation/bottom-tabs/-/bottom-tabs-7.8.6.tgz",
+ "integrity": "sha512-0wGtU+I1rCUjvAqKtzD2dwQaTICFf5J233vkg20cLrx8LNQPAgSsbnsDSM6S315OOoVLCIL1dcrNv7ExLBlWfw==",
"license": "MIT",
"dependencies": {
- "@react-navigation/elements": "^2.6.5",
- "color": "^4.2.3"
+ "@react-navigation/elements": "^2.8.3",
+ "color": "^4.2.3",
+ "sf-symbols-typescript": "^2.1.0"
},
"peerDependencies": {
- "@react-navigation/native": "^7.1.18",
+ "@react-navigation/native": "^7.1.21",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
@@ -3804,13 +3809,14 @@
}
},
"node_modules/@react-navigation/core": {
- "version": "7.12.4",
- "resolved": "https://mirrors.tencent.com/npm/@react-navigation/core/-/core-7.12.4.tgz",
- "integrity": "sha512-xLFho76FA7v500XID5z/8YfGTvjQPw7/fXsq4BIrVSqetNe/o/v+KAocEw4ots6kyv3XvSTyiWKh2g3pN6xZ9Q==",
+ "version": "7.13.2",
+ "resolved": "https://mirrors.tencent.com/npm/@react-navigation/core/-/core-7.13.2.tgz",
+ "integrity": "sha512-A0pFeZlKp+FJob2lVr7otDt3M4rsSJrnAfXWoWR9JVeFtfEXsH/C0s7xtpDCMRUO58kzSBoTF1GYzoMC5DLD4g==",
"license": "MIT",
"dependencies": {
- "@react-navigation/routers": "^7.5.1",
+ "@react-navigation/routers": "^7.5.2",
"escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"query-string": "^7.1.3",
"react-is": "^19.1.0",
@@ -3822,9 +3828,9 @@
}
},
"node_modules/@react-navigation/elements": {
- "version": "2.6.5",
- "resolved": "https://mirrors.tencent.com/npm/@react-navigation/elements/-/elements-2.6.5.tgz",
- "integrity": "sha512-HOaekvFeoqKyaSKP2hakL7OUnw0jIhk/1wMjcovUKblT76LMTumZpriqsc30m/Vnyy1a8zgp4VsuA1xftcalgQ==",
+ "version": "2.8.3",
+ "resolved": "https://mirrors.tencent.com/npm/@react-navigation/elements/-/elements-2.8.3.tgz",
+ "integrity": "sha512-0c5nSDPP3bUFujgkSVqqMShaAup3XIxNe1KTK9LSmwKgWEneyo6OPIjIdiEwPlZvJZKi7ag5hDjacQLGwO0LGA==",
"license": "MIT",
"dependencies": {
"color": "^4.2.3",
@@ -3833,7 +3839,7 @@
},
"peerDependencies": {
"@react-native-masked-view/masked-view": ">= 0.2.0",
- "@react-navigation/native": "^7.1.18",
+ "@react-navigation/native": "^7.1.21",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0"
@@ -3845,12 +3851,12 @@
}
},
"node_modules/@react-navigation/native": {
- "version": "7.1.18",
- "resolved": "https://mirrors.tencent.com/npm/@react-navigation/native/-/native-7.1.18.tgz",
- "integrity": "sha512-DZgd6860dxcq3YX7UzIXeBr6m3UgXvo9acxp5jiJyIZXdR00Br9JwVkO7e0bUeTA2d3Z8dsmtAR84Y86NnH64Q==",
+ "version": "7.1.21",
+ "resolved": "https://mirrors.tencent.com/npm/@react-navigation/native/-/native-7.1.21.tgz",
+ "integrity": "sha512-mhpAewdivBL01ibErr91FUW9bvKhfAF6Xv/yr6UOJtDhv0jU6iUASUcA3i3T8VJCOB/vxmoke7VDp8M+wBFs/Q==",
"license": "MIT",
"dependencies": {
- "@react-navigation/core": "^7.12.4",
+ "@react-navigation/core": "^7.13.2",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
@@ -3862,16 +3868,18 @@
}
},
"node_modules/@react-navigation/native-stack": {
- "version": "7.3.27",
- "resolved": "https://mirrors.tencent.com/npm/@react-navigation/native-stack/-/native-stack-7.3.27.tgz",
- "integrity": "sha512-bbbud0pT63tGh706hQD/A3Z9gF1O2HtQ0dJqaiYzHzPy9wSOi82i721530tJkmccevAemUrZbEeEC5mxVo1DzQ==",
+ "version": "7.8.0",
+ "resolved": "https://mirrors.tencent.com/npm/@react-navigation/native-stack/-/native-stack-7.8.0.tgz",
+ "integrity": "sha512-iRqQY+IYB610BJY/335/kdNDhXQ8L9nPUlIT+DSk88FA86+C+4/vek8wcKw8IrfwdorT4m+6TY0v7Qnrt+WLKQ==",
"license": "MIT",
"dependencies": {
- "@react-navigation/elements": "^2.6.5",
+ "@react-navigation/elements": "^2.8.3",
+ "color": "^4.2.3",
+ "sf-symbols-typescript": "^2.1.0",
"warn-once": "^0.1.1"
},
"peerDependencies": {
- "@react-navigation/native": "^7.1.18",
+ "@react-navigation/native": "^7.1.21",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
@@ -3879,23 +3887,23 @@
}
},
"node_modules/@react-navigation/routers": {
- "version": "7.5.1",
- "resolved": "https://mirrors.tencent.com/npm/@react-navigation/routers/-/routers-7.5.1.tgz",
- "integrity": "sha512-pxipMW/iEBSUrjxz2cDD7fNwkqR4xoi0E/PcfTQGCcdJwLoaxzab5kSadBLj1MTJyT0YRrOXL9umHpXtp+Dv4w==",
+ "version": "7.5.2",
+ "resolved": "https://mirrors.tencent.com/npm/@react-navigation/routers/-/routers-7.5.2.tgz",
+ "integrity": "sha512-kymreY5aeTz843E+iPAukrsOtc7nabAH6novtAPREmmGu77dQpfxPB2ZWpKb5nRErIRowp1kYRoN2Ckl+S6JYw==",
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11"
}
},
"node_modules/@reduxjs/toolkit": {
- "version": "2.9.0",
- "resolved": "https://mirrors.tencent.com/npm/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
- "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
+ "version": "2.11.0",
+ "resolved": "https://mirrors.tencent.com/npm/@reduxjs/toolkit/-/toolkit-2.11.0.tgz",
+ "integrity": "sha512-hBjYg0aaRL1O2Z0IqWhnTLytnjDIxekmRxm1snsHjHaKVmIF1HiImWqsq+PuEbn6zdMlkIj9WofK1vR8jjx+Xw==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
- "immer": "^10.0.3",
+ "immer": "^11.0.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
@@ -3914,24 +3922,24 @@
}
},
"node_modules/@revenuecat/purchases-js": {
- "version": "1.15.0",
- "resolved": "https://mirrors.tencent.com/npm/@revenuecat/purchases-js/-/purchases-js-1.15.0.tgz",
- "integrity": "sha512-bBzDwTvrwkpW9vFmK7u5tWf2aT1PAHbK7KMr9cXkMpaZy8ct39IP/4hIQLWPAEQBlTc7GvoRhgnDpwLMwJTDpg==",
+ "version": "1.18.3",
+ "resolved": "https://mirrors.tencent.com/npm/@revenuecat/purchases-js/-/purchases-js-1.18.3.tgz",
+ "integrity": "sha512-n43M5aRqC3dnWizJPx930cJSZ9M3UxiTiFri/4msH4wmqIb30nRspDdoEt79cGybUxcSt85Ma/Ot7AYmn/IClg==",
"license": "MIT"
},
"node_modules/@revenuecat/purchases-js-hybrid-mappings": {
- "version": "17.10.0",
- "resolved": "https://mirrors.tencent.com/npm/@revenuecat/purchases-js-hybrid-mappings/-/purchases-js-hybrid-mappings-17.10.0.tgz",
- "integrity": "sha512-oSofBs7oGxX2N9/CyWpAcvlXvUe8VdS5J2J4SWHWCeCohgkOI4xsAPrbisXYPiNaILLFt/slD1mqL3sawzLBug==",
+ "version": "17.19.1",
+ "resolved": "https://mirrors.tencent.com/npm/@revenuecat/purchases-js-hybrid-mappings/-/purchases-js-hybrid-mappings-17.19.1.tgz",
+ "integrity": "sha512-fwJvk/Tyw2t9P58AkJNDeVo41Ihh+l61ZhunWaAqSEIlJysGojWJOQrCvOAIXqGDwUObbntZ4BRNJ3VjOFuRrQ==",
"license": "MIT",
"dependencies": {
- "@revenuecat/purchases-js": "1.15.0"
+ "@revenuecat/purchases-js": "1.18.3"
}
},
"node_modules/@revenuecat/purchases-typescript-internal": {
- "version": "17.10.0",
- "resolved": "https://mirrors.tencent.com/npm/@revenuecat/purchases-typescript-internal/-/purchases-typescript-internal-17.10.0.tgz",
- "integrity": "sha512-ILcAS3ixsij/06sy5ZtA62R3oscYVB+0fGCMmfgta3zrY5gM5dBto5YzSerSL57icSJhz2RbHYlGsUknQzJ1pQ==",
+ "version": "17.19.1",
+ "resolved": "https://mirrors.tencent.com/npm/@revenuecat/purchases-typescript-internal/-/purchases-typescript-internal-17.19.1.tgz",
+ "integrity": "sha512-OGhDqdUQTGc8Vfhv0DERo7E+WdaiiNr157gFYnoeaTjh/eCBD3GoDzRP8HR5QBnrBWhVgT0KpRAZnzEnBwhjDg==",
"license": "MIT"
},
"node_modules/@rtsao/scc": {
@@ -3942,84 +3950,84 @@
"license": "MIT"
},
"node_modules/@sentry-internal/browser-utils": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/browser-utils/-/browser-utils-10.12.0.tgz",
- "integrity": "sha512-dozbx389jhKynj0d657FsgbBVOar7pX3mb6GjqCxslXF0VKpZH2Xks0U32RgDY/nK27O+o095IWz7YvjVmPkDw==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/browser-utils/-/browser-utils-10.26.0.tgz",
+ "integrity": "sha512-rPg1+JZlfp912pZONQAWZzbSaZ9L6R2VrMcCEa+2e2Gqk9um4b+LqF5RQWZsbt5Z0n0azSy/KQ6zAe/zTPXSOg==",
"license": "MIT",
"dependencies": {
- "@sentry/core": "10.12.0"
+ "@sentry/core": "10.26.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/feedback/-/feedback-10.12.0.tgz",
- "integrity": "sha512-0+7ceO6yQPPqfxRc9ue/xoPHKcnB917ezPaehGQNfAFNQB9PNTG1y55+8mRu0Fw+ANbZeCt/HyoCmXuRdxmkpg==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/feedback/-/feedback-10.26.0.tgz",
+ "integrity": "sha512-0vk9eQP0CXD7Y2WkcCIWHaAqnXOAi18/GupgWLnbB2kuQVYVtStWxtW+OWRe8W/XwSnZ5m6JBTVeokuk/O16DQ==",
"license": "MIT",
"dependencies": {
- "@sentry/core": "10.12.0"
+ "@sentry/core": "10.26.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/replay/-/replay-10.12.0.tgz",
- "integrity": "sha512-/1093gSNGN5KlOBsuyAl33JkzGiG38kCnxswQLZWpPpR6LBbR1Ddb18HjhDpoQNNEZybJBgJC3a5NKl43C2TSQ==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/replay/-/replay-10.26.0.tgz",
+ "integrity": "sha512-FMySQnY2/p0dVtFUBgUO+aMdK2ovqnd7Q/AkvMQUsN/5ulyj6KZx3JX3CqOqRtAr1izoCe4Kh2pi5t//sQmvsg==",
"license": "MIT",
"dependencies": {
- "@sentry-internal/browser-utils": "10.12.0",
- "@sentry/core": "10.12.0"
+ "@sentry-internal/browser-utils": "10.26.0",
+ "@sentry/core": "10.26.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/replay-canvas/-/replay-canvas-10.12.0.tgz",
- "integrity": "sha512-W/z1/+69i3INNfPjD1KuinSNaRQaApjzwb37IFmiyF440F93hxmEYgXHk3poOlYYaigl2JMYbysGPWOiXnqUXA==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry-internal/replay-canvas/-/replay-canvas-10.26.0.tgz",
+ "integrity": "sha512-vs7d/P+8M1L1JVAhhJx2wo15QDhqAipnEQvuRZ6PV7LUcS1un9/Vx49FMxpIkx6JcKADJVwtXrS1sX2hoNT/kw==",
"license": "MIT",
"dependencies": {
- "@sentry-internal/replay": "10.12.0",
- "@sentry/core": "10.12.0"
+ "@sentry-internal/replay": "10.26.0",
+ "@sentry/core": "10.26.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/babel-plugin-component-annotate": {
- "version": "4.3.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.3.0.tgz",
- "integrity": "sha512-OuxqBprXRyhe8Pkfyz/4yHQJc5c3lm+TmYWSSx8u48g5yKewSQDOxkiLU5pAk3WnbLPy8XwU/PN+2BG0YFU9Nw==",
+ "version": "4.6.1",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.1.tgz",
+ "integrity": "sha512-aSIk0vgBqv7PhX6/Eov+vlI4puCE0bRXzUG5HdCsHBpAfeMkI8Hva6kSOusnzKqs8bf04hU7s3Sf0XxGTj/1AA==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/@sentry/browser": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/browser/-/browser-10.12.0.tgz",
- "integrity": "sha512-lKyaB2NFmr7SxPjmMTLLhQ7xfxaY3kdkMhpzuRI5qwOngtKt4+FtvNYHRuz+PTtEFv4OaHhNNbRn6r91gWguQg==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/browser/-/browser-10.26.0.tgz",
+ "integrity": "sha512-uvV4hnkt8bh8yP0disJ0fszy8FdnkyGtzyIVKdeQZbNUefwbDhd3H0KJrAHhJ5ocULMH3B+dipdPmw2QXbEflg==",
"license": "MIT",
"dependencies": {
- "@sentry-internal/browser-utils": "10.12.0",
- "@sentry-internal/feedback": "10.12.0",
- "@sentry-internal/replay": "10.12.0",
- "@sentry-internal/replay-canvas": "10.12.0",
- "@sentry/core": "10.12.0"
+ "@sentry-internal/browser-utils": "10.26.0",
+ "@sentry-internal/feedback": "10.26.0",
+ "@sentry-internal/replay": "10.26.0",
+ "@sentry-internal/replay-canvas": "10.26.0",
+ "@sentry/core": "10.26.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/cli": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli/-/cli-2.55.0.tgz",
- "integrity": "sha512-cynvcIM2xL8ddwELyFRSpZQw4UtFZzoM2rId2l9vg7+wDREPDocMJB9lEQpBIo3eqhp9JswqUT037yjO6iJ5Sw==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli/-/cli-2.58.2.tgz",
+ "integrity": "sha512-U4u62V4vaTWF+o40Mih8aOpQKqKUbZQt9A3LorIJwaE3tO3XFLRI70eWtW2se1Qmy0RZ74zB14nYcFNFl2t4Rw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -4036,20 +4044,20 @@
"node": ">= 10"
},
"optionalDependencies": {
- "@sentry/cli-darwin": "2.55.0",
- "@sentry/cli-linux-arm": "2.55.0",
- "@sentry/cli-linux-arm64": "2.55.0",
- "@sentry/cli-linux-i686": "2.55.0",
- "@sentry/cli-linux-x64": "2.55.0",
- "@sentry/cli-win32-arm64": "2.55.0",
- "@sentry/cli-win32-i686": "2.55.0",
- "@sentry/cli-win32-x64": "2.55.0"
+ "@sentry/cli-darwin": "2.58.2",
+ "@sentry/cli-linux-arm": "2.58.2",
+ "@sentry/cli-linux-arm64": "2.58.2",
+ "@sentry/cli-linux-i686": "2.58.2",
+ "@sentry/cli-linux-x64": "2.58.2",
+ "@sentry/cli-win32-arm64": "2.58.2",
+ "@sentry/cli-win32-i686": "2.58.2",
+ "@sentry/cli-win32-x64": "2.58.2"
}
},
"node_modules/@sentry/cli-darwin": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-darwin/-/cli-darwin-2.55.0.tgz",
- "integrity": "sha512-jGHE7SHHzqXUmnsmRLgorVH6nmMmTjQQXdPZbSL5tRtH8d3OIYrVNr5D72DSgD26XAPBDMV0ibqOQ9NKoiSpfA==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-darwin/-/cli-darwin-2.58.2.tgz",
+ "integrity": "sha512-MArsb3zLhA2/cbd4rTm09SmTpnEuZCoZOpuZYkrpDw1qzBVJmRFA1W1hGAQ9puzBIk/ubY3EUhhzuU3zN2uD6w==",
"license": "BSD-3-Clause",
"optional": true,
"os": [
@@ -4060,9 +4068,9 @@
}
},
"node_modules/@sentry/cli-linux-arm": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-arm/-/cli-linux-arm-2.55.0.tgz",
- "integrity": "sha512-ATjU0PsiWADSPLF/kZroLZ7FPKd5W9TDWHVkKNwIUNTei702LFgTjNeRwOIzTgSvG3yTmVEqtwFQfFN/7hnVXQ==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.2.tgz",
+ "integrity": "sha512-HU9lTCzcHqCz/7Mt5n+cv+nFuJdc1hGD2h35Uo92GgxX3/IujNvOUfF+nMX9j6BXH6hUt73R5c0Ycq9+a3Parg==",
"cpu": [
"arm"
],
@@ -4078,9 +4086,9 @@
}
},
"node_modules/@sentry/cli-linux-arm64": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.55.0.tgz",
- "integrity": "sha512-jNB/0/gFcOuDCaY/TqeuEpsy/k52dwyk1SOV3s1ku4DUsln6govTppeAGRewY3T1Rj9B2vgIWTrnB8KVh9+Rgg==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.2.tgz",
+ "integrity": "sha512-ay3OeObnbbPrt45cjeUyQjsx5ain1laj1tRszWj37NkKu55NZSp4QCg1gGBZ0gBGhckI9nInEsmKtix00alw2g==",
"cpu": [
"arm64"
],
@@ -4096,9 +4104,9 @@
}
},
"node_modules/@sentry/cli-linux-i686": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-i686/-/cli-linux-i686-2.55.0.tgz",
- "integrity": "sha512-8LZjo6PncTM6bWdaggscNOi5r7F/fqRREsCwvd51dcjGj7Kp1plqo9feEzYQ+jq+KUzVCiWfHrUjddFmYyZJrg==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.2.tgz",
+ "integrity": "sha512-CN9p0nfDFsAT1tTGBbzOUGkIllwS3hygOUyTK7LIm9z+UHw5uNgNVqdM/3Vg+02ymjkjISNB3/+mqEM5osGXdA==",
"cpu": [
"x86",
"ia32"
@@ -4115,9 +4123,9 @@
}
},
"node_modules/@sentry/cli-linux-x64": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-x64/-/cli-linux-x64-2.55.0.tgz",
- "integrity": "sha512-5LUVvq74Yj2cZZy5g5o/54dcWEaX4rf3myTHy73AKhRj1PABtOkfexOLbF9xSrZy95WXWaXyeH+k5n5z/vtHfA==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.2.tgz",
+ "integrity": "sha512-oX/LLfvWaJO50oBVOn4ZvG2SDWPq0MN8SV9eg5tt2nviq+Ryltfr7Rtoo+HfV+eyOlx1/ZXhq9Wm7OT3cQuz+A==",
"cpu": [
"x64"
],
@@ -4133,9 +4141,9 @@
}
},
"node_modules/@sentry/cli-win32-arm64": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.55.0.tgz",
- "integrity": "sha512-cWIQdzm1pfLwPARsV6dUb8TVd6Y3V1A2VWxjTons3Ift6GvtVmiAe0OWL8t2Yt95i8v61kTD/6Tq21OAaogqzA==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.2.tgz",
+ "integrity": "sha512-+cl3x2HPVMpoSVGVM1IDWlAEREZrrVQj4xBb0TRKII7g3hUxRsAIcsrr7+tSkie++0FuH4go/b5fGAv51OEF3w==",
"cpu": [
"arm64"
],
@@ -4149,9 +4157,9 @@
}
},
"node_modules/@sentry/cli-win32-i686": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-i686/-/cli-win32-i686-2.55.0.tgz",
- "integrity": "sha512-ldepCn2t9r4I0wvgk7NRaA7coJyy4rTQAzM66u9j5nTEsUldf66xym6esd5ZZRAaJUjffqvHqUIr/lrieTIrVg==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.2.tgz",
+ "integrity": "sha512-omFVr0FhzJ8oTJSg1Kf+gjLgzpYklY0XPfLxZ5iiMiYUKwF5uo1RJRdkUOiEAv0IqpUKnmKcmVCLaDxsWclB7Q==",
"cpu": [
"x86",
"ia32"
@@ -4166,9 +4174,9 @@
}
},
"node_modules/@sentry/cli-win32-x64": {
- "version": "2.55.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-x64/-/cli-win32-x64-2.55.0.tgz",
- "integrity": "sha512-4hPc/I/9tXx+HLTdTGwlagtAfDSIa2AoTUP30tl32NAYQhx9a6niUbPAemK2qfxesiufJ7D2djX83rCw6WnJVA==",
+ "version": "2.58.2",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.2.tgz",
+ "integrity": "sha512-2NAFs9UxVbRztQbgJSP5i8TB9eJQ7xraciwj/93djrSMHSEbJ0vC47TME0iifgvhlHMs5vqETOKJtfbbpQAQFA==",
"cpu": [
"x64"
],
@@ -4207,22 +4215,22 @@
}
},
"node_modules/@sentry/core": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/core/-/core-10.12.0.tgz",
- "integrity": "sha512-Jrf0Yo7DvmI/ZQcvBnA0xKNAFkJlVC/fMlvcin+5IrFNRcqOToZ2vtF+XqTgjRZymXQNE8s1QTD7IomPHk0TAw==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/core/-/core-10.26.0.tgz",
+ "integrity": "sha512-TjDe5QI37SLuV0q3nMOH8JcPZhv2e85FALaQMIhRILH9Ce6G7xW5GSjmH91NUVq8yc3XtiqYlz/EenEZActc4Q==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/react": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/react/-/react-10.12.0.tgz",
- "integrity": "sha512-TpqgdoYbkf5JynmmW2oQhHQ/h5w+XPYk0cEb/UrsGlvJvnBSR+5tgh0AqxCSi3gvtp82rAXI5w1TyRPBbhLDBw==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/react/-/react-10.26.0.tgz",
+ "integrity": "sha512-Qi0/FVXAalwQNr8zp0tocViH3+MRelW8ePqj3TdMzapkbXRuh07czdGgw8Zgobqcb7l4rRCRAUo2sl/H3KVkIw==",
"license": "MIT",
"dependencies": {
- "@sentry/browser": "10.12.0",
- "@sentry/core": "10.12.0",
+ "@sentry/browser": "10.26.0",
+ "@sentry/core": "10.26.0",
"hoist-non-react-statics": "^3.3.2"
},
"engines": {
@@ -4233,17 +4241,17 @@
}
},
"node_modules/@sentry/react-native": {
- "version": "7.2.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/react-native/-/react-native-7.2.0.tgz",
- "integrity": "sha512-rjqYgEjntPz1sPysud78wi4B9ui7LBVPsG6qr8s/htLMYho9GPGFA5dF+eqsQWqMX8NDReAxNkLTC4+gCNklLQ==",
+ "version": "7.7.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/react-native/-/react-native-7.7.0.tgz",
+ "integrity": "sha512-D+gqiw88mOnouY+Pd8A3wcUDOilPOIcypBPw7WL9v+K1jM12Snf6sosEG4xgFFMXoK+GSsYAeC5MR0skD/b+Zg==",
"license": "MIT",
"dependencies": {
- "@sentry/babel-plugin-component-annotate": "4.3.0",
- "@sentry/browser": "10.12.0",
- "@sentry/cli": "2.55.0",
- "@sentry/core": "10.12.0",
- "@sentry/react": "10.12.0",
- "@sentry/types": "10.12.0"
+ "@sentry/babel-plugin-component-annotate": "4.6.1",
+ "@sentry/browser": "10.26.0",
+ "@sentry/cli": "2.58.2",
+ "@sentry/core": "10.26.0",
+ "@sentry/react": "10.26.0",
+ "@sentry/types": "10.26.0"
},
"bin": {
"sentry-expo-upload-sourcemaps": "scripts/expo-upload-sourcemaps.js"
@@ -4260,12 +4268,12 @@
}
},
"node_modules/@sentry/types": {
- "version": "10.12.0",
- "resolved": "https://mirrors.tencent.com/npm/@sentry/types/-/types-10.12.0.tgz",
- "integrity": "sha512-sKGj3l3V8ZKISh2Tu88bHfnm5ztkRtSLdmpZ6TmCeJdSM9pV+RRd6CMJ0RnSEXmYHselPNUod521t2NQFd4W1w==",
+ "version": "10.26.0",
+ "resolved": "https://mirrors.tencent.com/npm/@sentry/types/-/types-10.26.0.tgz",
+ "integrity": "sha512-mDpG7lnOJppbk9iKrnuvkuiCTbh3aBAlUK4NZxZNLOSI0SeefYXHRAcri89BqWZ/MT98sQLU+Hf+rlwrwq38/A==",
"license": "MIT",
"dependencies": {
- "@sentry/core": "10.12.0"
+ "@sentry/core": "10.26.0"
},
"engines": {
"node": ">=18"
@@ -4408,9 +4416,9 @@
"license": "MIT"
},
"node_modules/@types/lodash": {
- "version": "4.17.20",
- "resolved": "https://mirrors.tencent.com/npm/@types/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+ "version": "4.17.21",
+ "resolved": "https://mirrors.tencent.com/npm/@types/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==",
"license": "MIT"
},
"node_modules/@types/node": {
@@ -5404,9 +5412,9 @@
}
},
"node_modules/babel-preset-expo": {
- "version": "54.0.6",
- "resolved": "https://mirrors.tencent.com/npm/babel-preset-expo/-/babel-preset-expo-54.0.6.tgz",
- "integrity": "sha512-GxJfwnuOPQJbzDe5WASJZdNQiukLw7i9z+Lh6JQWkUHXsShHyQrqgiKE55MD/KaP9VqJ70yZm7bYqOu8zwcWqQ==",
+ "version": "54.0.7",
+ "resolved": "https://mirrors.tencent.com/npm/babel-preset-expo/-/babel-preset-expo-54.0.7.tgz",
+ "integrity": "sha512-JENWk0bvxW4I1ftveO8GRtX2t2TH6N4Z0TPvIHxroZ/4SswUfyNsUNbbP7Fm4erj3ar/JHGri5kTZ+s3xdjHZw==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.25.9",
@@ -5504,9 +5512,10 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.16",
- "resolved": "https://mirrors.tencent.com/npm/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
- "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==",
+ "version": "2.8.31",
+ "resolved": "https://mirrors.tencent.com/npm/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
+ "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
+ "license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
@@ -5527,6 +5536,7 @@
"version": "8.4.2",
"resolved": "https://mirrors.tencent.com/npm/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
@@ -5597,9 +5607,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.26.3",
- "resolved": "https://mirrors.tencent.com/npm/browserslist/-/browserslist-4.26.3.tgz",
- "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "version": "4.28.0",
+ "resolved": "https://mirrors.tencent.com/npm/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
"funding": [
{
"type": "opencollective",
@@ -5614,13 +5624,12 @@
"url": "https://github.com/sponsors/ai"
}
],
- "license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.8.9",
- "caniuse-lite": "^1.0.30001746",
- "electron-to-chromium": "^1.5.227",
- "node-releases": "^2.0.21",
- "update-browserslist-db": "^1.1.3"
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
},
"bin": {
"browserslist": "cli.js"
@@ -5752,9 +5761,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001750",
- "resolved": "https://mirrors.tencent.com/npm/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
- "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
+ "version": "1.0.30001757",
+ "resolved": "https://mirrors.tencent.com/npm/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"funding": [
{
"type": "opencollective",
@@ -5943,6 +5952,7 @@
"version": "1.0.4",
"resolved": "https://mirrors.tencent.com/npm/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "license": "MIT",
"engines": {
"node": ">=0.8"
}
@@ -5992,6 +6002,7 @@
"version": "7.2.0",
"resolved": "https://mirrors.tencent.com/npm/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
"engines": {
"node": ">= 10"
}
@@ -6030,6 +6041,7 @@
"version": "2.6.9",
"resolved": "https://mirrors.tencent.com/npm/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
@@ -6037,14 +6049,12 @@
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://mirrors.tencent.com/npm/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://mirrors.tencent.com/npm/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -6092,12 +6102,12 @@
"license": "MIT"
},
"node_modules/core-js-compat": {
- "version": "3.46.0",
- "resolved": "https://mirrors.tencent.com/npm/core-js-compat/-/core-js-compat-3.46.0.tgz",
- "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==",
+ "version": "3.47.0",
+ "resolved": "https://mirrors.tencent.com/npm/core-js-compat/-/core-js-compat-3.47.0.tgz",
+ "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==",
"license": "MIT",
"dependencies": {
- "browserslist": "^4.26.3"
+ "browserslist": "^4.28.0"
},
"funding": {
"type": "opencollective",
@@ -6283,10 +6293,9 @@
}
},
"node_modules/dayjs": {
- "version": "1.11.18",
- "resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.18.tgz",
- "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
- "license": "MIT"
+ "version": "1.11.19",
+ "resolved": "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="
},
"node_modules/debug": {
"version": "4.4.1",
@@ -6307,7 +6316,7 @@
},
"node_modules/decode-uri-component": {
"version": "0.2.2",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"license": "MIT",
"engines": {
@@ -6318,6 +6327,7 @@
"version": "0.6.0",
"resolved": "https://mirrors.tencent.com/npm/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
"engines": {
"node": ">=4.0.0"
}
@@ -6342,6 +6352,7 @@
"version": "1.0.4",
"resolved": "https://mirrors.tencent.com/npm/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "license": "MIT",
"dependencies": {
"clone": "^1.0.2"
},
@@ -6370,6 +6381,7 @@
"version": "2.0.0",
"resolved": "https://mirrors.tencent.com/npm/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "license": "MIT",
"engines": {
"node": ">=8"
}
@@ -6558,9 +6570,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.234",
- "resolved": "https://mirrors.tencent.com/npm/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
- "integrity": "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg==",
+ "version": "1.5.260",
+ "resolved": "https://mirrors.tencent.com/npm/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz",
+ "integrity": "sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -7246,29 +7258,29 @@
"license": "MIT"
},
"node_modules/expo": {
- "version": "54.0.21",
- "resolved": "https://mirrors.tencent.com/npm/expo/-/expo-54.0.21.tgz",
- "integrity": "sha512-I3kzMNW/43a71pt6hT0Zebd2zAPIMMeucUDDEdfUKYrzzTRwISZfVAv0dp8GWKHHDjZsy+FjE4RQCMdyKmiDeQ==",
+ "version": "54.0.25",
+ "resolved": "https://mirrors.tencent.com/npm/expo/-/expo-54.0.25.tgz",
+ "integrity": "sha512-+iSeBJfHRHzNPnHMZceEXhSGw4t5bNqFyd/5xMUoGfM+39rO7F72wxiLRpBKj0M6+0GQtMaEs+eTbcCrO7XyJQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
- "@expo/cli": "54.0.14",
+ "@expo/cli": "54.0.16",
"@expo/config": "~12.0.10",
"@expo/config-plugins": "~54.0.2",
"@expo/devtools": "0.1.7",
- "@expo/fingerprint": "0.15.2",
+ "@expo/fingerprint": "0.15.3",
"@expo/metro": "~54.1.0",
- "@expo/metro-config": "54.0.8",
+ "@expo/metro-config": "54.0.9",
"@expo/vector-icons": "^15.0.3",
"@ungap/structured-clone": "^1.3.0",
- "babel-preset-expo": "~54.0.6",
- "expo-asset": "~12.0.9",
+ "babel-preset-expo": "~54.0.7",
+ "expo-asset": "~12.0.10",
"expo-constants": "~18.0.10",
- "expo-file-system": "~19.0.17",
+ "expo-file-system": "~19.0.19",
"expo-font": "~14.0.9",
"expo-keep-awake": "~15.0.7",
- "expo-modules-autolinking": "3.0.19",
- "expo-modules-core": "3.0.23",
+ "expo-modules-autolinking": "3.0.22",
+ "expo-modules-core": "3.0.26",
"pretty-format": "^29.7.0",
"react-refresh": "^0.14.2",
"whatwg-url-without-unicode": "8.0.0-3"
@@ -7317,13 +7329,13 @@
}
},
"node_modules/expo-asset": {
- "version": "12.0.9",
- "resolved": "https://mirrors.tencent.com/npm/expo-asset/-/expo-asset-12.0.9.tgz",
- "integrity": "sha512-vrdRoyhGhBmd0nJcssTSk1Ypx3Mbn/eXaaBCQVkL0MJ8IOZpAObAjfD5CTy8+8RofcHEQdh3wwZVCs7crvfOeg==",
+ "version": "12.0.10",
+ "resolved": "https://mirrors.tencent.com/npm/expo-asset/-/expo-asset-12.0.10.tgz",
+ "integrity": "sha512-pZyeJkoDsALh4gpCQDzTA/UCLaPH/1rjQNGubmLn/uDM27S4iYJb/YWw4+CNZOtd5bCUOhDPg5DtGQnydNFSXg==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.8.7",
- "expo-constants": "~18.0.9"
+ "expo-constants": "~18.0.10"
},
"peerDependencies": {
"expo": "*",
@@ -7355,9 +7367,9 @@
}
},
"node_modules/expo-camera": {
- "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==",
+ "version": "17.0.9",
+ "resolved": "https://mirrors.tencent.com/npm/expo-camera/-/expo-camera-17.0.9.tgz",
+ "integrity": "sha512-KgticPGurqEsaPBIwbG0T6mzAVnqZasDdM/6OoJt5zPh6tWB09+th6cBF1WafIBMPy8AWbfyUQSqQXqOrNJClg==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
@@ -7374,6 +7386,17 @@
}
}
},
+ "node_modules/expo-clipboard": {
+ "version": "8.0.7",
+ "resolved": "https://mirrors.tencent.com/npm/expo-clipboard/-/expo-clipboard-8.0.7.tgz",
+ "integrity": "sha512-zvlfFV+wB2QQrQnHWlo0EKHAkdi2tycLtE+EXFUWTPZYkgu1XcH+aiKfd4ul7Z0SDF+1IuwoiW9AA9eO35aj3Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo-constants": {
"version": "18.0.10",
"resolved": "https://mirrors.tencent.com/npm/expo-constants/-/expo-constants-18.0.10.tgz",
@@ -7389,9 +7412,9 @@
}
},
"node_modules/expo-file-system": {
- "version": "19.0.17",
- "resolved": "https://mirrors.tencent.com/npm/expo-file-system/-/expo-file-system-19.0.17.tgz",
- "integrity": "sha512-WwaS01SUFrxBnExn87pg0sCTJjZpf2KAOzfImG0o8yhkU7fbYpihpl/oocXBEsNbj58a8hVt1Y4CVV5c1tzu/g==",
+ "version": "19.0.19",
+ "resolved": "https://mirrors.tencent.com/npm/expo-file-system/-/expo-file-system-19.0.19.tgz",
+ "integrity": "sha512-OrpOV4fEBFMFv+jy7PnENpPbsWoBmqWGidSwh1Ai52PLl6JIInYGfZTc6kqyPNGtFTwm7Y9mSWnE8g+dtLxu7g==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
@@ -7413,9 +7436,9 @@
}
},
"node_modules/expo-glass-effect": {
- "version": "0.1.5",
- "resolved": "https://mirrors.tencent.com/npm/expo-glass-effect/-/expo-glass-effect-0.1.5.tgz",
- "integrity": "sha512-DnleHtTpWET89I4GQsr4PDDvesKFA4miX+xV+1+ko5Z6SHkanBHD8gAOnvf07wTanzm4e8dAf0/abu7JmZlZIA==",
+ "version": "0.1.7",
+ "resolved": "https://mirrors.tencent.com/npm/expo-glass-effect/-/expo-glass-effect-0.1.7.tgz",
+ "integrity": "sha512-DxminueyL6TWoC9A3omka57XzpxUXLEGpDi/tnlvYwPSihB6lvGp2my+0k97lUKsBHbJg29weMCEQxNa/AyRHA==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
@@ -7492,12 +7515,12 @@
}
},
"node_modules/expo-linking": {
- "version": "8.0.8",
- "resolved": "https://mirrors.tencent.com/npm/expo-linking/-/expo-linking-8.0.8.tgz",
- "integrity": "sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==",
+ "version": "8.0.9",
+ "resolved": "https://mirrors.tencent.com/npm/expo-linking/-/expo-linking-8.0.9.tgz",
+ "integrity": "sha512-a0UHhlVyfwIbn8b1PSFPoFiIDJeps2iEq109hVH3CHd0CMKuRxFfNio9Axe2BjXhiJCYWR4OV1iIyzY/GjiVkQ==",
"license": "MIT",
"dependencies": {
- "expo-constants": "~18.0.8",
+ "expo-constants": "~18.0.10",
"invariant": "^2.2.4"
},
"peerDependencies": {
@@ -7529,15 +7552,14 @@
}
},
"node_modules/expo-modules-autolinking": {
- "version": "3.0.19",
- "resolved": "https://mirrors.tencent.com/npm/expo-modules-autolinking/-/expo-modules-autolinking-3.0.19.tgz",
- "integrity": "sha512-tSMYGnfZmAaN77X8iMLiaSgbCFnA7eh6s2ac09J2N2N0Rcf2RCE27jg0c0XenTMTWUcM4QvLhsNHof/WtlKqPw==",
+ "version": "3.0.22",
+ "resolved": "https://mirrors.tencent.com/npm/expo-modules-autolinking/-/expo-modules-autolinking-3.0.22.tgz",
+ "integrity": "sha512-Ej4SsZAnUUVFmbn6SoBso8K308mRKg8xgapdhP7v7IaSgfbexUoqxoiV31949HQQXuzmgvpkXCfp6Ex+mDW0EQ==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.1.0",
"commander": "^7.2.0",
- "glob": "^10.4.2",
"require-from-string": "^2.0.2",
"resolve-from": "^5.0.0"
},
@@ -7546,9 +7568,9 @@
}
},
"node_modules/expo-modules-core": {
- "version": "3.0.23",
- "resolved": "https://mirrors.tencent.com/npm/expo-modules-core/-/expo-modules-core-3.0.23.tgz",
- "integrity": "sha512-NYHi5LK/cdIyOjK9ZQAgfDPCOqER26cIbU3gzsce7YdnsmlNFR0qMfWOj9zAmaFBviC2kCkCOmitwk4357Td3Q==",
+ "version": "3.0.26",
+ "resolved": "https://mirrors.tencent.com/npm/expo-modules-core/-/expo-modules-core-3.0.26.tgz",
+ "integrity": "sha512-WWjficXz32VmQ+xDoO+c0+jwDME0n/47wONrJkRvtm32H9W8n3MXkOMGemDl95HyPKYsaYKhjFGUOVOxIF3hcQ==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
@@ -7593,9 +7615,9 @@
}
},
"node_modules/expo-router": {
- "version": "6.0.14",
- "resolved": "https://mirrors.tencent.com/npm/expo-router/-/expo-router-6.0.14.tgz",
- "integrity": "sha512-vizLO4SgnMEL+PPs2dXr+etEOuksjue7yUQBCtfCEdqoDkQlB0r35zI7rS34Wt53sxKWSlM2p+038qQEpxtiFw==",
+ "version": "6.0.15",
+ "resolved": "https://mirrors.tencent.com/npm/expo-router/-/expo-router-6.0.15.tgz",
+ "integrity": "sha512-PAettvLifQzb6hibCmBqxbR9UljlH61GvDRLyarGxs/tG9OpMXCoZHZo8gGCO24K1/6cchBKBcjvQ0PRrKwPew==",
"license": "MIT",
"dependencies": {
"@expo/metro-runtime": "^6.1.2",
@@ -7608,7 +7630,7 @@
"client-only": "^0.0.1",
"debug": "^4.3.4",
"escape-string-regexp": "^4.0.0",
- "expo-server": "^1.0.3",
+ "expo-server": "^1.0.4",
"fast-deep-equal": "^3.1.3",
"invariant": "^2.2.4",
"nanoid": "^3.3.8",
@@ -7628,7 +7650,7 @@
"@testing-library/react-native": ">= 12.0.0",
"expo": "*",
"expo-constants": "^18.0.10",
- "expo-linking": "^8.0.8",
+ "expo-linking": "^8.0.9",
"react": "*",
"react-dom": "*",
"react-native": "*",
@@ -7846,21 +7868,21 @@
}
},
"node_modules/expo-server": {
- "version": "1.0.3",
- "resolved": "https://mirrors.tencent.com/npm/expo-server/-/expo-server-1.0.3.tgz",
- "integrity": "sha512-SOwdzM/BFAL+vTFlUDJG6ljhyk6TyTl+LRK3ubGmN+Pf18ENRqKj37U8krc5vH926sAsB3IFcE8kJEYf4dG7PA==",
+ "version": "1.0.4",
+ "resolved": "https://mirrors.tencent.com/npm/expo-server/-/expo-server-1.0.4.tgz",
+ "integrity": "sha512-IN06r3oPxFh3plSXdvBL7dx0x6k+0/g0bgxJlNISs6qL5Z+gyPuWS750dpTzOeu37KyBG0RcyO9cXUKzjYgd4A==",
"license": "MIT",
"engines": {
"node": ">=20.16.0"
}
},
"node_modules/expo-splash-screen": {
- "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==",
+ "version": "31.0.11",
+ "resolved": "https://mirrors.tencent.com/npm/expo-splash-screen/-/expo-splash-screen-31.0.11.tgz",
+ "integrity": "sha512-D7MQflYn/PAN3+fACSyxHO4oxZMBezllbgFdVY8roAS1gXpCy8SS6LrGHTD0VpOPEp3X4Gn7evTnXSI9nFoI5Q==",
"license": "MIT",
"dependencies": {
- "@expo/prebuild-config": "^54.0.3"
+ "@expo/prebuild-config": "^54.0.6"
},
"peerDependencies": {
"expo": "*"
@@ -7950,9 +7972,9 @@
}
},
"node_modules/expo/node_modules/@expo/cli": {
- "version": "54.0.14",
- "resolved": "https://mirrors.tencent.com/npm/@expo/cli/-/cli-54.0.14.tgz",
- "integrity": "sha512-M7QW/GHx1FJg+CGgChGKerYXmCGWDskJ8S6w+8m49IBZ41CMDeWRH5snQkFoGCttF8WnzhGiX+nu69AFnEuDHQ==",
+ "version": "54.0.16",
+ "resolved": "https://mirrors.tencent.com/npm/@expo/cli/-/cli-54.0.16.tgz",
+ "integrity": "sha512-hY/OdRaJMs5WsVPuVSZ+RLH3VObJmL/pv5CGCHEZHN2PxZjSZSdctyKV8UcFBXTF0yIKNAJ9XLs1dlNYXHh4Cw==",
"license": "MIT",
"dependencies": {
"@0no-co/graphql.web": "^1.0.8",
@@ -7963,9 +7985,9 @@
"@expo/env": "~2.0.7",
"@expo/image-utils": "^0.8.7",
"@expo/json-file": "^10.0.7",
- "@expo/mcp-tunnel": "~0.0.7",
+ "@expo/mcp-tunnel": "~0.1.0",
"@expo/metro": "~54.1.0",
- "@expo/metro-config": "~54.0.8",
+ "@expo/metro-config": "~54.0.9",
"@expo/osascript": "^2.3.7",
"@expo/package-manager": "^1.9.8",
"@expo/plist": "^0.4.7",
@@ -7988,7 +8010,7 @@
"connect": "^3.7.0",
"debug": "^4.3.4",
"env-editor": "^0.4.1",
- "expo-server": "^1.0.3",
+ "expo-server": "^1.0.4",
"freeport-async": "^2.0.0",
"getenv": "^2.0.0",
"glob": "^10.4.2",
@@ -8064,6 +8086,7 @@
"version": "7.7.3",
"resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -8253,7 +8276,7 @@
},
"node_modules/filter-obj": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
"license": "MIT",
"engines": {
@@ -8837,7 +8860,8 @@
"node_modules/hosted-git-info/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://mirrors.tencent.com/npm/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
@@ -9064,9 +9088,9 @@
}
},
"node_modules/immer": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
- "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "version": "11.0.0",
+ "resolved": "https://mirrors.tencent.com/npm/immer/-/immer-11.0.0.tgz",
+ "integrity": "sha512-XtRG4SINt4dpqlnJvs70O2j6hH7H0X8fUzFsjMn1rwnETaxwp83HLNimXBjZ78MrKl3/d3/pkzDH0o0Lkxm37Q==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -10146,6 +10170,7 @@
"cpu": [
"arm64"
],
+ "license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
@@ -10265,6 +10290,7 @@
"cpu": [
"x64"
],
+ "license": "MPL-2.0",
"optional": true,
"os": [
"linux"
@@ -10304,6 +10330,7 @@
"cpu": [
"arm64"
],
+ "license": "MPL-2.0",
"optional": true,
"os": [
"win32"
@@ -10395,6 +10422,7 @@
"version": "2.2.0",
"resolved": "https://mirrors.tencent.com/npm/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+ "license": "MIT",
"dependencies": {
"chalk": "^2.0.1"
},
@@ -10438,7 +10466,8 @@
"node_modules/log-symbols/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://mirrors.tencent.com/npm/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
},
"node_modules/log-symbols/node_modules/escape-string-regexp": {
"version": "1.0.5",
@@ -10453,6 +10482,7 @@
"version": "3.0.0",
"resolved": "https://mirrors.tencent.com/npm/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
"engines": {
"node": ">=4"
}
@@ -10461,6 +10491,7 @@
"version": "5.5.0",
"resolved": "https://mirrors.tencent.com/npm/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -10954,7 +10985,6 @@
"version": "1.54.0",
"resolved": "https://mirrors.tencent.com/npm/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -10984,6 +11014,7 @@
"version": "1.2.0",
"resolved": "https://mirrors.tencent.com/npm/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "license": "MIT",
"engines": {
"node": ">=4"
}
@@ -11136,9 +11167,9 @@
}
},
"node_modules/node-forge": {
- "version": "1.3.1",
- "resolved": "https://mirrors.tencent.com/npm/node-forge/-/node-forge-1.3.1.tgz",
- "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "version": "1.3.2",
+ "resolved": "https://mirrors.tencent.com/npm/node-forge/-/node-forge-1.3.2.tgz",
+ "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
@@ -11151,9 +11182,9 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.23",
- "resolved": "https://mirrors.tencent.com/npm/node-releases/-/node-releases-2.0.23.tgz",
- "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
+ "version": "2.0.27",
+ "resolved": "https://mirrors.tencent.com/npm/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"license": "MIT"
},
"node_modules/normalize-path": {
@@ -11184,6 +11215,7 @@
"version": "7.7.3",
"resolved": "https://mirrors.tencent.com/npm/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -11391,6 +11423,7 @@
"version": "2.0.1",
"resolved": "https://mirrors.tencent.com/npm/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
+ "license": "MIT",
"dependencies": {
"mimic-fn": "^1.0.0"
},
@@ -11453,6 +11486,7 @@
"version": "4.1.1",
"resolved": "https://mirrors.tencent.com/npm/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -11493,7 +11527,8 @@
"node_modules/ora/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://mirrors.tencent.com/npm/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
},
"node_modules/ora/node_modules/escape-string-regexp": {
"version": "1.0.5",
@@ -11508,6 +11543,7 @@
"version": "3.0.0",
"resolved": "https://mirrors.tencent.com/npm/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
"engines": {
"node": ">=4"
}
@@ -11527,6 +11563,7 @@
"version": "5.5.0",
"resolved": "https://mirrors.tencent.com/npm/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -11929,7 +11966,7 @@
},
"node_modules/query-string": {
"version": "7.1.3",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"license": "MIT",
"dependencies": {
@@ -11994,6 +12031,7 @@
"version": "1.2.8",
"resolved": "https://mirrors.tencent.com/npm/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -12008,6 +12046,7 @@
"version": "2.0.1",
"resolved": "https://mirrors.tencent.com/npm/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -12260,17 +12299,17 @@
}
},
"node_modules/react-native-purchases": {
- "version": "9.5.4",
- "resolved": "https://mirrors.tencent.com/npm/react-native-purchases/-/react-native-purchases-9.5.4.tgz",
- "integrity": "sha512-N2S5GvV6gkcl9O+1XzzH10AjPMYie2aWoHi2QTN2fG4tulGSHhTYIYhX0QjkJzN0eX2ACRgPB4BUxcbh1/DERQ==",
+ "version": "9.6.7",
+ "resolved": "https://mirrors.tencent.com/npm/react-native-purchases/-/react-native-purchases-9.6.7.tgz",
+ "integrity": "sha512-gbb2l0oKkmxKo7Zk11BgOWeGnixiMU+Kig/8QYS78ka7S5rc8aR38RzygtXmPEfxhwRT7w4uzCVV49bab0RPjg==",
"license": "MIT",
"workspaces": [
"examples/purchaseTesterTypescript",
"react-native-purchases-ui"
],
"dependencies": {
- "@revenuecat/purchases-js-hybrid-mappings": "17.10.0",
- "@revenuecat/purchases-typescript-internal": "17.10.0"
+ "@revenuecat/purchases-js-hybrid-mappings": "17.19.1",
+ "@revenuecat/purchases-typescript-internal": "17.19.1"
},
"peerDependencies": {
"react": ">= 16.6.3",
@@ -12284,9 +12323,9 @@
}
},
"node_modules/react-native-reanimated": {
- "version": "4.1.3",
- "resolved": "https://mirrors.tencent.com/npm/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz",
- "integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==",
+ "version": "4.1.5",
+ "resolved": "https://mirrors.tencent.com/npm/react-native-reanimated/-/react-native-reanimated-4.1.5.tgz",
+ "integrity": "sha512-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q==",
"license": "MIT",
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1",
@@ -12706,14 +12745,14 @@
},
"node_modules/regenerate": {
"version": "1.4.2",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/regenerate/-/regenerate-1.4.2.tgz",
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
"license": "MIT"
},
"node_modules/regenerate-unicode-properties": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
- "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
+ "version": "10.2.2",
+ "resolved": "https://mirrors.tencent.com/npm/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
+ "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2"
@@ -12750,17 +12789,17 @@
}
},
"node_modules/regexpu-core": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz",
- "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
+ "version": "6.4.0",
+ "resolved": "https://mirrors.tencent.com/npm/regexpu-core/-/regexpu-core-6.4.0.tgz",
+ "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2",
- "regenerate-unicode-properties": "^10.2.0",
+ "regenerate-unicode-properties": "^10.2.2",
"regjsgen": "^0.8.0",
- "regjsparser": "^0.12.0",
+ "regjsparser": "^0.13.0",
"unicode-match-property-ecmascript": "^2.0.0",
- "unicode-match-property-value-ecmascript": "^2.1.0"
+ "unicode-match-property-value-ecmascript": "^2.2.1"
},
"engines": {
"node": ">=4"
@@ -12768,34 +12807,22 @@
},
"node_modules/regjsgen": {
"version": "0.8.0",
- "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
"license": "MIT"
},
"node_modules/regjsparser": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
- "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
+ "version": "0.13.0",
+ "resolved": "https://mirrors.tencent.com/npm/regjsparser/-/regjsparser-0.13.0.tgz",
+ "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
"license": "BSD-2-Clause",
"dependencies": {
- "jsesc": "~3.0.2"
+ "jsesc": "~3.1.0"
},
"bin": {
"regjsparser": "bin/parser"
}
},
- "node_modules/regjsparser/node_modules/jsesc": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
- "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -12831,6 +12858,7 @@
"version": "1.7.1",
"resolved": "https://mirrors.tencent.com/npm/resolve/-/resolve-1.7.1.tgz",
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
+ "license": "MIT",
"dependencies": {
"path-parse": "^1.0.5"
}
@@ -13543,7 +13571,7 @@
},
"node_modules/split-on-first": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"license": "MIT",
"engines": {
@@ -13636,7 +13664,7 @@
},
"node_modules/strict-uri-encode": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
"license": "MIT",
"engines": {
@@ -13949,10 +13977,10 @@
}
},
"node_modules/tar": {
- "version": "7.5.1",
- "resolved": "https://mirrors.tencent.com/npm/tar/-/tar-7.5.1.tgz",
- "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
- "license": "ISC",
+ "version": "7.5.2",
+ "resolved": "https://mirrors.tencent.com/npm/tar/-/tar-7.5.2.tgz",
+ "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
@@ -13968,6 +13996,7 @@
"version": "5.0.0",
"resolved": "https://mirrors.tencent.com/npm/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
@@ -14403,7 +14432,7 @@
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
"license": "MIT",
"engines": {
@@ -14412,7 +14441,7 @@
},
"node_modules/unicode-match-property-ecmascript": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
"license": "MIT",
"dependencies": {
@@ -14424,18 +14453,18 @@
}
},
"node_modules/unicode-match-property-value-ecmascript": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
- "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
+ "version": "2.2.1",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
+ "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-property-aliases-ecmascript": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
- "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
+ "version": "2.2.0",
+ "resolved": "https://mirrors.tencent.com/npm/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
"license": "MIT",
"engines": {
"node": ">=4"
@@ -14513,9 +14542,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "version": "1.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
"funding": [
{
"type": "opencollective",
@@ -14530,7 +14559,6 @@
"url": "https://github.com/sponsors/ai"
}
],
- "license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
@@ -14672,7 +14700,6 @@
"version": "1.1.2",
"resolved": "https://mirrors.tencent.com/npm/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -15385,12 +15412,12 @@
}
},
"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==",
+ "version": "3.25.0",
+ "resolved": "https://mirrors.tencent.com/npm/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz",
+ "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==",
"license": "ISC",
"peerDependencies": {
- "zod": "^3.24.1"
+ "zod": "^3.25 || ^4"
}
}
}
diff --git a/package.json b/package.json
index cb01ed8..e2ab6d9 100644
--- a/package.json
+++ b/package.json
@@ -11,27 +11,28 @@
"dependencies": {
"@expo/metro-runtime": "~6.1.2",
"@expo/ui": "~0.2.0-beta.7",
- "@expo/vector-icons": "^15.0.2",
+ "@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "^2.2.0",
- "@react-native-community/datetimepicker": "8.4.4",
+ "@react-native-community/datetimepicker": "8.5.1",
"@react-native-masked-view/masked-view": "^0.3.2",
- "@react-native-picker/picker": "2.11.1",
+ "@react-native-picker/picker": "2.11.4",
"@react-native-voice/voice": "^3.2.4",
- "@react-navigation/bottom-tabs": "^7.4.0",
- "@react-navigation/elements": "^2.6.4",
- "@react-navigation/native": "^7.1.8",
- "@reduxjs/toolkit": "^2.9.0",
- "@sentry/react-native": "~7.2.0",
- "@types/lodash": "^4.17.20",
- "dayjs": "^1.11.18",
- "expo": "54.0.21",
+ "@react-navigation/bottom-tabs": "^7.8.6",
+ "@react-navigation/elements": "^2.8.3",
+ "@react-navigation/native": "^7.1.21",
+ "@reduxjs/toolkit": "^2.11.0",
+ "@sentry/react-native": "~7.7.0",
+ "@types/lodash": "^4.17.21",
+ "dayjs": "^1.11.19",
+ "expo": "54.0.25",
"expo-apple-authentication": "~8.0.7",
"expo-background-task": "~1.0.8",
"expo-blur": "~15.0.7",
- "expo-camera": "~17.0.8",
- "expo-constants": "~18.0.9",
+ "expo-clipboard": "~8.0.7",
+ "expo-camera": "~17.0.9",
+ "expo-constants": "~18.0.10",
"expo-font": "~14.0.9",
- "expo-glass-effect": "~0.1.5",
+ "expo-glass-effect": "~0.1.7",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.10",
"expo-image-picker": "~17.0.8",
@@ -41,8 +42,8 @@
"expo-media-library": "^18.2.0",
"expo-notifications": "~0.32.12",
"expo-quick-actions": "^6.0.0",
- "expo-router": "~6.0.14",
- "expo-splash-screen": "~31.0.10",
+ "expo-router": "~6.0.15",
+ "expo-splash-screen": "~31.0.11",
"expo-sqlite": "^16.0.8",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
@@ -84,4 +85,4 @@
"typescript": "~5.9.2"
},
"private": true
-}
+}
\ No newline at end of file
diff --git a/services/api.ts b/services/api.ts
index 4a6f02c..c96d857 100644
--- a/services/api.ts
+++ b/services/api.ts
@@ -82,14 +82,38 @@ async function handle401Unauthorized() {
}
}
+// Token 缓存:内存中保存一份,避免每次都读取 AsyncStorage
let inMemoryToken: string | null = null;
+/**
+ * 设置认证 token
+ * 同时更新内存缓存和持久化存储
+ */
export async function setAuthToken(token: string | null): Promise {
inMemoryToken = token;
+
+ // 同步更新 AsyncStorage
+ if (token) {
+ await AsyncStorage.setItem(STORAGE_KEYS.authToken, token);
+ } else {
+ await AsyncStorage.removeItem(STORAGE_KEYS.authToken);
+ }
}
-export function getAuthToken(): Promise {
- return AsyncStorage.getItem(STORAGE_KEYS.authToken);
+/**
+ * 获取认证 token
+ * 优先使用内存缓存,若无则从 AsyncStorage 读取并缓存
+ */
+export async function getAuthToken(): Promise {
+ // 如果内存中有,直接返回
+ if (inMemoryToken !== null) {
+ return inMemoryToken;
+ }
+
+ // 否则从 AsyncStorage 读取并缓存到内存
+ const token = await AsyncStorage.getItem(STORAGE_KEYS.authToken);
+ inMemoryToken = token;
+ return token;
}
export type ApiRequestOptions = {
diff --git a/services/challengesApi.ts b/services/challengesApi.ts
index a6b7f3b..8e48098 100644
--- a/services/challengesApi.ts
+++ b/services/challengesApi.ts
@@ -2,11 +2,24 @@ import { api } from './api';
export type ChallengeStatus = 'upcoming' | 'ongoing' | 'expired';
+export enum ChallengeSource {
+ SYSTEM = 'system',
+ CUSTOM = 'custom',
+}
+
+export enum ChallengeState {
+ DRAFT = 'draft',
+ ACTIVE = 'active',
+ ARCHIVED = 'archived',
+}
+
export type ChallengeProgressDto = {
completed: number;
target: number;
remaining: number
checkedInToday: boolean;
+ lastProgressAt?: string;
+ last_progress_at?: string;
};
export type RankingItemDto = {
@@ -38,7 +51,7 @@ export type ChallengeListItemDto = {
durationLabel: string;
requirementLabel: string;
unit?: string;
- status: ChallengeStatus;
+ status?: ChallengeStatus;
participantsCount: number;
rankingDescription?: string;
highlightTitle: string;
@@ -50,12 +63,23 @@ export type ChallengeListItemDto = {
endAt?: string;
minimumCheckInDays: number; // 最小打卡天数
type: ChallengeType;
+ shareCode?: string | null;
+ source?: ChallengeSource;
+ creatorId?: string | null;
+ isCreator?: boolean;
+ isPublic?: boolean;
+ maxParticipants?: number | null;
+ challengeState?: ChallengeState;
+ progressUnit?: string;
+ targetValue?: number;
+ summary?: string | null;
};
export type ChallengeDetailDto = ChallengeListItemDto & {
- summary?: string;
- rankings: RankingItemDto[];
+ summary?: string | null;
+ rankings?: RankingItemDto[];
userRank?: number;
+ challengeState?: ChallengeState;
};
export type ChallengeRankingsDto = {
@@ -65,6 +89,31 @@ export type ChallengeRankingsDto = {
items: RankingItemDto[];
};
+export type ChallengeListResponse = {
+ items: ChallengeListItemDto[];
+ total: number;
+ page: number;
+ pageSize: number;
+};
+
+export type CreateCustomChallengePayload = {
+ title: string;
+ type: ChallengeType;
+ image?: string | null;
+ startAt: number;
+ endAt: number;
+ targetValue: number;
+ minimumCheckInDays: number;
+ durationLabel: string;
+ requirementLabel: string;
+ summary?: string | null;
+ progressUnit?: string;
+ periodLabel?: string | null;
+ rankingDescription?: string | null;
+ isPublic?: boolean;
+ maxParticipants?: number | null;
+};
+
export async function listChallenges(): Promise {
return api.get('/challenges');
}
@@ -101,3 +150,43 @@ export async function getChallengeRankings(
const url = `/challenges/${encodeURIComponent(id)}/rankings${query ? `?${query}` : ''}`;
return api.get(url);
}
+
+export async function listMyCustomChallenges(
+ params?: { page?: number; pageSize?: number; state?: ChallengeState }
+): Promise {
+ const searchParams = new URLSearchParams();
+ if (params?.page) {
+ searchParams.append('page', String(params.page));
+ }
+ if (params?.pageSize) {
+ searchParams.append('pageSize', String(params.pageSize));
+ }
+ if (params?.state) {
+ searchParams.append('state', params.state);
+ }
+ const query = searchParams.toString();
+ const url = `/challenges/my/created${query ? `?${query}` : ''}`;
+ return api.get(url);
+}
+
+export async function createCustomChallenge(
+ payload: CreateCustomChallengePayload
+): Promise {
+ return api.post('/challenges/custom', payload);
+}
+
+export async function joinChallengeByCode(shareCode: string): Promise {
+ return api.post('/challenges/join-by-code', { shareCode });
+}
+
+export async function getChallengeByShareCode(shareCode: string): Promise {
+ return api.get(`/challenges/share/${encodeURIComponent(shareCode)}`);
+}
+
+export async function regenerateChallengeShareCode(
+ id: string
+): Promise<{ shareCode: string }> {
+ return api.post<{ shareCode: string }>(
+ `/challenges/custom/${encodeURIComponent(id)}/regenerate-code`
+ );
+}
diff --git a/store/challengesSlice.ts b/store/challengesSlice.ts
index aabba71..f7a24a4 100644
--- a/store/challengesSlice.ts
+++ b/store/challengesSlice.ts
@@ -1,13 +1,21 @@
+import dayjs from 'dayjs';
+
import { appStoreReviewService } from '@/services/appStoreReview';
import {
type ChallengeDetailDto,
type ChallengeListItemDto,
type ChallengeProgressDto,
+ ChallengeSource,
+ ChallengeState,
type ChallengeStatus,
+ type CreateCustomChallengePayload,
type RankingItemDto,
+ createCustomChallenge,
+ getChallengeByShareCode,
getChallengeDetail,
getChallengeRankings,
joinChallenge as joinChallengeApi,
+ joinChallengeByCode as joinChallengeByCodeApi,
leaveChallenge as leaveChallengeApi,
listChallenges,
reportChallengeProgress as reportChallengeProgressApi,
@@ -21,9 +29,9 @@ export type ChallengeProgress = ChallengeProgressDto;
export type RankingItem = RankingItemDto;
export type ChallengeSummary = ChallengeListItemDto;
export type ChallengeDetail = ChallengeDetailDto;
-export type { ChallengeStatus };
+export type { ChallengeSource, ChallengeState, ChallengeStatus };
export type ChallengeEntity = ChallengeSummary & {
- summary?: string;
+ summary?: string | null;
rankings?: RankingItem[];
userRank?: number;
};
@@ -38,7 +46,7 @@ type ChallengeRankingList = {
type ChallengesState = {
entities: Record;
- order: string[];
+ orderedIds: string[];
listStatus: AsyncStatus;
listError?: string;
detailStatus: Record;
@@ -53,11 +61,15 @@ type ChallengesState = {
rankingStatus: Record;
rankingLoadMoreStatus: Record;
rankingError: Record;
+ createStatus: AsyncStatus;
+ createError?: string;
+ joinByCodeStatus: AsyncStatus;
+ joinByCodeError?: string;
};
const initialState: ChallengesState = {
entities: {},
- order: [],
+ orderedIds: [],
listStatus: 'idle',
listError: undefined,
detailStatus: {},
@@ -72,6 +84,10 @@ const initialState: ChallengesState = {
rankingStatus: {},
rankingLoadMoreStatus: {},
rankingError: {},
+ createStatus: 'idle',
+ createError: undefined,
+ joinByCodeStatus: 'idle',
+ joinByCodeError: undefined,
};
const toErrorMessage = (error: unknown): string => {
@@ -168,10 +184,41 @@ export const fetchChallengeRankings = createAsyncThunk<
}
});
+export const createCustomChallengeThunk = createAsyncThunk<
+ ChallengeDetail,
+ CreateCustomChallengePayload,
+ { rejectValue: string }
+>('challenges/createCustom', async (payload, { rejectWithValue }) => {
+ try {
+ return await createCustomChallenge(payload);
+ } catch (error) {
+ return rejectWithValue(toErrorMessage(error));
+ }
+});
+
+export const joinChallengeByCode = createAsyncThunk<
+ { challenge: ChallengeDetail; progress: ChallengeProgress },
+ string,
+ { rejectValue: string }
+>('challenges/joinByCode', async (shareCode, { rejectWithValue }) => {
+ try {
+ const progress = await joinChallengeByCodeApi(shareCode);
+ const challenge = await getChallengeByShareCode(shareCode);
+ return { challenge: { ...challenge, progress }, progress };
+ } catch (error) {
+ return rejectWithValue(toErrorMessage(error));
+ }
+});
+
const challengesSlice = createSlice({
name: 'challenges',
initialState,
- reducers: {},
+ reducers: {
+ resetJoinByCodeState: (state) => {
+ state.joinByCodeStatus = 'idle';
+ state.joinByCodeError = undefined;
+ },
+ },
extraReducers: (builder) => {
builder
.addCase(fetchChallenges.pending, (state) => {
@@ -181,18 +228,15 @@ const challengesSlice = createSlice({
.addCase(fetchChallenges.fulfilled, (state, action) => {
state.listStatus = 'succeeded';
state.listError = undefined;
- const ids = new Set();
+ const incomingIds = new Set();
action.payload.forEach((challenge) => {
- ids.add(challenge.id);
+ incomingIds.add(challenge.id);
+ const source = challenge.source ?? ChallengeSource.SYSTEM;
const existing = state.entities[challenge.id];
- if (existing) {
- Object.assign(existing, challenge);
- } else {
- state.entities[challenge.id] = { ...challenge };
- }
+ state.entities[challenge.id] = { ...(existing ?? {}), ...challenge, source };
});
Object.keys(state.entities).forEach((id) => {
- if (!ids.has(id)) {
+ if (!incomingIds.has(id) && !state.entities[id]?.isJoined) {
delete state.entities[id];
delete state.detailStatus[id];
delete state.detailError[id];
@@ -204,7 +248,7 @@ const challengesSlice = createSlice({
delete state.progressError[id];
}
});
- state.order = action.payload.map((item) => item.id);
+ state.orderedIds = action.payload.map((item) => item.id);
})
.addCase(fetchChallenges.rejected, (state, action) => {
state.listStatus = 'failed';
@@ -220,11 +264,8 @@ const challengesSlice = createSlice({
state.detailStatus[detail.id] = 'succeeded';
state.detailError[detail.id] = undefined;
const existing = state.entities[detail.id];
- if (existing) {
- Object.assign(existing, detail);
- } else {
- state.entities[detail.id] = { ...detail };
- }
+ const source = detail.source ?? existing?.source ?? ChallengeSource.SYSTEM;
+ state.entities[detail.id] = { ...(existing ?? {}), ...detail, source };
})
.addCase(fetchChallengeDetail.rejected, (state, action) => {
const id = action.meta.arg;
@@ -333,9 +374,50 @@ const challengesSlice = createSlice({
state.rankingError[id] = message;
}
});
+
+ builder
+ .addCase(createCustomChallengeThunk.pending, (state) => {
+ state.createStatus = 'loading';
+ state.createError = undefined;
+ })
+ .addCase(createCustomChallengeThunk.fulfilled, (state, action) => {
+ state.createStatus = 'succeeded';
+ state.createError = undefined;
+ const challenge = action.payload;
+ const existing = state.entities[challenge.id];
+ const source = ChallengeSource.CUSTOM;
+ state.entities[challenge.id] = { ...(existing ?? {}), ...challenge, source };
+ state.orderedIds = [challenge.id, ...state.orderedIds.filter((id) => id !== challenge.id)];
+ })
+ .addCase(createCustomChallengeThunk.rejected, (state, action) => {
+ state.createStatus = 'failed';
+ state.createError = action.payload ?? toErrorMessage(action.error);
+ });
+
+ builder
+ .addCase(joinChallengeByCode.pending, (state) => {
+ state.joinByCodeStatus = 'loading';
+ state.joinByCodeError = undefined;
+ })
+ .addCase(joinChallengeByCode.fulfilled, (state, action) => {
+ state.joinByCodeStatus = 'succeeded';
+ state.joinByCodeError = undefined;
+ const { challenge, progress } = action.payload;
+ const existing = state.entities[challenge.id];
+ const source = challenge.source ?? existing?.source ?? ChallengeSource.SYSTEM;
+ const merged = { ...(existing ?? {}), ...challenge, progress, isJoined: true, source };
+ state.entities[challenge.id] = merged as ChallengeEntity;
+ state.orderedIds = [challenge.id, ...state.orderedIds.filter((id) => id !== challenge.id)];
+ })
+ .addCase(joinChallengeByCode.rejected, (state, action) => {
+ state.joinByCodeStatus = 'failed';
+ state.joinByCodeError = action.payload ?? toErrorMessage(action.error);
+ });
},
});
+export const { resetJoinByCodeState } = challengesSlice.actions;
+
export default challengesSlice.reducer;
const selectChallengesState = (state: RootState) => state.challenges;
@@ -355,20 +437,30 @@ export const selectChallengeEntities = createSelector(
(state) => state.entities
);
-export const selectChallengeOrder = createSelector(
+const selectChallengeOrder = createSelector(
[selectChallengesState],
- (state) => state.order
+ (state) => state.orderedIds
);
export const selectChallengeList = createSelector(
[selectChallengeEntities, selectChallengeOrder],
- (entities, order) => order.map((id) => entities[id]).filter(Boolean) as ChallengeEntity[]
+ (entities, orderedIds) => orderedIds.map((id) => entities[id]).filter(Boolean) as ChallengeEntity[]
+);
+
+export const selectCustomChallengeList = createSelector(
+ [selectChallengeList],
+ (list) => list.filter((challenge) => challenge.source === ChallengeSource.CUSTOM)
+);
+
+export const selectOfficialChallengeList = createSelector(
+ [selectChallengeList],
+ (list) => list.filter((challenge) => challenge.source !== ChallengeSource.CUSTOM)
);
const formatNumberWithSeparator = (value: number): string =>
value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
-const formatMonthDay = (input: string | undefined): string | undefined => {
+const formatMonthDay = (input: string | number | undefined): string | undefined => {
if (!input) return undefined;
const date = new Date(input);
if (Number.isNaN(date.getTime())) return undefined;
@@ -384,6 +476,26 @@ const buildDateRangeLabel = (challenge: ChallengeEntity): string => {
return challenge.periodLabel ?? challenge.durationLabel;
};
+const deriveStatus = (challenge: ChallengeEntity): ChallengeStatus => {
+ if (challenge.status) return challenge.status;
+ if (challenge.challengeState === ChallengeState.ARCHIVED) {
+ return 'expired';
+ }
+ const now = dayjs();
+ const start = challenge.startAt ? dayjs(challenge.startAt) : null;
+ const end = challenge.endAt ? dayjs(challenge.endAt) : null;
+ if (start?.isValid() && start.isAfter(now)) {
+ return 'upcoming';
+ }
+ if (end?.isValid() && end.isBefore(now)) {
+ return 'expired';
+ }
+ return 'ongoing';
+};
+
+const FALLBACK_CHALLENGE_IMAGE =
+ 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?auto=format&fit=crop&w=1000&q=80';
+
export type ChallengeCardViewModel = {
id: string;
title: string;
@@ -392,7 +504,7 @@ export type ChallengeCardViewModel = {
participantsLabel: string;
status: ChallengeStatus;
isJoined: boolean;
- endAt?: string;
+ endAt?: string | number;
periodLabel?: string;
durationLabel: string;
requirementLabel: string;
@@ -401,27 +513,109 @@ export type ChallengeCardViewModel = {
ctaLabel: string;
progress?: ChallengeProgress;
avatars: string[];
+ source?: ChallengeSource;
+ shareCode?: string | null;
+ challengeState?: ChallengeState;
+ progressUnit?: string;
+ targetValue?: number;
+ isCreator?: boolean;
};
export const selectChallengeCards = createSelector([selectChallengeList], (challenges) =>
- challenges.map((challenge) => ({
- id: challenge.id,
- title: challenge.title,
- image: challenge.image,
- dateRange: buildDateRangeLabel(challenge),
- participantsLabel: `${formatNumberWithSeparator(challenge.participantsCount)} 人参与`,
- status: challenge.status,
- isJoined: challenge.isJoined,
- endAt: challenge.endAt,
- periodLabel: challenge.periodLabel,
- durationLabel: challenge.durationLabel,
- requirementLabel: challenge.requirementLabel,
- highlightTitle: challenge.highlightTitle,
- highlightSubtitle: challenge.highlightSubtitle,
- ctaLabel: challenge.ctaLabel,
- progress: challenge.progress,
- avatars: [],
- }))
+ challenges.map((challenge) => {
+ const participants =
+ typeof challenge.participantsCount === 'number' ? challenge.participantsCount : 0;
+ return {
+ id: challenge.id,
+ title: challenge.title,
+ image: challenge.image ?? FALLBACK_CHALLENGE_IMAGE,
+ dateRange: buildDateRangeLabel(challenge),
+ participantsLabel: `${formatNumberWithSeparator(participants)} 人参与`,
+ status: deriveStatus(challenge),
+ isJoined: challenge.isJoined,
+ endAt: challenge.endAt,
+ periodLabel: challenge.periodLabel,
+ durationLabel: challenge.durationLabel,
+ requirementLabel: challenge.requirementLabel,
+ highlightTitle: challenge.highlightTitle,
+ highlightSubtitle: challenge.highlightSubtitle,
+ ctaLabel: challenge.ctaLabel,
+ progress: challenge.progress,
+ avatars: [],
+ source: challenge.source,
+ shareCode: challenge.shareCode ?? null,
+ challengeState: challenge.challengeState,
+ progressUnit: challenge.progressUnit,
+ targetValue: challenge.targetValue,
+ isCreator: challenge.isCreator,
+ };
+ })
+);
+
+export const selectCustomChallengeCards = createSelector(
+ [selectCustomChallengeList],
+ (challenges) =>
+ challenges.map((challenge) => {
+ const participants =
+ typeof challenge.participantsCount === 'number' ? challenge.participantsCount : 0;
+ return {
+ id: challenge.id,
+ title: challenge.title,
+ image: challenge.image ?? FALLBACK_CHALLENGE_IMAGE,
+ dateRange: buildDateRangeLabel(challenge),
+ participantsLabel: `${formatNumberWithSeparator(participants)} 人参与`,
+ status: deriveStatus(challenge),
+ isJoined: challenge.isJoined,
+ endAt: challenge.endAt,
+ periodLabel: challenge.periodLabel,
+ durationLabel: challenge.durationLabel,
+ requirementLabel: challenge.requirementLabel,
+ highlightTitle: challenge.highlightTitle,
+ highlightSubtitle: challenge.highlightSubtitle,
+ ctaLabel: challenge.ctaLabel,
+ progress: challenge.progress,
+ avatars: [],
+ source: challenge.source ?? ChallengeSource.CUSTOM,
+ shareCode: challenge.shareCode ?? null,
+ challengeState: challenge.challengeState,
+ progressUnit: challenge.progressUnit,
+ targetValue: challenge.targetValue,
+ isCreator: challenge.isCreator,
+ };
+ })
+);
+
+export const selectOfficialChallengeCards = createSelector(
+ [selectOfficialChallengeList],
+ (challenges) =>
+ challenges.map((challenge) => {
+ const participants =
+ typeof challenge.participantsCount === 'number' ? challenge.participantsCount : 0;
+ return {
+ id: challenge.id,
+ title: challenge.title,
+ image: challenge.image ?? FALLBACK_CHALLENGE_IMAGE,
+ dateRange: buildDateRangeLabel(challenge),
+ participantsLabel: `${formatNumberWithSeparator(participants)} 人参与`,
+ status: deriveStatus(challenge),
+ isJoined: challenge.isJoined,
+ endAt: challenge.endAt,
+ periodLabel: challenge.periodLabel,
+ durationLabel: challenge.durationLabel,
+ requirementLabel: challenge.requirementLabel,
+ highlightTitle: challenge.highlightTitle,
+ highlightSubtitle: challenge.highlightSubtitle,
+ ctaLabel: challenge.ctaLabel,
+ progress: challenge.progress,
+ avatars: [],
+ source: challenge.source ?? ChallengeSource.SYSTEM,
+ shareCode: challenge.shareCode ?? null,
+ challengeState: challenge.challengeState,
+ progressUnit: challenge.progressUnit,
+ targetValue: challenge.targetValue,
+ isCreator: challenge.isCreator,
+ };
+ })
);
export const selectChallengeById = (id: string) =>
@@ -462,3 +656,23 @@ export const selectChallengeRankingLoadMoreStatus = (id: string) =>
export const selectChallengeRankingError = (id: string) =>
createSelector([selectChallengesState], (state) => state.rankingError[id]);
+
+export const selectCreateChallengeStatus = createSelector(
+ [selectChallengesState],
+ (state) => state.createStatus
+);
+
+export const selectCreateChallengeError = createSelector(
+ [selectChallengesState],
+ (state) => state.createError
+);
+
+export const selectJoinByCodeStatus = createSelector(
+ [selectChallengesState],
+ (state) => state.joinByCodeStatus
+);
+
+export const selectJoinByCodeError = createSelector(
+ [selectChallengesState],
+ (state) => state.joinByCodeError
+);
diff --git a/store/userSlice.ts b/store/userSlice.ts
index 9986111..b305a5a 100644
--- a/store/userSlice.ts
+++ b/store/userSlice.ts
@@ -5,65 +5,51 @@ import AsyncStorage from '@/utils/kvStore';
import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import dayjs from 'dayjs';
-// 预加载的用户数据存储
-let preloadedUserData: {
- token: string | null;
- profile: UserProfile;
- privacyAgreed: boolean;
- onboardingCompleted: boolean;
-} | null = null;
-
-// 预加载用户数据的函数
-export async function preloadUserData() {
+/**
+ * 同步加载用户数据(在 Redux store 初始化时立即执行)
+ * 使用 getItemSync 确保数据在 store 创建前就已加载
+ */
+function loadUserDataSync() {
try {
- const [profileStr, privacyAgreedStr, token, onboardingCompletedStr] = await Promise.all([
- AsyncStorage.getItem(STORAGE_KEYS.userProfile),
- AsyncStorage.getItem(STORAGE_KEYS.privacyAgreed),
- AsyncStorage.getItem(STORAGE_KEYS.authToken),
- AsyncStorage.getItem(STORAGE_KEYS.onboardingCompleted),
- ]);
+ const profileStr = AsyncStorage.getItemSync(STORAGE_KEYS.userProfile);
+ const token = AsyncStorage.getItemSync(STORAGE_KEYS.authToken);
+ const onboardingCompletedStr = AsyncStorage.getItemSync(STORAGE_KEYS.onboardingCompleted);
let profile: UserProfile = {
memberNumber: 0
};
+
if (profileStr) {
try {
profile = JSON.parse(profileStr) as UserProfile;
} catch {
- profile = {
- memberNumber: 0
- };
+ profile = { memberNumber: 0 };
}
}
- const privacyAgreed = privacyAgreedStr === 'true';
const onboardingCompleted = onboardingCompletedStr === 'true';
- // 如果有 token,需要设置到 API 客户端
+ // 如果有 token,需要异步设置到 API 客户端(但不阻塞初始化)
if (token) {
- await setAuthToken(token);
+ setAuthToken(token).catch(err => {
+ console.error('设置 auth token 失败:', err);
+ });
}
- preloadedUserData = { token, profile, privacyAgreed, onboardingCompleted };
- return preloadedUserData;
+ return { token, profile, onboardingCompleted };
} catch (error) {
- console.error('预加载用户数据失败:', error);
- preloadedUserData = {
+ console.error('同步加载用户数据失败:', error);
+ return {
token: null,
- profile: {
- memberNumber: 0
- },
- privacyAgreed: false,
+ profile: { memberNumber: 0 },
onboardingCompleted: false
};
- return preloadedUserData;
}
}
-// 获取预加载的用户数据
-function getPreloadedUserData() {
- return preloadedUserData || { token: null, profile: {}, privacyAgreed: false, onboardingCompleted: false };
-}
+// 在模块加载时立即同步加载用户数据
+const preloadedUserData = loadUserDataSync();
+
export type Gender = 'male' | 'female' | '';
@@ -120,22 +106,23 @@ export type UserState = {
export const DEFAULT_MEMBER_NAME = '朋友';
const getInitialState = (): UserState => {
- const preloaded = getPreloadedUserData();
+ // 使用模块加载时同步加载的数据
+ console.log('初始化 Redux state,使用预加载数据:', preloadedUserData);
+
return {
- token: preloaded.token,
+ token: preloadedUserData.token,
profile: {
name: DEFAULT_MEMBER_NAME,
isVip: false,
freeUsageCount: 3,
- memberNumber: 0,
maxUsageCount: 5,
- ...preloaded.profile, // 合并预加载的用户资料
+ ...preloadedUserData.profile, // 合并预加载的用户资料(包含 memberNumber)
},
loading: false,
error: null,
weightHistory: [],
activityHistory: [],
- onboardingCompleted: preloaded.onboardingCompleted, // 引导完成状态
+ onboardingCompleted: preloadedUserData.onboardingCompleted, // 引导完成状态
};
};
@@ -198,8 +185,11 @@ export const login = createAsyncThunk(
if (!token) throw new Error('登录响应缺少 token');
+ // 先持久化到本地存储
await AsyncStorage.setItem(STORAGE_KEYS.authToken, token);
await AsyncStorage.setItem(STORAGE_KEYS.userProfile, JSON.stringify(profile ?? {}));
+
+ // 再设置到 API 客户端(内部会同步更新 AsyncStorage)
await setAuthToken(token);
return { token, profile } as { token: string; profile: UserProfile };
@@ -222,12 +212,15 @@ export const setOnboardingCompleted = createAsyncThunk('user/setOnboardingComple
});
export const logout = createAsyncThunk('user/logout', async () => {
+ // 先清除 API 客户端的 token(内部会清除 AsyncStorage)
+ await setAuthToken(null);
+
+ // 再清除其他本地存储数据
await Promise.all([
- AsyncStorage.removeItem(STORAGE_KEYS.authToken),
AsyncStorage.removeItem(STORAGE_KEYS.userProfile),
AsyncStorage.removeItem(STORAGE_KEYS.privacyAgreed),
]);
- await setAuthToken(null);
+
return true;
});