diff --git a/README.md b/README.md index b989173..179a02c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,17 @@ 本项目面向宝妈群体,提供情绪价值与正念练习支持。 客户端采用 React Native + Expo,后端采用 Python + FastAPI,数据库 MySQL,任务调度 Celery,推送/定时等功能完整支持。 +# git提交 +git add . +git commit -m '备注' +git push origin + +获取最新分支 +git pull + +# 创建自己的分支 +git checkout -b 姓名拼写 + # 目录结构 /mindfulness diff --git a/client/app.json b/client/app.json index 6c4afd0..00ddbaf 100644 --- a/client/app.json +++ b/client/app.json @@ -1,11 +1,11 @@ { "expo": { - "name": "client", - "slug": "client", + "name": "Hey Mama", + "slug": "hey-mama", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/icon.png", - "scheme": "client", + "scheme": "heymama", "userInterfaceStyle": "automatic", "newArchEnabled": true, "splash": { @@ -15,7 +15,7 @@ }, "ios": { "supportsTablet": true, - "bundleIdentifier": "com.anonymous.client" + "bundleIdentifier": "com.heymama.app" }, "android": { "adaptiveIcon": { diff --git a/client/app/(app)/home.tsx b/client/app/(app)/home.tsx index 9a81838..c0fb964 100644 --- a/client/app/(app)/home.tsx +++ b/client/app/(app)/home.tsx @@ -1,8 +1,7 @@ -import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Pressable } from 'react-native'; +import { useEffect, useLayoutEffect, useMemo, useState, useCallback, useRef } from 'react'; +import { StyleSheet, View, Dimensions, Text, Pressable, PanResponder, Animated as RNAnimated } from 'react-native'; import { useTranslation } from 'react-i18next'; -import { useNavigation } from 'expo-router'; +import { useNavigation, useFocusEffect } from 'expo-router'; import Animated, { Easing, runOnJS, @@ -27,9 +26,10 @@ import ThemeModal from '@/components/home/ThemeModal'; import ThemeIcon from '@/assets/images/home/theme.svg'; import MyIcon from '@/assets/images/home/my.svg'; -import LikeOutlineIcon from '@/assets/images/home/like.svg'; import LikeFilledIcon from '@/assets/images/home/like_filled.svg'; -import HateIcon from '@/assets/images/home/hate.svg'; +import LikeIcon from '@/assets/images/icon/like_icon.svg'; + +const { height: SCREEN_HEIGHT } = Dimensions.get('window'); export default function HomeScreen() { const { t } = useTranslation(); @@ -44,19 +44,27 @@ export default function HomeScreen() { const item = useMemo(() => MOCK_CONTENT[index % MOCK_CONTENT.length], [index]); - useEffect(() => { - let cancelled = false; - (async () => { - const mode = await getThemeMode(); - const profile = await getUserProfile(); - if (cancelled) return; - setThemeModeState(mode); - setProfileName(profile.name); - })(); - return () => { - cancelled = true; - }; - }, []); + // 动画相关 Shared Values + const translateY = useSharedValue(0); + const opacity = useSharedValue(1); + const likeScale = useSharedValue(1); + + // 每次进入页面或页面获得焦点时刷新个人信息 + useFocusEffect( + useCallback(() => { + let cancelled = false; + (async () => { + const mode = await getThemeMode(); + const profile = await getUserProfile(); + if (cancelled) return; + setThemeModeState(mode); + setProfileName(profile.name); + })(); + return () => { + cancelled = true; + }; + }, []) + ); const backgroundColor = themeMode === 'color' ? '#F3D0E1' : '#F4D6C2'; @@ -83,60 +91,109 @@ export default function HomeScreen() { }); }, [backgroundColor, navigation, t]); - const likeScale = useSharedValue(1); - const hateScale = useSharedValue(1); + const textAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + opacity: opacity.value, + })); const likeAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: likeScale.value }], })); - const hateAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{ scale: hateScale.value }], - })); + // 切换到下一条文案的统一动画逻辑 + const triggerNextContent = useCallback(() => { + if (busy) return; + setBusy(true); - function playLikeAnimationAndThen(next: () => void) { + // 1. 当前文案向上移动并消失 + translateY.value = withTiming(-40, { duration: 300, easing: Easing.out(Easing.quad) }); + opacity.value = withTiming(0, { duration: 300 }, (finished) => { + if (finished) { + // 2. 切换数据索引 + runOnJS(setIndex)(index + 1); + runOnJS(setLikeFilled)(false); + + // 3. 准备下一条文案:先瞬移到下方 40pt + translateY.value = 40; + + // 4. 下一条文案向上移动到原位并显现 + translateY.value = withTiming(0, { duration: 400, easing: Easing.out(Easing.back(1)) }); + opacity.value = withTiming(1, { duration: 400 }, (finished) => { + if (finished) { + runOnJS(setBusy)(false); + } + }); + } + }); + }, [busy, index, translateY, opacity]); + + const lastTapRef = useRef(0); + + // 使用 Ref 解决 PanResponder 闭包陷阱,确保手势回调能拿到最新的 state 和 function + const handlersRef = useRef({ onPressLike, triggerNextContent }); + useEffect(() => { + handlersRef.current = { onPressLike, triggerNextContent }; + }, [onPressLike, triggerNextContent]); + + // 使用系统自带的 PanResponder 代替第三方手势库 + const panResponder = useRef( + PanResponder.create({ + onStartShouldSetPanResponder: () => true, // 允许开始捕获 + onMoveShouldSetPanResponder: (_, gestureState) => { + // 只有当垂直滑动距离大于 20 时才接管位移手势 + return Math.abs(gestureState.dy) > 20; + }, + onPanResponderRelease: (_, gestureState) => { + const now = Date.now(); + const DOUBLE_TAP_DELAY = 300; + + // 1. 双击逻辑判定 + if (now - lastTapRef.current < DOUBLE_TAP_DELAY) { + // 判定为双击 + if (Math.abs(gestureState.dx) < 10 && Math.abs(gestureState.dy) < 10) { + runOnJS(handlersRef.current.onPressLike)(); + lastTapRef.current = 0; // 重置 + return; + } + } + lastTapRef.current = now; + + // 2. 上滑逻辑判定 + if (gestureState.dy < -50) { // 上滑超过 50pt + runOnJS(handlersRef.current.triggerNextContent)(); + } + }, + }) + ).current; + + async function onPressLike() { + if (busy) return; setLikeFilled(true); + + // 1. 获取当前日期 + const now = new Date(); + const dateStr = `${now.getFullYear()}.${String(now.getMonth() + 1).padStart(2, '0')}.${String(now.getDate()).padStart(2, '0')}`; + + // 2. 保存到收藏夹,包含当前背景信息 + await addFavorite({ + id: item.id, + date: dateStr, + themeMode: themeMode, + background: backgroundColor, // 目前存储的是颜色值 + }); + + // 3. 爱心缩放动画 likeScale.value = withSequence( - withTiming(0.92, { duration: 90, easing: Easing.out(Easing.cubic) }), - withTiming(1.14, { duration: 120, easing: Easing.out(Easing.cubic) }), - withTiming(1, { duration: 140, easing: Easing.out(Easing.cubic) }, (finished) => { - if (finished) runOnJS(next)(); + withTiming(0.8, { duration: 100 }), + withTiming(1.2, { duration: 150 }), + withTiming(1, { duration: 100 }, (finished) => { + if (finished) { + runOnJS(triggerNextContent)(); + } }) ); } - function playHateAnimationAndThen(next: () => void) { - hateScale.value = withSequence( - withTiming(0.92, { duration: 90, easing: Easing.out(Easing.cubic) }), - withTiming(1.06, { duration: 110, easing: Easing.out(Easing.cubic) }), - withTiming(1, { duration: 120, easing: Easing.out(Easing.cubic) }, (finished) => { - if (finished) runOnJS(next)(); - }) - ); - } - - function onPressLike() { - if (busy) return; - setBusy(true); - playLikeAnimationAndThen(async () => { - await setReaction(item.id, 'like'); - await addFavorite(item.id); - setIndex((i) => i + 1); - setTimeout(() => setLikeFilled(false), 220); - setBusy(false); - }); - } - - function onPressHate() { - if (busy) return; - setBusy(true); - playHateAnimationAndThen(async () => { - await setReaction(item.id, 'dislike'); - setIndex((i) => i + 1); - setBusy(false); - }); - } - async function onSelectTheme(next: ThemeMode) { setThemeModeState(next); await setThemeMode(next); @@ -144,40 +201,24 @@ export default function HomeScreen() { } return ( - - - {item.text} - + + + {item.text} + - - (hateScale.value = withTiming(0.92, { duration: 80 }))} - onPressOut={() => (hateScale.value = withTiming(1, { duration: 120 }))} - accessibilityRole="button" - accessibilityLabel={t('home.dislike')} - hitSlop={10} - style={styles.reactionInner} - > - - - - (likeScale.value = withTiming(0.92, { duration: 80 }))} - onPressOut={() => (likeScale.value = withTiming(1, { duration: 120 }))} accessibilityRole="button" accessibilityLabel={t('home.like')} - hitSlop={10} + hitSlop={20} style={styles.reactionInner} > {likeFilled ? ( - + ) : ( - + )} @@ -220,7 +261,7 @@ const styles = StyleSheet.create({ flex: 1, padding: 20, justifyContent: 'center', - gap: 16, + alignItems: 'center', }, headerRight: { flexDirection: 'row', @@ -236,37 +277,31 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, card: { - borderRadius: 16, - padding: 20, - backgroundColor: 'rgba(255,255,255,0.65)', - borderWidth: StyleSheet.hairlineWidth, - borderColor: '#E5E7EB', + paddingHorizontal: 30, + alignItems: 'center', + justifyContent: 'center', }, text: { - fontSize: 20, - lineHeight: 28, + fontSize: 22, + lineHeight: 32, color: '#5E2A28', fontWeight: '700', + textAlign: 'center', }, actions: { + position: 'absolute', + bottom: SCREEN_HEIGHT * 0.16, + left: 0, + right: 0, flexDirection: 'row', - gap: 12, justifyContent: 'center', }, reactionButton: { - width: 58, - height: 58, - borderRadius: 29, - backgroundColor: 'rgba(255,255,255,0.75)', alignItems: 'center', justifyContent: 'center', }, reactionInner: { - width: 58, - height: 58, - borderRadius: 29, alignItems: 'center', justifyContent: 'center', }, }); - diff --git a/client/app/(onboarding)/onboarding.tsx b/client/app/(onboarding)/onboarding.tsx index e95d54a..df0ef33 100644 --- a/client/app/(onboarding)/onboarding.tsx +++ b/client/app/(onboarding)/onboarding.tsx @@ -1,66 +1,147 @@ import { useState } from 'react'; import { useRouter } from 'expo-router'; +import * as Notifications from 'expo-notifications'; import { OnboardingLayout } from '@/components/onboarding/OnboardingLayout'; import { NameInputStep } from '@/components/onboarding/NameInputStep'; -import { IntentSelectionStep } from '@/components/onboarding/IntentSelectionStep'; -import { setOnboardingCompleted, setUserProfile } from '@/src/storage/appStorage'; +import { SelectionStep } from '@/components/onboarding/SelectionStep'; +import { ReminderStep } from '@/components/onboarding/ReminderStep'; +import { setOnboardingCompleted, setUserProfile, setDailyReminderSettings } from '@/src/storage/appStorage'; + +const STEPS = [ + { id: 'name', type: 'name', title: '我可以怎么称呼你?' }, + { + id: 'status', + type: 'selection', + title: '媽媽的狀態?', + options: [ + { id: 'pregnant', label: '懷孕中/準備成為媽媽' }, + { id: 'has_kids', label: '已經有孩子' }, + { id: 'no_fill', label: '不想填寫' }, + ] + }, + { + id: 'emotion', + type: 'selection', + title: '當下情緒狀態?', + options: [ + { id: 'happy', label: '愉悅、滿足' }, + { id: 'calm', label: '平靜、安穩' }, + { id: 'stressed', label: '被壓得有點喘不過氣' }, + { id: 'low', label: '情緒低落' }, + ] + }, + { + id: 'influence', + type: 'selection', + title: '是什麼影響了你最近的感受?', + options: [ + { id: 'family', label: '家庭與孩子' }, + { id: 'work', label: '工作或學習' }, + { id: 'relationship', label: '親密關係' }, + { id: 'friends', label: '朋友與人際' }, + { id: 'health', label: '身心健康' }, + ] + }, + { + id: 'support', + type: 'selection', + title: '最需要什麼支持?', + options: [ + { id: 'emotional', label: '情緒支持' }, + { id: 'parenting', label: '育兒壓力' }, + { id: 'self_worth', label: '自我價值' }, + { id: 'anxiety', label: '焦慮舒緩' }, + { id: 'balance', label: '休息與平衡' }, + ] + }, + { id: 'reminder', type: 'reminder', title: '你需要每天几次提醒?' }, +]; export default function OnboardingScreen() { const router = useRouter(); - const [step, setStep] = useState(0); + const [stepIndex, setStepIndex] = useState(0); const [name, setName] = useState(''); - const [intents, setIntents] = useState([]); + const [selections, setSelections] = useState>({}); + const [reminderTimes, setReminderTimes] = useState(3); + + const currentStep = STEPS[stepIndex]; async function onFinish() { - // Save whatever input we have - await setUserProfile({ name, intents }); + // 请求推送权限 + const { status } = await Notifications.requestPermissionsAsync(); + const pushEnabled = status === 'granted'; + + await setUserProfile({ + name, + intents: Object.values(selections).flat() + }); + await setDailyReminderSettings({ + timesPerDay: reminderTimes, + pushEnabled: pushEnabled + }); await setOnboardingCompleted(true); - router.replace('/(onboarding)/push-prompt'); + router.replace('/(app)/home'); } - async function onNext() { - if (step === 0) { - setStep(1); + const onNext = () => { + if (stepIndex < STEPS.length - 1) { + setStepIndex(stepIndex + 1); } else { - await onFinish(); + onFinish(); } - } + }; - async function onSkip() { - // Skip logic: move to next step regardless of input - if (step === 0) { - setStep(1); - } else { - await onFinish(); + const onBack = () => { + if (stepIndex > 0) { + setStepIndex(stepIndex - 1); } - } + }; - // Step 0: Next button requires input - // Step 1: Next button always enabled (can proceed with empty selection) - const nextEnabled = step === 0 ? name.trim().length > 0 : true; + const onSkip = () => { + router.replace('/(app)/home'); + }; + + const handleToggleSelection = (id: string) => { + setSelections(prev => { + const currentIds = prev[currentStep.id] || []; + const nextIds = currentIds.includes(id) + ? currentIds.filter(i => i !== id) + : [...currentIds, id]; + return { ...prev, [currentStep.id]: nextIds }; + }); + }; return ( 0} > - {step === 0 ? ( + {currentStep.type === 'name' && ( 0 ? onNext : undefined} + onNext={onNext} /> - ) : ( - { - setIntents(prev => - prev.includes(id) - ? prev.filter(i => i !== id) - : [...prev, id] - ); - }} + )} + + {currentStep.type === 'selection' && ( + + )} + + {currentStep.type === 'reminder' && ( + )} diff --git a/client/app/(splash)/splash.tsx b/client/app/(splash)/splash.tsx index b59e3a6..dd2190b 100644 --- a/client/app/(splash)/splash.tsx +++ b/client/app/(splash)/splash.tsx @@ -1,12 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, Image, StyleSheet, TouchableOpacity, Dimensions, Platform, Alert } from 'react-native'; -import { LinearGradient } from 'expo-linear-gradient'; +import { View, Text, StyleSheet, TouchableOpacity, Dimensions, Platform, Alert, Image } from 'react-native'; import { useRouter } from 'expo-router'; import * as WebBrowser from 'expo-web-browser'; import { useTranslation } from 'react-i18next'; import { SafeAreaView } from 'react-native-safe-area-context'; import { setConsentAccepted, getConsentAccepted } from '../../src/storage/appStorage'; +// 导入 SVG 组件 +import FlowersBg from '../../assets/images/index/flowers_endbg.svg'; +import WelcomeBtn from '../../assets/images/index/welcome_btn.svg'; + const { width, height } = Dimensions.get('window'); export default function SplashScreen() { @@ -22,18 +25,15 @@ export default function SplashScreen() { const accepted = await getConsentAccepted(); setShowConsent(!accepted); if (accepted) { - // 如果已经同意过,直接跳转到首页分发 - // 注意:这里需要与 app/index.tsx 配合,如果 app/index.tsx 已经判断了 consent=true 不会跳过来, - // 那么这里其实是防守。 - // 但如果用户是通过 deep link 或其他方式强制进入 splash,这个逻辑会把他们送走。 - router.replace('/'); + router.replace('/'); } }; const handleAgree = async () => { await setConsentAccepted(true); setShowConsent(false); - router.replace('/'); + // 跳转到 onboarding 流程 + router.replace('/(onboarding)/onboarding'); }; const openLink = async (url: string) => { @@ -44,31 +44,40 @@ export default function SplashScreen() { } }; + const bgDecorationTop = 363; + const bgDecorationHeight = height * 0.6; + const contentTop = bgDecorationTop + (bgDecorationHeight * 0.25); + return ( - + {/* 中间的背景装饰 SVG (现在放在上面,作为上层) */} + + + - - {t('consent.title')} - {t('consent.subtitle')} + {/* 顶部的花图片 (现在放在下面,作为下层) */} + + + + + {/* 文案内容 */} + + + You Are Perfect.{"\n"} + Everything{"\n"} + Will Be Better. + {showConsent && ( <> - - - {t('consent.agree')} - + + @@ -90,74 +99,57 @@ export default function SplashScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#FFF9F0', // 假设的背景色,根据图片调整 + backgroundColor: '#F5D3B5', // 匹配 Figma 背景色 alignItems: 'center', }, - image: { - width: width * 0.8, - height: height * 0.5, - marginTop: height * 0.1, + topImageContainer: { + marginTop: 60, + zIndex: 1, // 降低层级 + }, + topImage: { + width: 308, + height: 354, + }, + bgDecorationContainer: { + position: 'absolute', + left: -3, + zIndex: 2, // 提高层级,使其覆盖在图片之上 }, contentContainer: { alignItems: 'center', - marginTop: 20, - paddingHorizontal: 30, + zIndex: 3, }, - title: { - fontSize: 24, - fontWeight: 'bold', - color: '#4A3427', // 深褐色 - marginBottom: 10, + titleText: { + fontSize: 30, + lineHeight: 42, + color: '#772F00', // 匹配 Figma 文字颜色 textAlign: 'center', - fontFamily: Platform.OS === 'ios' ? 'Georgia' : 'serif', // 尝试匹配衬线体 - }, - subtitle: { - fontSize: 18, - fontWeight: 'bold', - color: '#4A3427', // 深褐色 - textAlign: 'center', - lineHeight: 24, - fontFamily: Platform.OS === 'ios' ? 'Georgia' : 'serif', + fontWeight: '600', + fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif', }, bottomContainer: { position: 'absolute', - bottom: 0, + bottom: 60, width: '100%', alignItems: 'center', - paddingBottom: 20, + zIndex: 4, }, - button: { - width: width * 0.8, - paddingVertical: 16, - borderRadius: 30, - alignItems: 'center', - justifyContent: 'center', - marginBottom: 20, - shadowColor: '#F69F7B', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 5, - }, - buttonText: { - color: '#FFF', - fontSize: 18, - fontWeight: '600', + buttonWrapper: { + marginBottom: 40, }, linksContainer: { flexDirection: 'row', alignItems: 'center', - marginBottom: 10, }, linkText: { fontSize: 12, - color: '#999', + color: 'rgba(119, 47, 0, 0.5)', // 使用半透明的文字颜色 textDecorationLine: 'underline', }, divider: { width: 1, height: 12, - backgroundColor: '#CCC', + backgroundColor: 'rgba(119, 47, 0, 0.2)', marginHorizontal: 15, }, }); diff --git a/client/app/_layout.tsx b/client/app/_layout.tsx index da817ae..b5f5188 100644 --- a/client/app/_layout.tsx +++ b/client/app/_layout.tsx @@ -3,12 +3,22 @@ import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; +import * as Notifications from 'expo-notifications'; import { useEffect, useState } from 'react'; import 'react-native-reanimated'; import { useColorScheme } from '@/components/useColorScheme'; import { initI18n } from '@/src/i18n'; +// 配置通知处理方式(即使不发送也建议配置,以确保权限接口正常) +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: false, + shouldSetBadge: false, + }), +}); + export { // Catch any errors thrown by the Layout component. ErrorBoundary, diff --git a/client/app/index.tsx b/client/app/index.tsx index cf3d937..e9a5ae3 100644 --- a/client/app/index.tsx +++ b/client/app/index.tsx @@ -1,8 +1,9 @@ import { useEffect } from 'react'; import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { useRouter } from 'expo-router'; +import AsyncStorage from '@react-native-async-storage/async-storage'; -import { getOnboardingCompleted, getConsentAccepted } from '@/src/storage/appStorage'; +import { getOnboardingCompleted, getConsentAccepted, setOnboardingCompleted, setConsentAccepted } from '@/src/storage/appStorage'; /** * 启动分发:根据 consent 和 onboarding 状态跳转 @@ -13,8 +14,9 @@ export default function Index() { useEffect(() => { let cancelled = false; (async () => { - // 临时重置引导页状态(开发调试用) - await setOnboardingCompleted(false); + // 【完全重置】:清除本地存储的所有数据(收藏、设置、引导状态等) + await AsyncStorage.clear(); + console.log('AsyncStorage has been cleared.'); // 1. 检查是否同意协议 const consentAccepted = await getConsentAccepted(); diff --git a/client/assets/images/home/Profile/Default_avatar.png b/client/assets/images/home/Profile/Default_avatar.png deleted file mode 100644 index d8aa749..0000000 Binary files a/client/assets/images/home/Profile/Default_avatar.png and /dev/null differ diff --git a/client/assets/images/home/Profile/Default_avatar.svg b/client/assets/images/home/Profile/Default_avatar.svg new file mode 100644 index 0000000..2870ae0 --- /dev/null +++ b/client/assets/images/home/Profile/Default_avatar.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/assets/images/home/Profile/widget/Widget1_en.png b/client/assets/images/home/Profile/widget/Widget1_en.png new file mode 100644 index 0000000..45e118a Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget1_en.png differ diff --git a/client/assets/images/home/Profile/widget/Widget1_tw.png b/client/assets/images/home/Profile/widget/Widget1_tw.png new file mode 100644 index 0000000..96b8e60 Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget1_tw.png differ diff --git a/client/assets/images/home/Profile/widget/Widget2_en.png b/client/assets/images/home/Profile/widget/Widget2_en.png new file mode 100644 index 0000000..c04db52 Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget2_en.png differ diff --git a/client/assets/images/home/Profile/widget/Widget2_tw.png b/client/assets/images/home/Profile/widget/Widget2_tw.png new file mode 100644 index 0000000..0cb5808 Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget2_tw.png differ diff --git a/client/assets/images/home/Profile/widget/Widget_description1_en.png b/client/assets/images/home/Profile/widget/Widget_description1_en.png new file mode 100644 index 0000000..7ee26ca Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget_description1_en.png differ diff --git a/client/assets/images/home/Profile/widget/Widget_description1_tw.png b/client/assets/images/home/Profile/widget/Widget_description1_tw.png new file mode 100644 index 0000000..acd6c21 Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget_description1_tw.png differ diff --git a/client/assets/images/home/Profile/widget/Widget_description2_en.png b/client/assets/images/home/Profile/widget/Widget_description2_en.png new file mode 100644 index 0000000..64a1625 Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget_description2_en.png differ diff --git a/client/assets/images/home/Profile/widget/Widget_description2_tw.png b/client/assets/images/home/Profile/widget/Widget_description2_tw.png new file mode 100644 index 0000000..31f48d7 Binary files /dev/null and b/client/assets/images/home/Profile/widget/Widget_description2_tw.png differ diff --git a/client/assets/images/home/Profile/widget/question_icon.svg b/client/assets/images/home/Profile/widget/question_icon.svg new file mode 100644 index 0000000..d262eb1 --- /dev/null +++ b/client/assets/images/home/Profile/widget/question_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/Push_icon.svg b/client/assets/images/icon/Push_icon.svg new file mode 100644 index 0000000..cd08c3f --- /dev/null +++ b/client/assets/images/icon/Push_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/Terms_icon.svg b/client/assets/images/icon/Terms_icon.svg new file mode 100644 index 0000000..3a7c24e --- /dev/null +++ b/client/assets/images/icon/Terms_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/add_icon.svg b/client/assets/images/icon/add_icon.svg new file mode 100644 index 0000000..0294ab7 --- /dev/null +++ b/client/assets/images/icon/add_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/assets/images/icon/back_icon.png b/client/assets/images/icon/back_icon.png new file mode 100644 index 0000000..06d5a4a Binary files /dev/null and b/client/assets/images/icon/back_icon.png differ diff --git a/client/assets/images/icon/btn_Notclicked.svg b/client/assets/images/icon/btn_Notclicked.svg new file mode 100644 index 0000000..a421323 --- /dev/null +++ b/client/assets/images/icon/btn_Notclicked.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/assets/images/icon/btn_clicked.svg b/client/assets/images/icon/btn_clicked.svg new file mode 100644 index 0000000..8d229e0 --- /dev/null +++ b/client/assets/images/icon/btn_clicked.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/assets/images/icon/enter_Light_icon.svg b/client/assets/images/icon/enter_Light_icon.svg new file mode 100644 index 0000000..fbdae4d --- /dev/null +++ b/client/assets/images/icon/enter_Light_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/language_icon.svg b/client/assets/images/icon/language_icon.svg new file mode 100644 index 0000000..3691630 --- /dev/null +++ b/client/assets/images/icon/language_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/like_icon.svg b/client/assets/images/icon/like_icon.svg new file mode 100644 index 0000000..a52fe32 --- /dev/null +++ b/client/assets/images/icon/like_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/mylike_icon.svg b/client/assets/images/icon/mylike_icon.svg new file mode 100644 index 0000000..59ef6ce --- /dev/null +++ b/client/assets/images/icon/mylike_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/next_icon.svg b/client/assets/images/icon/next_icon.svg new file mode 100644 index 0000000..863a77c --- /dev/null +++ b/client/assets/images/icon/next_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/privacy_icon.svg b/client/assets/images/icon/privacy_icon.svg new file mode 100644 index 0000000..08db4a1 --- /dev/null +++ b/client/assets/images/icon/privacy_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/reduce_icon.svg b/client/assets/images/icon/reduce_icon.svg new file mode 100644 index 0000000..3ac444d --- /dev/null +++ b/client/assets/images/icon/reduce_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/assets/images/icon/selected_icon.svg b/client/assets/images/icon/selected_icon.svg new file mode 100644 index 0000000..2a1607f --- /dev/null +++ b/client/assets/images/icon/selected_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/images/icon/skip_icon.png b/client/assets/images/icon/skip_icon.png new file mode 100644 index 0000000..5379ca3 Binary files /dev/null and b/client/assets/images/icon/skip_icon.png differ diff --git a/client/assets/images/icon/weight_icon.svg b/client/assets/images/icon/weight_icon.svg new file mode 100644 index 0000000..e9184b5 --- /dev/null +++ b/client/assets/images/icon/weight_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/assets/images/theme/theme_color.png b/client/assets/images/theme/theme_color.png new file mode 100644 index 0000000..7020149 Binary files /dev/null and b/client/assets/images/theme/theme_color.png differ diff --git a/client/assets/images/theme/theme_landscape.png b/client/assets/images/theme/theme_landscape.png new file mode 100644 index 0000000..f987049 Binary files /dev/null and b/client/assets/images/theme/theme_landscape.png differ diff --git a/client/babel.config.js b/client/babel.config.js deleted file mode 100644 index d413afc..0000000 --- a/client/babel.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = function (api) { - api.cache(true); - return { - presets: ['babel-preset-expo'], - plugins: [ - ['@babel/plugin-transform-react-jsx', { runtime: 'automatic' }] - ] - }; -}; diff --git a/client/components/home/.ProfileModal.tsx.swp b/client/components/home/.ProfileModal.tsx.swp new file mode 100644 index 0000000..9e07e56 Binary files /dev/null and b/client/components/home/.ProfileModal.tsx.swp differ diff --git a/client/components/home/ProfileModal.tsx b/client/components/home/ProfileModal.tsx index dc32fd6..e657e3f 100644 --- a/client/components/home/ProfileModal.tsx +++ b/client/components/home/ProfileModal.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { Alert, FlatList, Image, Pressable, StyleSheet, Text, View } from 'react-native'; +import React, { useEffect, useMemo, useState, useRef, useCallback } from 'react'; +import { Alert, FlatList, Image, Pressable, StyleSheet, Text, View, Platform, Dimensions } from 'react-native'; import { useTranslation } from 'react-i18next'; import { LinearGradient } from 'expo-linear-gradient'; import { Switch } from 'react-native'; @@ -11,6 +11,7 @@ import Animated, { SlideInRight, SlideOutLeft, SlideOutRight, + Layout, } from 'react-native-reanimated'; import SheetModal from '@/components/ui/SheetModal'; @@ -20,16 +21,25 @@ import { getDailyReminderSettings, getFavorites, setDailyReminderSettings, + removeFavorite, + getUserProfile, type DailyReminderSettings, + type FavoriteItem, } from '@/src/storage/appStorage'; -import AvatarIcon from '@/assets/images/home/Profile/Default_avatar.png'; -import MyLikeIcon from '@/assets/images/home/Profile/mylike.svg'; -import SmallComponentIcon from '@/assets/images/home/Profile/small_component.svg'; -import RemindIcon from '@/assets/images/home/Profile/remind.svg'; -import PrivacyIcon from '@/assets/images/home/Profile/privacy Policy.svg'; -import TermsIcon from '@/assets/images/home/Profile/terms_of_use.svg'; -import LanguageIcon from '@/assets/images/home/Profile/language.svg'; +import AvatarIcon from '@/assets/images/home/Profile/Default_avatar.svg'; +import MyLikeIcon from '@/assets/images/icon/mylike_icon.svg'; +import SmallComponentIcon from '@/assets/images/icon/weight_icon.svg'; +import RemindIcon from '@/assets/images/icon/Push_icon.svg'; +import PrivacyIcon from '@/assets/images/icon/privacy_icon.svg'; +import TermsIcon from '@/assets/images/icon/Terms_icon.svg'; +import LanguageIcon from '@/assets/images/icon/language_icon.svg'; +import SelectedIcon from '@/assets/images/icon/selected_icon.svg'; +import QuestionIcon from '@/assets/images/home/Profile/widget/question_icon.svg'; +import * as Notifications from 'expo-notifications'; +import { changeLanguage } from '@/src/i18n'; + +const { width } = Dimensions.get('window'); type Props = { visible: boolean; @@ -37,24 +47,38 @@ type Props = { onClose: () => void; }; -type Page = 'root' | 'favorites' | 'dailyReminder' | 'widget'; +type Page = 'root' | 'favorites' | 'dailyReminder' | 'widget' | 'language' | 'widgetHowTo'; type NavDirection = 'forward' | 'back'; -export default function ProfileModal({ visible, name, onClose }: Props) { +export default function ProfileModal({ visible, name: propName, onClose }: Props) { const { t } = useTranslation(); const [page, setPage] = useState('root'); const [navDirection, setNavDirection] = useState('forward'); + const [currentName, setCurrentName] = useState(propName); const isRoot = page === 'root'; - // 关闭弹窗时重置为首页,避免下次打开停留在二级页 + // 当弹窗打开时,尝试从存储中获取最新的昵称,确保与 onboarding 同步 useEffect(() => { - if (!visible) { + if (visible) { + getUserProfile().then(profile => { + if (profile.name) { + setCurrentName(profile.name); + } + }); + } else { setNavDirection('back'); setPage('root'); } }, [visible]); + // 同步外部 propName 的变化 + useEffect(() => { + if (propName) { + setCurrentName(propName); + } + }, [propName]); + function go(next: Page, direction: NavDirection) { setNavDirection(direction); setPage(next); @@ -79,6 +103,8 @@ export default function ProfileModal({ visible, name, onClose }: Props) { if (page === 'favorites') return t('profile.favorites'); if (page === 'dailyReminder') return t('dailyReminder.title'); if (page === 'widget') return t('profile.widget'); + if (page === 'language') return t('profile.language'); + if (page === 'widgetHowTo') return t('widget.howToTitle'); return t('profile.title'); }, [page, t]); @@ -107,42 +133,46 @@ export default function ProfileModal({ visible, name, onClose }: Props) { }, [navDirection]); return ( - - {!isRoot && ( - go('root', 'back')} - hitSlop={8} - accessibilityRole="button" - accessibilityLabel={t('common.back')} - style={styles.backRow} + + + - ‹ {t('common.back')} - - )} - - - + {page === 'root' ? ( go('favorites', 'forward')} onOpenWidget={() => go('widget', 'forward')} onOpenDailyReminder={() => go('dailyReminder', 'forward')} + onOpenLanguage={() => go('language', 'forward')} /> ) : page === 'favorites' ? ( - + ) : page === 'dailyReminder' ? ( go('root', 'back')} /> + ) : page === 'language' ? ( + + ) : page === 'widgetHowTo' ? ( + ) : ( - + go('widgetHowTo', 'forward')} /> )} + - + ); } @@ -156,17 +186,19 @@ function RootPage({ onOpenFavorites, onOpenWidget, onOpenDailyReminder, + onOpenLanguage, }: { name?: string; onOpenFavorites: () => void; onOpenWidget: () => void; onOpenDailyReminder: () => void; + onOpenLanguage: () => void; }) { const { t } = useTranslation(); return ( <> - + {name || 'Hali'} @@ -198,48 +230,79 @@ function RootPage({ } title={t('profile.language')} - onPress={() => toastTodo(t)} + onPress={onOpenLanguage} /> ); } -function FavoritesPage({ visible }: { visible: boolean }) { +function FavoritesPage({ visible, page }: { visible: boolean; page: Page }) { const { t } = useTranslation(); - const [ids, setIds] = useState([]); + const [favorites, setFavorites] = useState<(FavoriteItem & { text: string })[]>([]); - // 每次进入该页刷新一次,确保展示最新“喜欢” useEffect(() => { if (!visible) return; - let cancelled = false; - (async () => { - const list = await getFavorites(); - if (!cancelled) setIds(list); - })(); - return () => { - cancelled = true; - }; - }, [visible]); + refreshFavorites(); + }, [visible, page]); // 当页面切换回 favorites 时也刷新一次 - const items = useMemo(() => { - const map = new Map(MOCK_CONTENT.map((c) => [c.id, c])); - return ids.map((id) => map.get(id)).filter(Boolean) as { id: string; text: string }[]; - }, [ids]); + async function refreshFavorites() { + const storedFavs = await getFavorites(); + const map = new Map(MOCK_CONTENT.map((c) => [c.id, c.text])); + + const list = storedFavs + .map((fav) => ({ + ...fav, + text: map.get(fav.id) || '' + })) + .filter(item => item.text !== ''); + + setFavorites(list); + } + + async function handleRemove(id: string) { + // 1. 调用存储层移除收藏 + await removeFavorite(id); + // 2. 更新本地状态 + setFavorites(prev => prev.filter(item => item.id !== id)); + } return ( - {items.length === 0 ? ( + {favorites.length === 0 ? ( {t('favorites.empty')} ) : ( it.id} contentContainerStyle={styles.favList} + showsVerticalScrollIndicator={false} renderItem={({ item }) => ( - - {item.text} - + + + {item.date} + + + + {item.text} + handleRemove(item.id)} + style={styles.favRemoveBtn} + hitSlop={10} + > + + + + + )} /> )} @@ -252,16 +315,23 @@ function DailyReminderPage({ visible, onDone }: { visible: boolean; onDone: () = const [loading, setLoading] = useState(false); const [timesPerDay, setTimesPerDay] = useState(3); const [pushEnabled, setPushEnabled] = useState(false); + const [hasSystemPermission, setHasSystemPermission] = useState(null); useEffect(() => { let cancelled = false; if (!visible) return; setLoading(true); (async () => { + // 1. 获取本地存储设置 const s = await getDailyReminderSettings(); + + // 【测试模式】:强制模拟无权限状态 + const granted = false; + if (cancelled) return; setTimesPerDay(s.timesPerDay); - setPushEnabled(s.pushEnabled); + setPushEnabled(granted); + setHasSystemPermission(granted); setLoading(false); })(); return () => { @@ -269,6 +339,40 @@ function DailyReminderPage({ visible, onDone }: { visible: boolean; onDone: () = }; }, [visible]); + const handleTogglePush = async (value: boolean) => { + if (value) { + // 获取当前权限状态 + const settings = await Notifications.getPermissionsAsync(); + + // 如果已经拒绝,弹窗提示 + if (settings.status === 'denied') { + Alert.alert( + t('common.notice'), + "系统权限已被拒绝,请前往手机设置开启通知。" + ); + setPushEnabled(false); + return; + } + + // 尝试申请权限 + const { status } = await Notifications.requestPermissionsAsync(); + + // 调试:打印状态 + console.log('Push Permission Status:', status); + + if (status === 'granted') { + setPushEnabled(true); + setHasSystemPermission(true); + } else { + setPushEnabled(false); + setHasSystemPermission(false); + Alert.alert(t('common.notice'), t('dailyReminder.permissionDenied')); + } + } else { + setPushEnabled(false); + } + }; + function clamp(next: number) { return Math.min(10, Math.max(1, next)); } @@ -311,15 +415,24 @@ function DailyReminderPage({ visible, onDone }: { visible: boolean; onDone: () = - - - - + {!hasSystemPermission && ( + + + + + + {t('dailyReminder.pushLabel')} + + + - {t('dailyReminder.pushLabel')} - - + )} void }) { + const { t, i18n } = useTranslation(); + const currentLang = i18n.language; + + // 根据语言选择图片 + const widget1 = currentLang === 'en' + ? require('@/assets/images/home/Profile/widget/Widget1_en.png') + : require('@/assets/images/home/Profile/widget/Widget1_tw.png'); + + const widget2 = currentLang === 'en' + ? require('@/assets/images/home/Profile/widget/Widget2_en.png') + : require('@/assets/images/home/Profile/widget/Widget2_tw.png'); + return ( - - - - - {t('widget.previewDate')} - - - 13:45 - - - {t('widget.lockScreen')} - + + + - - - - {t('widget.previewQuote')} - - - {t('widget.homeScreen')} - + + + + {t('widget.lockScreen')} + + + + + {t('widget.homeScreen')} + + + ); +} - {t('settings.widgetDesc')} +function WidgetHowToPage() { + const { t, i18n } = useTranslation(); + const currentLang = i18n.language; + const flatListRef = useRef(null); + const [activeIndex, setActiveIndex] = useState(0); + const [isManual, setIsManual] = useState(false); + const timerRef = useRef(null); + + const images = currentLang === 'en' ? [ + { id: '1', src: require('@/assets/images/home/Profile/widget/Widget_description1_en.png'), desc: t('widget.howToDesc1') }, + { id: '2', src: require('@/assets/images/home/Profile/widget/Widget_description2_en.png'), desc: t('widget.howToDesc2') }, + ] : [ + { id: '1', src: require('@/assets/images/home/Profile/widget/Widget_description1_tw.png'), desc: t('widget.howToDesc1') }, + { id: '2', src: require('@/assets/images/home/Profile/widget/Widget_description2_tw.png'), desc: t('widget.howToDesc2') }, + ]; + + const startAutoPlay = useCallback(() => { + if (timerRef.current) clearInterval(timerRef.current); + timerRef.current = setInterval(() => { + if (!isManual) { + const nextIndex = (activeIndex + 1) % images.length; + flatListRef.current?.scrollToIndex({ index: nextIndex, animated: true }); + setActiveIndex(nextIndex); + } + }, 3000); + }, [activeIndex, isManual, images.length]); + + useEffect(() => { + startAutoPlay(); + return () => { + if (timerRef.current) clearInterval(timerRef.current); + }; + }, [startAutoPlay]); + + const onScroll = (event: any) => { + const x = event.nativeEvent.contentOffset.x; + const index = Math.round(x / (width - 32)); + if (index !== activeIndex) { + setActiveIndex(index); + } + }; + + const onScrollBeginDrag = () => { + setIsManual(true); + if (timerRef.current) clearInterval(timerRef.current); + }; + + return ( + + item.id} + horizontal + pagingEnabled + showsHorizontalScrollIndicator={false} + onScroll={onScroll} + onScrollBeginDrag={onScrollBeginDrag} + scrollEventThrottle={16} + renderItem={({ item }) => ( + + + {item.desc} + + )} + /> + + + {images.map((_, i) => ( + + ))} + + + ); +} + +function LanguagePage() { + const { i18n } = useTranslation(); + const currentLang = i18n.language; + + const languages = [ + { id: 'zh-TW', label: '繁体' }, + { id: 'en', label: 'English' }, + ]; + + return ( + + + {languages.map((lang, index) => ( + changeLanguage(lang.id as any)} + > + {lang.label} + {currentLang === lang.id && ( + + )} + + ))} + ); } @@ -442,26 +671,31 @@ const styles = StyleSheet.create({ }, quickCard: { flex: 1, - borderRadius: 16, + borderRadius: 20, padding: 14, backgroundColor: 'rgba(244,214,194,0.65)', + height: 142, + justifyContent: 'space-between', }, quickTitle: { - color: '#5E2A28', - fontSize: 14, - fontWeight: '700', - marginBottom: 10, + color: '#482A0D', + fontSize: 15, + fontWeight: '500', + }, + quickIconWrap: { + alignItems: 'flex-end', + justifyContent: 'flex-end', + flex: 1 }, - quickIconWrap: { alignItems: 'center', justifyContent: 'center', flex: 1 }, list: { - borderRadius: 16, + borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.75)', overflow: 'hidden', marginBottom: 8, }, item: { height: 52, - paddingHorizontal: 14, + paddingHorizontal: 18, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', @@ -471,55 +705,82 @@ const styles = StyleSheet.create({ itemLeft: { flexDirection: 'row', alignItems: 'center', - gap: 10, + gap: 12, }, itemIcon: { - width: 28, - height: 28, - borderRadius: 10, - backgroundColor: 'rgba(244,214,194,0.55)', + width: 24, + height: 24, alignItems: 'center', justifyContent: 'center', }, itemText: { - color: '#5E2A28', + color: '#522B09', fontSize: 15, - fontWeight: '600', + fontWeight: '500', }, chevron: { - color: 'rgba(94,42,40,0.45)', + color: '#C7B4A1', fontSize: 20, marginTop: -2, }, favContainer: { - borderRadius: 16, - backgroundColor: 'rgba(255,255,255,0.75)', - overflow: 'hidden', - marginBottom: 8, + flex: 1, + minHeight: 500, // 确保容器有足够高度 }, favEmpty: { color: 'rgba(94,42,40,0.55)', fontSize: 15, textAlign: 'center', - paddingVertical: 26, - paddingHorizontal: 14, + paddingVertical: 100, }, favList: { - paddingBottom: 10, + paddingHorizontal: 20, + paddingBottom: 80, }, - favRow: { - paddingHorizontal: 14, - paddingVertical: 14, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: 'rgba(94,42,40,0.10)', - backgroundColor: 'rgba(255,255,255,0.15)', + favCard: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 24, }, - favText: { - color: '#5E2A28', + favLeft: { + width: 90, + }, + favDate: { + fontSize: 14, + color: '#772F00', + fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif', + fontWeight: '600', + }, + favRight: { + flex: 1, + }, + favThumb: { + backgroundColor: '#FFF4EA', + borderRadius: 16, + padding: 20, + width: width * 0.6, + height: 161, + justifyContent: 'center', + position: 'relative', + borderWidth: 1, + borderColor: 'rgba(119, 47, 0, 0.05)', + }, + favThumbText: { fontSize: 15, lineHeight: 22, - fontWeight: '600', + color: '#5E2A28', + fontWeight: '500', + textAlign: 'center', + }, + favRemoveBtn: { + position: 'absolute', + top: 15, + right: 15, + width: 37, + height: 33, + alignItems: 'center', + justifyContent: 'center', }, counter: { @@ -563,15 +824,22 @@ const styles = StyleSheet.create({ }, remindRow: { height: 54, - borderRadius: 16, + borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.75)', paddingHorizontal: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 18, + width: width - 40, // 屏幕宽度减去左右各 20pt + alignSelf: 'center', }, rowLeft: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + rowRight: { + height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, rowIcon: { width: 28, height: 28, @@ -587,6 +855,7 @@ const styles = StyleSheet.create({ }, okPressable: { marginBottom: 6, + marginTop: 40, // 增加顶部间距以撑开高度 }, okBtn: { height: 52, @@ -602,61 +871,104 @@ const styles = StyleSheet.create({ }, widgetContent: { - paddingBottom: 18, - gap: 14, - }, - widgetRow: { - flexDirection: 'row', - gap: 12, - }, - widgetCard: { flex: 1, - borderRadius: 16, - overflow: 'hidden', - backgroundColor: 'rgba(255,255,255,0.75)', - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(94,42,40,0.12)', + paddingTop: 10, }, - widgetPreviewInner: { - height: 120, - padding: 12, - justifyContent: 'center', + questionBtn: { + position: 'absolute', + right: 20, + top: -34, // 放在标题栏右侧 + zIndex: 10, }, - widgetPreviewLock: { - backgroundColor: 'rgba(244,214,194,0.65)', + widgetScroll: { + alignItems: 'center', + gap: 30, + paddingBottom: 40, }, - widgetPreviewHome: { - backgroundColor: 'rgba(243,208,225,0.55)', + widgetItem: { + alignItems: 'center', + width: '100%', }, - widgetPreviewDate: { - color: 'rgba(94,42,40,0.70)', - fontSize: 12, - fontWeight: '600', - marginBottom: 6, + widgetImg1: { + width: width * 0.9, + height: (width * 0.9) * (156 / 311), }, - widgetPreviewTime: { - color: '#ffffff', - fontSize: 44, - fontWeight: '800', - letterSpacing: 1, + widgetImg2: { + width: width * 0.9, + height: (width * 0.9) * (175 / 311), }, - widgetPreviewQuote: { - color: '#5E2A28', - fontSize: 14, - lineHeight: 20, - fontWeight: '700', + widgetLabel: { + marginTop: 12, + fontSize: 15, + color: 'rgba(94, 42, 40, 0.45)', + fontWeight: '500', }, - widgetCardLabel: { - paddingVertical: 10, + howToPage: { + flex: 1, + alignItems: 'center', + paddingTop: 20, + }, + howToSlide: { + width: width - 32, // 减去 SheetModal 的 paddingHorizontal: 16 * 2 + alignItems: 'center', + }, + howToImg: { + width: width * 0.9, + height: (width * 0.9) * (234 / 326), + marginBottom: 40, + }, + howToDesc: { + fontSize: 15, + lineHeight: 25, + color: '#522B09', textAlign: 'center', - color: '#5E2A28', - fontSize: 13, - fontWeight: '700', + fontWeight: '500', + paddingHorizontal: 20, }, - widgetDesc: { - color: 'rgba(94,42,40,0.75)', - fontSize: 13, - lineHeight: 18, + pagination: { + flexDirection: 'row', + position: 'absolute', + top: (width * 0.9) * (234 / 326) + 35, // 根据新的图片高度动态计算 + gap: 8, + }, + dot: { + width: 8, + height: 8, + borderRadius: 4, + }, + dotActive: { + backgroundColor: '#EEB054', + }, + dotInactive: { + backgroundColor: 'rgba(238, 176, 84, 0.3)', + }, + langPage: { + paddingHorizontal: 20, + paddingTop: 10, + }, + langList: { + backgroundColor: '#FFFFFF', + borderRadius: 20, + overflow: 'hidden', + width: width - 40, // 屏幕宽度减去左右各 20pt + alignSelf: 'center', + }, + langItem: { + height: 62, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 24, + }, + langItemBorder: { + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: 'rgba(94,42,40,0.1)', + }, + langText: { + fontSize: 15, + color: '#522B09', + fontWeight: '500', + textTransform: 'capitalize', }, }); diff --git a/client/components/home/ThemeModal.tsx b/client/components/home/ThemeModal.tsx index d5f9b59..54e8207 100644 --- a/client/components/home/ThemeModal.tsx +++ b/client/components/home/ThemeModal.tsx @@ -16,7 +16,7 @@ type Props = { export default function ThemeModal({ visible, mode, onSelect, onClose }: Props) { const { t } = useTranslation(); return ( - + onSelect('scenery')} > @@ -35,7 +35,11 @@ export default function ThemeModal({ visible, mode, onSelect, onClose }: Props) selected={mode === 'color'} onPress={() => onSelect('color')} > - + @@ -56,11 +60,20 @@ function ThemeCard({ return ( - {children} - {title} + + + {children} + {/* 文案展示在图片中心 */} + + + {title} + + + + ); } @@ -68,43 +81,57 @@ function ThemeCard({ const styles = StyleSheet.create({ row: { flexDirection: 'row', - gap: 14, - paddingBottom: 18, + gap: 30, + paddingHorizontal: 10, + paddingBottom: 50, + paddingTop: 20, + justifyContent: 'center', }, - card: { - flex: 1, - borderRadius: 18, - padding: 10, - backgroundColor: 'rgba(255,255,255,0.55)', + cardContainer: { + alignItems: 'center', + width: 143, }, - cardSelected: { - borderWidth: 2, - borderColor: '#F99CC0', + previewWrapper: { + width: 138, + height: 203, + borderRadius: 26, + padding: 6.5, + justifyContent: 'center', + alignItems: 'center', }, - cardUnselected: { - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(94,42,40,0.18)', + selectedWrapper: { + borderWidth: 4, + borderColor: '#E7837A', + borderRadius: 26, }, - preview: { - height: 96, - borderRadius: 14, + previewInner: { + width: '100%', + height: '100%', + borderRadius: 21, overflow: 'hidden', - backgroundColor: '#fff', - marginBottom: 10, + backgroundColor: '#F5F5F5', + position: 'relative', }, previewImage: { width: '100%', height: '100%', }, - colorPreview: { - flex: 1, - backgroundColor: '#F3D0E1', + textOverlay: { + ...StyleSheet.absoluteFillObject, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0,0,0,0.05)', // 轻微遮罩增加文字可读性 }, - cardTitle: { - color: '#5E2A28', - fontSize: 14, + overlayTitle: { + color: '#FFFFFF', + fontSize: 15, fontWeight: '700', textAlign: 'center', + textShadowColor: 'rgba(0, 0, 0, 0.3)', + textShadowOffset: { width: 0, height: 1 }, + textShadowRadius: 3, + }, + selectedOverlayTitle: { + color: '#FFFFFF', }, }); - diff --git a/client/components/onboarding/NameInputStep.tsx b/client/components/onboarding/NameInputStep.tsx index beaddcb..1472d0f 100644 --- a/client/components/onboarding/NameInputStep.tsx +++ b/client/components/onboarding/NameInputStep.tsx @@ -1,32 +1,82 @@ -import React from 'react'; -import { View, StyleSheet, TextInput, Platform } from 'react-native'; -import { SerifText } from './SerifText'; +import React, { useEffect, useRef, useState } from 'react'; +import { View, StyleSheet, TextInput, Platform, Animated, TouchableOpacity, Dimensions, Text } from 'react-native'; import { OnboardingColors } from '@/constants/OnboardingTheme'; +import BtnNotClicked from '@/assets/images/icon/btn_Notclicked.svg'; +import BtnClicked from '@/assets/images/icon/btn_clicked.svg'; +import EnterLightIcon from '@/assets/images/icon/enter_Light_icon.svg'; + +const { height } = Dimensions.get('window'); interface NameInputStepProps { value: string; onChangeText: (text: string) => void; - onSubmitEditing?: () => void; + onNext: () => void; } -export function NameInputStep({ value, onChangeText, onSubmitEditing }: NameInputStepProps) { +export function NameInputStep({ value, onChangeText, onNext }: NameInputStepProps) { + const [isFocused, setIsFocused] = useState(false); + const blinkAnim = useRef(new Animated.Value(1)).current; + const hasInput = value.trim().length > 0; + + useEffect(() => { + const animation = Animated.loop( + Animated.sequence([ + Animated.timing(blinkAnim, { toValue: 0, duration: 500, useNativeDriver: true }), + Animated.timing(blinkAnim, { toValue: 1, duration: 500, useNativeDriver: true }), + ]) + ); + if (isFocused) { + animation.start(); + } else { + animation.stop(); + blinkAnim.setValue(0); + } + return () => animation.stop(); + }, [blinkAnim, isFocused]); + return ( - 我可以怎么称呼你? - - - + + + {/* 显示层:文案 + 跟随的光标 */} + + + {hasInput ? value : (isFocused ? "" : "Mama")} + + {isFocused && ( + + + + )} + + + {/* 交互层:隐藏的输入框 */} + setIsFocused(true)} + onBlur={() => setIsFocused(false)} + caretHidden={true} + autoCorrect={false} + spellCheck={false} + /> + + + + + + {hasInput ? : } + ); @@ -34,31 +84,53 @@ export function NameInputStep({ value, onChangeText, onSubmitEditing }: NameInpu const styles = StyleSheet.create({ container: { + flex: 1, alignItems: 'center', - width: '100%', + paddingTop: 20, }, - title: { - fontSize: 24, - marginBottom: 40, - textAlign: 'center', - }, - inputContainer: { - width: '100%', + inputCard: { + width: 335, + height: 75, backgroundColor: OnboardingColors.cardBackground, borderRadius: 20, - paddingVertical: 20, - paddingHorizontal: 24, + justifyContent: 'center', + alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 10, elevation: 2, }, - input: { - fontSize: 24, - fontFamily: Platform.select({ ios: 'Georgia', android: 'serif', default: 'serif' }), + inputWrapper: { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + displayLayer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + displayText: { + fontSize: 22, + fontFamily: Platform.select({ ios: 'STIX Two Text', android: 'serif', default: 'serif' }), + fontWeight: '600', color: OnboardingColors.textPrimary, textAlign: 'center', - padding: 0, // remove default padding }, + hiddenInput: { + ...StyleSheet.absoluteFillObject, + color: 'transparent', // 文字透明,只负责输入逻辑 + fontSize: 22, + textAlign: 'center', + }, + cursorWrapper: { + // 默认居中显示时,光标在左侧 + }, + footer: { + position: 'absolute', + bottom: height * 0.12, + alignItems: 'center', + } }); diff --git a/client/components/onboarding/OnboardingLayout.tsx b/client/components/onboarding/OnboardingLayout.tsx index 33a014e..9644db7 100644 --- a/client/components/onboarding/OnboardingLayout.tsx +++ b/client/components/onboarding/OnboardingLayout.tsx @@ -1,45 +1,61 @@ import React from 'react'; -import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar } from 'react-native'; +import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar, Text, Image, Platform } from 'react-native'; import { OnboardingColors } from '@/constants/OnboardingTheme'; -import { SerifText } from './SerifText'; -import { NextButton } from './NextButton'; interface OnboardingLayoutProps { children: React.ReactNode; - onSkip?: () => void; - onNext?: () => void; - nextEnabled?: boolean; - showNextButton?: boolean; + title?: string; + currentStep: number; + totalSteps: number; + onSkip: () => void; + onBack?: () => void; + showBackButton?: boolean; } export function OnboardingLayout({ children, + title, + currentStep, + totalSteps, onSkip, - onNext, - nextEnabled = true, - showNextButton = true + onBack, + showBackButton = false }: OnboardingLayoutProps) { return ( + {/* Header: Back & Skip */} - - {onSkip && ( - - Skip {'->'} - - )} - - - - {children} + + {showBackButton && onBack && ( + + + + )} + + + + skip + + - - {showNextButton && onNext && ( - - )} + {/* Title & Progress Row */} + + {title} + ({currentStep}/{totalSteps}) + + + {/* Content */} + + {children} @@ -58,27 +74,60 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingHorizontal: 24, - paddingVertical: 12, + paddingHorizontal: 20, + height: 44, }, - spacer: { - width: 60, // Balance the skip button width approximately + headerLeft: { + width: 44, + height: 44, + justifyContent: 'center', + }, + iconButton: { + padding: 8, + }, + backIcon: { + width: 19, + height: 19, + resizeMode: 'contain', }, skipButton: { + flexDirection: 'row', + alignItems: 'center', padding: 8, }, skipText: { - fontSize: 16, - color: OnboardingColors.textPrimary, + fontSize: 13, + color: OnboardingColors.textMuted, + fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif', + marginRight: 4, + }, + skipIcon: { + width: 10, + height: 4, + resizeMode: 'contain', + }, + titleRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-end', + paddingHorizontal: 20, + marginTop: 20, + marginBottom: 20, + }, + questionTitle: { + fontSize: 22, + color: OnboardingColors.questionTitle, + fontFamily: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif', + flex: 1, + }, + progressText: { + fontSize: 18, + color: OnboardingColors.textProgress, + fontFamily: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif', + marginLeft: 10, }, content: { flex: 1, - justifyContent: 'center', - paddingHorizontal: 24, - }, - footer: { - alignItems: 'center', - paddingBottom: 40, - minHeight: 100, // Reserve space for button + paddingHorizontal: 20, }, }); diff --git a/client/components/onboarding/ReminderStep.tsx b/client/components/onboarding/ReminderStep.tsx new file mode 100644 index 0000000..165fe8d --- /dev/null +++ b/client/components/onboarding/ReminderStep.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity, Text, Platform, Dimensions } from 'react-native'; +import { OnboardingColors } from '@/constants/OnboardingTheme'; +import AddIcon from '@/assets/images/icon/add_icon.svg'; +import ReduceIcon from '@/assets/images/icon/reduce_icon.svg'; +import BtnClicked from '@/assets/images/icon/btn_clicked.svg'; + +const { height } = Dimensions.get('window'); + +interface ReminderStepProps { + value: number; + onChange: (value: number) => void; + onFinish: () => void; +} + +export function ReminderStep({ value, onChange, onFinish }: ReminderStepProps) { + const handleReduce = () => { + if (value > 1) onChange(value - 1); + }; + + const handleAdd = () => { + if (value < 5) onChange(value + 1); + }; + + return ( + + + + + + + + {value} + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + paddingTop: 20, + }, + counterContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + marginTop: 40, + }, + numberWrapper: { + flexDirection: 'row', + alignItems: 'flex-end', + marginHorizontal: 40, + }, + numberText: { + fontSize: 107, + fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif', + fontWeight: '600', + color: OnboardingColors.textPrimary, + lineHeight: 120, + }, + unitText: { + fontSize: 17, + fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif', + fontWeight: '600', + color: OnboardingColors.textPrimary, + marginBottom: 20, + marginLeft: 4, + }, + footer: { + position: 'absolute', + bottom: height * 0.12, + alignItems: 'center', + } +}); diff --git a/client/components/onboarding/SelectionStep.tsx b/client/components/onboarding/SelectionStep.tsx new file mode 100644 index 0000000..8268cca --- /dev/null +++ b/client/components/onboarding/SelectionStep.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { View, StyleSheet, TouchableOpacity, ScrollView, Dimensions } from 'react-native'; +import { OnboardingColors } from '@/constants/OnboardingTheme'; +import { SerifText } from './SerifText'; +import SelectedIcon from '@/assets/images/icon/selected_icon.svg'; +import BtnNotClicked from '@/assets/images/icon/btn_Notclicked.svg'; +import BtnClicked from '@/assets/images/icon/btn_clicked.svg'; + +const { height } = Dimensions.get('window'); + +interface Option { + id: string; + label: string; +} + +interface SelectionStepProps { + options: Option[]; + selectedIds: string[]; + onToggle: (id: string) => void; + onNext: () => void; +} + +export function SelectionStep({ options, selectedIds, onToggle, onNext }: SelectionStepProps) { + const hasSelection = selectedIds.length > 0; + + return ( + + + {options.map((option) => { + const isSelected = selectedIds.includes(option.id); + return ( + onToggle(option.id)} + activeOpacity={0.7} + > + {option.label} + {isSelected && ( + + + + )} + + ); + })} + + + {/* 底部按钮:距离底部 12% 高度 */} + + + {hasSelection ? : } + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingTop: 20, + }, + optionsList: { + paddingBottom: 150, // 为底部按钮留出空间 + }, + optionCard: { + width: '100%', + height: 75, + backgroundColor: OnboardingColors.cardBackground, + borderRadius: 20, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 24, + marginBottom: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.05, + shadowRadius: 10, + elevation: 2, + }, + optionText: { + fontSize: 18, + color: OnboardingColors.textPrimary, + fontWeight: '500', + flex: 1, + }, + iconWrapper: { + marginLeft: 10, + }, + footer: { + position: 'absolute', + bottom: height * 0.12, + left: 0, + right: 0, + alignItems: 'center', + } +}); diff --git a/client/components/ui/.SheetModal.tsx.swp b/client/components/ui/.SheetModal.tsx.swp new file mode 100644 index 0000000..e0c938d Binary files /dev/null and b/client/components/ui/.SheetModal.tsx.swp differ diff --git a/client/components/ui/SheetModal.tsx b/client/components/ui/SheetModal.tsx index e0fc3ea..bdb9d97 100644 --- a/client/components/ui/SheetModal.tsx +++ b/client/components/ui/SheetModal.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { Modal, Pressable, StyleSheet, Text, View } from 'react-native'; +import React, { useEffect, useMemo, useState, useRef } from 'react'; +import { Modal, Pressable, StyleSheet, Text, View, PanResponder, Animated as RNAnimated, Dimensions, Image, ImageSourcePropType } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Animated, { Easing, @@ -9,26 +9,35 @@ import Animated, { withTiming, } from 'react-native-reanimated'; +const { height: SCREEN_HEIGHT } = Dimensions.get('window'); +const FIXED_TOP_GAP = 100; // 统一距离顶部的高度 + type Props = { visible: boolean; title?: string; onClose: () => void; children: React.ReactNode; + leftIcon?: ImageSourcePropType; // 新增:支持自定义左侧图标 + height?: number; // 新增:支持自定义高度 }; /** * 通用底部上拉弹窗(Sheet) * - 内容区背景色固定:#FAF3EC - * - 关闭方式:点 X(本期不要求点遮罩关闭) + * - 高度固定:默认距离顶部固定间距,也支持传入指定高度 */ -export default function SheetModal({ visible, title, onClose, children }: Props) { +export default function SheetModal({ visible, title, onClose, children, leftIcon, height: customHeight }: Props) { const insets = useSafeAreaInsets(); const [mounted, setMounted] = useState(false); const progress = useSharedValue(0); // 0: 关闭, 1: 打开 + const dragY = useSharedValue(0); // 拖拽位移 + + const sheetHeight = customHeight || (SCREEN_HEIGHT - FIXED_TOP_GAP); useEffect(() => { if (visible) { setMounted(true); + dragY.value = 0; progress.value = withTiming(1, { duration: 260, easing: Easing.out(Easing.cubic) }); return; } @@ -40,46 +49,88 @@ export default function SheetModal({ visible, title, onClose, children }: Props) if (finished) runOnJS(setMounted)(false); } ); - }, [visible, mounted, progress]); + }, [visible, mounted, progress, dragY]); + + // 使用 PanResponder 处理下滑手势 + const panResponder = useRef( + PanResponder.create({ + onStartShouldSetPanResponder: () => false, + onMoveShouldSetPanResponder: () => false, // 暂时屏蔽下拉关闭手势,解决滑动冲突 + onPanResponderMove: (_, gestureState) => { + if (gestureState.dy > 0) { + dragY.value = gestureState.dy; + } + }, + onPanResponderRelease: (_, gestureState) => { + if (gestureState.dy > 80 || gestureState.vy > 0.5) { + runOnJS(onClose)(); + } else { + dragY.value = withTiming(0, { + duration: 300, + easing: Easing.out(Easing.back(1)) + }); + } + }, + onPanResponderTerminate: () => { + dragY.value = withTiming(0, { duration: 200 }); + }, + }) + ).current; const overlayStyle = useAnimatedStyle(() => { return { opacity: 0.4 * progress.value }; }); const sheetStyle = useAnimatedStyle(() => { - const translateY = (1 - progress.value) * 380; - return { transform: [{ translateY }] }; + const baseTranslateY = (1 - progress.value) * sheetHeight; // 基础位移 + return { + transform: [{ translateY: baseTranslateY + dragY.value }] + }; }); - const containerPaddingBottom = useMemo(() => Math.max(insets.bottom, 12), [insets.bottom]); + const containerPaddingBottom = useMemo(() => Math.max(insets.bottom, 80), [insets.bottom]); // 增加底部间距至 80,约占 350 高度的 22%,确保内容不被截断并留出足够呼吸感 // 注意:Modal 的 visible 必须为 true 才会渲染,因此用 mounted 保持退场动画 return ( - + + + + + + {title ?? ''} - × + {leftIcon ? ( + + ) : ( + × + )} @@ -103,9 +154,19 @@ const styles = StyleSheet.create({ backgroundColor: '#FAF3EC', borderTopLeftRadius: 24, borderTopRightRadius: 24, - paddingTop: 14, + paddingTop: 8, paddingHorizontal: 16, }, + handleContainer: { + alignItems: 'center', + paddingVertical: 8, + }, + handle: { + width: 40, + height: 5, + borderRadius: 2.5, + backgroundColor: 'rgba(94,42,40,0.15)', + }, header: { height: 44, justifyContent: 'center', @@ -131,8 +192,13 @@ const styles = StyleSheet.create({ lineHeight: 28, fontWeight: '400', }, + backIcon: { + width: 20, + height: 20, + resizeMode: 'contain', + }, body: { paddingTop: 10, + flex: 1, }, }); - diff --git a/client/constants/OnboardingTheme.ts b/client/constants/OnboardingTheme.ts index 7d8354a..871489f 100644 --- a/client/constants/OnboardingTheme.ts +++ b/client/constants/OnboardingTheme.ts @@ -1,9 +1,14 @@ export const OnboardingColors = { background: '#FFF4EA', - textPrimary: '#4A3B32', - textSecondary: '#8C8C8C', + textPrimary: '#772F00', + textSecondary: '#DED2CA', // 默认 Mama 字体颜色 + textMuted: '#A27854', // Skip 按钮颜色 + textProgress: '#D4B08E', // (0/5) 颜色 + questionTitle: '#B8504D', // 问题标题颜色 + buttonNotClicked: '#F2DDCA', buttonStart: '#F69F7B', buttonEnd: '#F99CC0', cardBackground: '#FFFFFF', - cursor: '#F99CC0', + cardSelected: '#FFD8E2', // 选中后的背景色 + cursor: '#F89DB4', }; diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index a555c69..87fc2f5 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -3,6 +3,9 @@ PODS: - ExpoModulesCore - EXConstants (18.0.13): - ExpoModulesCore + - EXJSONUtils (0.15.0) + - EXManifests (1.0.10): + - ExpoModulesCore - EXNotifications (0.32.16): - ExpoModulesCore - Expo (54.0.32): @@ -30,6 +33,177 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - expo-dev-client (6.0.20): + - EXManifests + - expo-dev-launcher + - expo-dev-menu + - expo-dev-menu-interface + - EXUpdatesInterface + - expo-dev-launcher (6.0.20): + - EXManifests + - expo-dev-launcher/Main (= 6.0.20) + - expo-dev-menu + - expo-dev-menu-interface + - ExpoModulesCore + - EXUpdatesInterface + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-launcher/Main (6.0.20): + - EXManifests + - expo-dev-launcher/Unsafe + - expo-dev-menu + - expo-dev-menu-interface + - ExpoModulesCore + - EXUpdatesInterface + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-launcher/Unsafe (6.0.20): + - EXManifests + - expo-dev-menu + - expo-dev-menu-interface + - ExpoModulesCore + - EXUpdatesInterface + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-menu (7.0.18): + - expo-dev-menu/Main (= 7.0.18) + - expo-dev-menu/ReactNativeCompatibles (= 7.0.18) + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-menu-interface (2.0.0) + - expo-dev-menu/Main (7.0.18): + - EXManifests + - expo-dev-menu-interface + - ExpoModulesCore + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-menu/ReactNativeCompatibles (7.0.18): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - ExpoAsset (12.0.12): - ExpoModulesCore - ExpoFileSystem (19.0.21): @@ -74,6 +248,8 @@ PODS: - ExpoModulesCore - ExpoWebBrowser (15.0.10): - ExpoModulesCore + - EXUpdatesInterface (2.0.0): + - ExpoModulesCore - FBLazyVector (0.81.5) - hermes-engine (0.81.5): - hermes-engine/Pre-built (= 0.81.5) @@ -1798,6 +1974,28 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - RNGestureHandler (2.30.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - RNReanimated (4.1.6): - hermes-engine - RCTRequired @@ -2040,12 +2238,18 @@ PODS: DEPENDENCIES: - "EXApplication (from `../node_modules/.pnpm/expo-application@7.0.8_expo@54.0.32/node_modules/expo-application/ios`)" - "EXConstants (from `../node_modules/.pnpm/expo-constants@18.0.13_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0_/node_modules/expo-constants/ios`)" + - "EXJSONUtils (from `../node_modules/.pnpm/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios`)" + - "EXManifests (from `../node_modules/.pnpm/expo-manifests@1.0.10_expo@54.0.32/node_modules/expo-manifests/ios`)" - "EXNotifications (from `../node_modules/.pnpm/expo-notifications@0.32.16_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+r_758952db70529f49bda448def1c13c49/node_modules/expo-notifications/ios`)" - "Expo (from `../node_modules/.pnpm/expo@54.0.32_@babel+core@7.28.6_@expo+metro-runtime@6.1.2_expo-router@6.0.22_react-nati_18ad48ba284ee86e6eb1cb0f939697b0/node_modules/expo`)" + - "expo-dev-client (from `../node_modules/.pnpm/expo-dev-client@6.0.20_expo@54.0.32/node_modules/expo-dev-client/ios`)" + - "expo-dev-launcher (from `../node_modules/.pnpm/expo-dev-launcher@6.0.20_expo@54.0.32/node_modules/expo-dev-launcher`)" + - "expo-dev-menu (from `../node_modules/.pnpm/expo-dev-menu@7.0.18_expo@54.0.32/node_modules/expo-dev-menu`)" + - "expo-dev-menu-interface (from `../node_modules/.pnpm/expo-dev-menu-interface@2.0.0_expo@54.0.32/node_modules/expo-dev-menu-interface/ios`)" - "ExpoAsset (from `../node_modules/.pnpm/expo-asset@12.0.12_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/expo-asset/ios`)" - "ExpoFileSystem (from `../node_modules/.pnpm/expo-file-system@19.0.21_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0_/node_modules/expo-file-system/ios`)" - "ExpoFont (from `../node_modules/.pnpm/expo-font@14.0.11_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/expo-font/ios`)" - - "ExpoHead (from `../node_modules/.pnpm/expo-router@6.0.22_@expo+metro-runtime@6.1.2_@types+react@19.1.17_expo-constants@18.0.1_4eba8c13adbbc1edf57cc04c4adac5f6/node_modules/expo-router/ios`)" + - "ExpoHead (from `../node_modules/.pnpm/expo-router@6.0.22_@expo+metro-runtime@6.1.2_@types+react@19.1.17_expo-constants@18.0.1_bd9aa16746ed7110429f931eb008e6d2/node_modules/expo-router/ios`)" - "ExpoKeepAwake (from `../node_modules/.pnpm/expo-keep-awake@15.0.8_expo@54.0.32_react@19.1.0/node_modules/expo-keep-awake/ios`)" - "ExpoLinearGradient (from `../node_modules/.pnpm/expo-linear-gradient@15.0.8_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+_53aef72480df9baa4504f4743d9c64bb/node_modules/expo-linear-gradient/ios`)" - "ExpoLinking (from `../node_modules/.pnpm/expo-linking@8.0.11_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/expo-linking/ios`)" @@ -2053,6 +2257,7 @@ DEPENDENCIES: - "ExpoModulesCore (from `../node_modules/.pnpm/expo-modules-core@3.0.29_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/expo-modules-core`)" - "ExpoSplashScreen (from `../node_modules/.pnpm/expo-splash-screen@31.0.13_expo@54.0.32/node_modules/expo-splash-screen/ios`)" - "ExpoWebBrowser (from `../node_modules/.pnpm/expo-web-browser@15.0.10_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0_/node_modules/expo-web-browser/ios`)" + - "EXUpdatesInterface (from `../node_modules/.pnpm/expo-updates-interface@2.0.0_expo@54.0.32/node_modules/expo-updates-interface/ios`)" - "FBLazyVector (from `../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/Libraries/FBLazyVector`)" - "hermes-engine (from `../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)" - "RCTDeprecation (from `../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)" @@ -2122,6 +2327,7 @@ DEPENDENCIES: - "ReactCommon/turbomodule/core (from `../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/ReactCommon`)" - "ReactNativeDependencies (from `../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)" - "RNCAsyncStorage (from `../node_modules/.pnpm/@react-native-async-storage+async-storage@2.2.0_react-native@0.81.5_@babel+core@7.28.6__ce3c4972004f3d6791573ec5b64bee38/node_modules/@react-native-async-storage/async-storage`)" + - "RNGestureHandler (from `../node_modules/.pnpm/react-native-gesture-handler@2.30.0_react-native@0.81.5_@babel+core@7.28.6_@types+react_39cf7da47c9c8531caaa923ee740e293/node_modules/react-native-gesture-handler`)" - "RNReanimated (from `../node_modules/.pnpm/react-native-reanimated@4.1.6_@babel+core@7.28.6_react-native-worklets@0.5.1_@babel+cor_c7c888bd389fb93c9cfe2d3c1c8b0777/node_modules/react-native-reanimated`)" - "RNScreens (from `../node_modules/.pnpm/react-native-screens@4.16.0_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/react-native-screens`)" - "RNSVG (from `../node_modules/.pnpm/react-native-svg@15.12.1_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/react-native-svg`)" @@ -2133,10 +2339,22 @@ EXTERNAL SOURCES: :path: "../node_modules/.pnpm/expo-application@7.0.8_expo@54.0.32/node_modules/expo-application/ios" EXConstants: :path: "../node_modules/.pnpm/expo-constants@18.0.13_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0_/node_modules/expo-constants/ios" + EXJSONUtils: + :path: "../node_modules/.pnpm/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios" + EXManifests: + :path: "../node_modules/.pnpm/expo-manifests@1.0.10_expo@54.0.32/node_modules/expo-manifests/ios" EXNotifications: :path: "../node_modules/.pnpm/expo-notifications@0.32.16_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+r_758952db70529f49bda448def1c13c49/node_modules/expo-notifications/ios" Expo: :path: "../node_modules/.pnpm/expo@54.0.32_@babel+core@7.28.6_@expo+metro-runtime@6.1.2_expo-router@6.0.22_react-nati_18ad48ba284ee86e6eb1cb0f939697b0/node_modules/expo" + expo-dev-client: + :path: "../node_modules/.pnpm/expo-dev-client@6.0.20_expo@54.0.32/node_modules/expo-dev-client/ios" + expo-dev-launcher: + :path: "../node_modules/.pnpm/expo-dev-launcher@6.0.20_expo@54.0.32/node_modules/expo-dev-launcher" + expo-dev-menu: + :path: "../node_modules/.pnpm/expo-dev-menu@7.0.18_expo@54.0.32/node_modules/expo-dev-menu" + expo-dev-menu-interface: + :path: "../node_modules/.pnpm/expo-dev-menu-interface@2.0.0_expo@54.0.32/node_modules/expo-dev-menu-interface/ios" ExpoAsset: :path: "../node_modules/.pnpm/expo-asset@12.0.12_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/expo-asset/ios" ExpoFileSystem: @@ -2144,7 +2362,7 @@ EXTERNAL SOURCES: ExpoFont: :path: "../node_modules/.pnpm/expo-font@14.0.11_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0__react@19.1.0/node_modules/expo-font/ios" ExpoHead: - :path: "../node_modules/.pnpm/expo-router@6.0.22_@expo+metro-runtime@6.1.2_@types+react@19.1.17_expo-constants@18.0.1_4eba8c13adbbc1edf57cc04c4adac5f6/node_modules/expo-router/ios" + :path: "../node_modules/.pnpm/expo-router@6.0.22_@expo+metro-runtime@6.1.2_@types+react@19.1.17_expo-constants@18.0.1_bd9aa16746ed7110429f931eb008e6d2/node_modules/expo-router/ios" ExpoKeepAwake: :path: "../node_modules/.pnpm/expo-keep-awake@15.0.8_expo@54.0.32_react@19.1.0/node_modules/expo-keep-awake/ios" ExpoLinearGradient: @@ -2159,6 +2377,8 @@ EXTERNAL SOURCES: :path: "../node_modules/.pnpm/expo-splash-screen@31.0.13_expo@54.0.32/node_modules/expo-splash-screen/ios" ExpoWebBrowser: :path: "../node_modules/.pnpm/expo-web-browser@15.0.10_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0_/node_modules/expo-web-browser/ios" + EXUpdatesInterface: + :path: "../node_modules/.pnpm/expo-updates-interface@2.0.0_expo@54.0.32/node_modules/expo-updates-interface/ios" FBLazyVector: :path: "../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/Libraries/FBLazyVector" hermes-engine: @@ -2296,6 +2516,8 @@ EXTERNAL SOURCES: :podspec: "../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1.17_react@19.1.0/node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" RNCAsyncStorage: :path: "../node_modules/.pnpm/@react-native-async-storage+async-storage@2.2.0_react-native@0.81.5_@babel+core@7.28.6__ce3c4972004f3d6791573ec5b64bee38/node_modules/@react-native-async-storage/async-storage" + RNGestureHandler: + :path: "../node_modules/.pnpm/react-native-gesture-handler@2.30.0_react-native@0.81.5_@babel+core@7.28.6_@types+react_39cf7da47c9c8531caaa923ee740e293/node_modules/react-native-gesture-handler" RNReanimated: :path: "../node_modules/.pnpm/react-native-reanimated@4.1.6_@babel+core@7.28.6_react-native-worklets@0.5.1_@babel+cor_c7c888bd389fb93c9cfe2d3c1c8b0777/node_modules/react-native-reanimated" RNScreens: @@ -2310,8 +2532,14 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f EXConstants: fce59a631a06c4151602843667f7cfe35f81e271 + EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd + EXManifests: a8d97683e5c7a3b026ffbd58559c64dc655b747b EXNotifications: 9eec98712cc814ceff916d876cb53859003b0597 Expo: 4e503a041c59c4e34c8be262a135848ad5cd3710 + expo-dev-client: 425ee077d6754a98cfe3a2e2410d29b440b24c9d + expo-dev-launcher: a4f4cdef064ab1fb8621e5b8c7c457cd6e9568c3 + expo-dev-menu: 05b18812110c175814c6af0d09dd658abcc5e00d + expo-dev-menu-interface: 600df12ea01efecdd822daaf13cc0ac091775533 ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18 ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063 ExpoFont: f543ce20a228dd702813668b1a07b46f51878d47 @@ -2323,6 +2551,7 @@ SPEC CHECKSUMS: ExpoModulesCore: f3da4f1ab5a8375d0beafab763739dbee8446583 ExpoSplashScreen: bc3cffefca2716e5f22350ca109badd7e50ec14d ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52 + EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734 FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12 hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990 @@ -2391,6 +2620,7 @@ SPEC CHECKSUMS: ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8 ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 + RNGestureHandler: e0d0bce5599f6120b7adf90c38d2805e2935795f RNReanimated: 9c6a550b41de91cf374e60afd79db93a362f1126 RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845 RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34 diff --git a/client/ios/client.xcodeproj/project.pbxproj b/client/ios/client.xcodeproj/project.pbxproj index 4a11c49..37c23a4 100644 --- a/client/ios/client.xcodeproj/project.pbxproj +++ b/client/ios/client.xcodeproj/project.pbxproj @@ -374,6 +374,8 @@ "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -387,6 +389,8 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/client/ios/情绪小组件/EmotionWidget.swift b/client/ios/情绪小组件/EmotionWidget.swift index 3376cfa..510cc41 100644 --- a/client/ios/情绪小组件/EmotionWidget.swift +++ b/client/ios/情绪小组件/EmotionWidget.swift @@ -1,28 +1,54 @@ import WidgetKit import SwiftUI -// V1:写死文案的小组件(Small/Medium/Large + 点击跳转 Home) +// V2:纯色背景 + 随机文案小组件(Small/Medium/Large + 点击跳转 Home) struct EmotionProvider: TimelineProvider { + private let quotes = [ + "你已经很努力了,今天也值得被温柔对待。", + "轻轻呼吸,感受当下的每一刻。", + "所有的压力,都会在深呼吸中慢慢消散。", + "给生活一点留白,给自己一点温柔。", + "不要走得太快,等一等落下的灵魂。", + "世界虽嘈杂,但你可以拥有一颗宁静的心。", + "每一个瞬间,都是生命最好的安排。", + "抱抱自己,辛苦了,亲爱的。", + "慢一点也没关系,只要你在前行。", + "今天,你对自己微笑了吗?", + "愿你历经山河,仍觉得人间值得。", + "心简单,世界就简单;心平顺,生活就平顺。", + "即使生活偶尔晦暗,你也要成为自己的光。", + "别让琐事挤走生活的快乐,别让压力消磨奋斗的激情。" + ] + func placeholder(in context: Context) -> EmotionEntry { - EmotionEntry(date: Date()) + EmotionEntry(date: Date(), text: quotes[0]) } func getSnapshot(in context: Context, completion: @escaping (EmotionEntry) -> ()) { - completion(EmotionEntry(date: Date())) + let entry = EmotionEntry(date: Date(), text: quotes.randomElement() ?? quotes[0]) + completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - // V1:内容写死,不做数据更新;给一个较长的刷新间隔(系统仍可能自行调度) - let entry = EmotionEntry(date: Date()) - let nextUpdate = Calendar.current.date(byAdding: .day, value: 7, to: Date()) - ?? Date().addingTimeInterval(60 * 60 * 24 * 7) - completion(Timeline(entries: [entry], policy: .after(nextUpdate))) + var entries: [EmotionEntry] = [] + let currentDate = Date() + + // 生成未来 24 小时的 6 个条目,每 4 小时更换一次随机文案 + for hourOffset in 0..<6 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset * 4, to: currentDate)! + let entry = EmotionEntry(date: entryDate, text: quotes.randomElement() ?? quotes[0]) + entries.append(entry) + } + + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) } } struct EmotionEntry: TimelineEntry { let date: Date + let text: String } struct EmotionWidgetView: View { @@ -30,160 +56,37 @@ struct EmotionWidgetView: View { @Environment(\.widgetFamily) var family private let title = "正念" - private let text = "你已经很努力了,今天也值得被温柔对待。" private let deepLink = URL(string: "client:///(app)/home") + + // 背景色 #F7D9BF + private let backgroundColor = Color(red: 247/255, green: 217/255, blue: 191/255) + // 文本颜色(深咖色,适合搭配浅橘色背景) + private let textColor = Color(red: 74/255, green: 52/255, blue: 40/255) var body: some View { - switch family { - case .systemSmall: - smallView() - case .systemMedium: - mediumView() - case .systemLarge: - largeView() - default: - smallView() - } - } - - // 统一的“卡片背景”风格(iOS 15 兼容) - private func cardBackground(colors: [Color]) -> some View { - ZStack { - LinearGradient( - colors: colors, - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - // 轻微光斑,增加层次 - RadialGradient( - gradient: Gradient(colors: [Color.white.opacity(0.16), Color.white.opacity(0.0)]), - center: .topTrailing, - startRadius: 10, - endRadius: 180 - ) - } - .overlay( - RoundedRectangle(cornerRadius: 18, style: .continuous) - .stroke(Color.white.opacity(0.14), lineWidth: 1) - ) - .cornerRadius(18) - } - - private func chip(_ text: String) -> some View { - Text(text) - .font(.system(size: 12, weight: .semibold)) - .foregroundColor(Color.white.opacity(0.9)) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.white.opacity(0.14)) - .cornerRadius(999) - } - - private func smallView() -> some View { - ZStack { - cardBackground(colors: [ - Color(red: 0.06, green: 0.08, blue: 0.12), - Color(red: 0.13, green: 0.16, blue: 0.22), - ]) - - VStack(alignment: .leading, spacing: 10) { - HStack { - chip(title) - Spacer(minLength: 0) - } - - Text(text) - .font(.system(size: 15, weight: .semibold)) - .foregroundColor(Color.white.opacity(0.92)) - .lineSpacing(2) - .lineLimit(4) - - Spacer(minLength: 0) - - Text("点我回到 App") - .font(.system(size: 11, weight: .medium)) - .foregroundColor(Color.white.opacity(0.65)) + VStack(alignment: .center, spacing: 0) { + Spacer(minLength: 0) + + Text(entry.text) + .font(.system(size: family == .systemSmall ? 17 : 20, weight: .medium)) + .foregroundColor(textColor) + .lineSpacing(6) + .multilineTextAlignment(.center) + .minimumScaleFactor(0.7) + .fixedSize(horizontal: false, vertical: true) + + Spacer(minLength: 0) + + if family != .systemSmall { + Text("Hey Mama") + .font(.system(size: 10, weight: .semibold)) + .foregroundColor(textColor.opacity(0.3)) + .padding(.bottom, 4) } - .padding(14) - } - .widgetURL(deepLink) - } - - private func mediumView() -> some View { - ZStack { - cardBackground(colors: [ - Color(red: 0.06, green: 0.08, blue: 0.12), - Color(red: 0.09, green: 0.11, blue: 0.17), - ]) - - HStack(alignment: .top, spacing: 14) { - VStack(alignment: .leading, spacing: 10) { - chip(title) - Text(text) - .font(.system(size: 17, weight: .semibold)) - .foregroundColor(Color.white.opacity(0.92)) - .lineSpacing(3) - .lineLimit(5) - - Spacer(minLength: 0) - - Text("轻轻呼吸,回到当下") - .font(.system(size: 12, weight: .medium)) - .foregroundColor(Color.white.opacity(0.7)) - } - - // 右侧装饰区:让版面更饱满 - VStack(alignment: .trailing, spacing: 8) { - Text(entry.date, style: .time) - .font(.system(size: 12, weight: .semibold)) - .foregroundColor(Color.white.opacity(0.8)) - Spacer(minLength: 0) - Text("今日") - .font(.system(size: 28, weight: .bold)) - .foregroundColor(Color.white.opacity(0.12)) - } - } - .padding(16) - } - .widgetURL(deepLink) - } - - private func largeView() -> some View { - ZStack { - cardBackground(colors: [ - Color(red: 0.06, green: 0.08, blue: 0.12), - Color(red: 0.14, green: 0.18, blue: 0.28), - ]) - - VStack(alignment: .leading, spacing: 14) { - HStack { - chip(title) - Spacer(minLength: 0) - Text(entry.date, style: .time) - .font(.system(size: 12, weight: .semibold)) - .foregroundColor(Color.white.opacity(0.78)) - } - - Text(text) - .font(.system(size: 20, weight: .semibold)) - .foregroundColor(Color.white.opacity(0.92)) - .lineSpacing(4) - .lineLimit(8) - - Spacer(minLength: 0) - - HStack { - Text("点我回到 Home") - .font(.system(size: 12, weight: .medium)) - .foregroundColor(Color.white.opacity(0.7)) - Spacer(minLength: 0) - Text("🌿") - .font(.system(size: 18)) - .opacity(0.9) - } - } - .padding(18) } + .padding(family == .systemSmall ? 16 : 24) + .frame(maxWidth: .infinity, maxHeight: .infinity) // 强制撑开容器 + .background(backgroundColor) // 将背景色直接应用到容器上 .widgetURL(deepLink) } } @@ -194,7 +97,13 @@ struct EmotionWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: EmotionProvider()) { entry in - EmotionWidgetView(entry: entry) + if #available(iOS 17.0, *) { + EmotionWidgetView(entry: entry) + .containerBackground(Color(red: 247/255, green: 217/255, blue: 191/255), for: .widget) + } else { + EmotionWidgetView(entry: entry) + .background(Color(red: 247/255, green: 217/255, blue: 191/255)) + } } .configurationDisplayName("情绪小组件") .description("一段温柔提醒,陪你回到当下。") diff --git a/client/metro.config.js b/client/metro.config.js index ad2b03f..0e691ea 100644 --- a/client/metro.config.js +++ b/client/metro.config.js @@ -1,19 +1,20 @@ +// Metro 配置:支持 import 本地 .svg 为 React 组件 +// 说明:Expo SDK 54 + react-native-svg-transformer 的常见配置方式 const { getDefaultConfig } = require('expo/metro-config'); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); -const { transformer, resolver } = config; - config.transformer = { - ...transformer, + ...config.transformer, babelTransformerPath: require.resolve('react-native-svg-transformer'), }; config.resolver = { - ...resolver, - assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), - sourceExts: [...resolver.sourceExts, 'svg', 'mjs'], + ...config.resolver, + assetExts: config.resolver.assetExts.filter((ext) => ext !== 'svg'), + sourceExts: [...config.resolver.sourceExts, 'svg'], }; module.exports = config; + diff --git a/client/package.json b/client/package.json index 88935a4..3456181 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "@react-navigation/native": "^7.1.8", "expo": "~54.0.32", "expo-constants": "~18.0.13", + "expo-dev-client": "^6.0.20", "expo-font": "~14.0.11", "expo-linear-gradient": "^15.0.8", "expo-linking": "~8.0.11", @@ -25,11 +26,11 @@ "expo-status-bar": "~3.0.9", "expo-web-browser": "~15.0.10", "i18next": "^25.8.0", - "pnpm": "^10.28.2", "react": "19.1.0", "react-dom": "19.1.0", "react-i18next": "^16.5.4", "react-native": "0.81.5", + "react-native-gesture-handler": "^2.30.0", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", @@ -39,7 +40,6 @@ "react-native-worklets": "0.5.1" }, "devDependencies": { - "@babel/plugin-transform-react-jsx": "^7.28.6", "@types/react": "~19.1.0", "react-test-renderer": "19.1.0", "typescript": "~5.9.2", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index e3a7695..cae49cb 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: expo-constants: specifier: ~18.0.13 version: 18.0.13(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0)) + expo-dev-client: + specifier: ^6.0.20 + version: 6.0.20(expo@54.0.32) expo-font: specifier: ~14.0.11 version: 14.0.11(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -40,7 +43,7 @@ importers: version: 0.32.16(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-router: specifier: ~6.0.22 - version: 6.0.22(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native-reanimated@4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 6.0.22(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.30.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-splash-screen: specifier: ~31.0.13 version: 31.0.13(expo@54.0.32) @@ -53,9 +56,6 @@ importers: i18next: specifier: ^25.8.0 version: 25.8.0(typescript@5.9.3) - pnpm: - specifier: ^10.28.2 - version: 10.28.2 react: specifier: 19.1.0 version: 19.1.0 @@ -68,6 +68,9 @@ importers: react-native: specifier: 0.81.5 version: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) + react-native-gesture-handler: + specifier: ^2.30.0 + version: 2.30.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-reanimated: specifier: ~4.1.1 version: 4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -90,9 +93,6 @@ importers: specifier: 0.5.1 version: 0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) devDependencies: - '@babel/plugin-transform-react-jsx': - specifier: ^7.28.6 - version: 7.28.6(@babel/core@7.28.6) '@types/react': specifier: ~19.1.0 version: 19.1.17 @@ -117,7 +117,7 @@ packages: optional: true '@babel/code-frame@7.10.4': - resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} + resolution: {integrity: sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=} '@babel/code-frame@7.28.6': resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} @@ -244,12 +244,12 @@ packages: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + resolution: {integrity: sha1-qYP7Gusuw/btBCohD2QOkOeG/g0=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + resolution: {integrity: sha1-TJpvZp9dDN8bkKFnHpoUa+UwDOo=} peerDependencies: '@babel/core': ^7.0.0-0 @@ -271,7 +271,7 @@ packages: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-dynamic-import@7.8.3': - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + resolution: {integrity: sha1-Yr+Ysto80h1iYVT8lu5bPLaOrLM=} peerDependencies: '@babel/core': ^7.0.0-0 @@ -294,12 +294,12 @@ packages: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + resolution: {integrity: sha1-7mATSMNw+jNNIge+FYd3SWUh/VE=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + resolution: {integrity: sha1-AcohtmjNghjJ5kDLbdiMVBKyyWo=} peerDependencies: '@babel/core': ^7.0.0-0 @@ -310,32 +310,32 @@ packages: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + resolution: {integrity: sha1-ypHvRjA1MESLkGZSusLp/plB9pk=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + resolution: {integrity: sha1-Fn7XA2iIYIH3S1w2xlqIwDtm0ak=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + resolution: {integrity: sha1-ubBws+M1cM2f0Hun+pHA3Te5r5c=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + resolution: {integrity: sha1-YOIl7cvZimQDMqLnLdPmbxr1WHE=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + resolution: {integrity: sha1-YRGiZbz7Ag6579D9/X0mQCue1sE=} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + resolution: {integrity: sha1-T2nCq5UWfgGAzVM2YT+MV4j31Io=} peerDependencies: '@babel/core': ^7.0.0-0 @@ -619,6 +619,10 @@ packages: resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} + '@egjs/hammerjs@2.0.17': + resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} + engines: {node: '>=0.8.0'} + '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -911,7 +915,7 @@ packages: engines: {node: '>=12'} '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + resolution: {integrity: sha1-/T2x1Z7PfPEh6AZQu4ZxL5tV7O0=} engines: {node: '>=8'} '@istanbuljs/schema@0.1.3': @@ -1337,66 +1341,79 @@ packages: resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -1542,6 +1559,9 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1611,7 +1631,7 @@ packages: engines: {node: '>=10.0.0'} abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + resolution: {integrity: sha1-6vVNU7YrrkE46AnKIlyEOabvs5I=} engines: {node: '>=6.5'} accepts@1.3.8: @@ -1627,8 +1647,11 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + anser@1.4.10: - resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + resolution: {integrity: sha1-vvo+3fKCaEvQO2Pc2jknrvjC41s=} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} @@ -1643,11 +1666,11 @@ packages: engines: {node: '>=8'} ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + resolution: {integrity: sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=} engines: {node: '>=4'} ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + resolution: {integrity: sha1-7dgDYornHATIWuegkG7a00tkiTc=} engines: {node: '>=8'} ansi-styles@5.2.0: @@ -1655,7 +1678,7 @@ packages: engines: {node: '>=10'} any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -1665,17 +1688,17 @@ packages: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + resolution: {integrity: sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=} argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + resolution: {integrity: sha1-JG9Q88p4oyQPbJl+ipvR6sSeSzg=} aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + resolution: {integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=} assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} @@ -1685,7 +1708,7 @@ packages: engines: {node: '>=12'} async-limiter@1.0.1: - resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + resolution: {integrity: sha1-3TeelPDbgxCwgpH51kwyCXZmF/0=} available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} @@ -1762,7 +1785,7 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + resolution: {integrity: sha1-GxtEAWClv3rUC2UPCVljSBkDkwo=} baseline-browser-mapping@2.9.19: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} @@ -1777,7 +1800,7 @@ packages: engines: {node: '>=0.6'} boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=} bplist-creator@0.1.0: resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} @@ -1806,13 +1829,13 @@ packages: hasBin: true bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + resolution: {integrity: sha1-5nh9og7OnQeZhTPP2d5vXDj0vAU=} buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + resolution: {integrity: sha1-umLnwTEzBTWCGXFghRqPZI6Z7tA=} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -1831,11 +1854,11 @@ packages: engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + resolution: {integrity: sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=} engines: {node: '>=6'} camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + resolution: {integrity: sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=} engines: {node: '>=6'} camelcase@6.3.0: @@ -1850,7 +1873,7 @@ packages: engines: {node: '>=18'} chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + resolution: {integrity: sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=} engines: {node: '>=4'} chalk@4.1.2: @@ -1870,14 +1893,14 @@ packages: resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} ci-info@2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + resolution: {integrity: sha1-Z6npZL4xpR4V5QENWObxKDQAL0Y=} ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} cli-cursor@2.1.0: - resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + resolution: {integrity: sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=} engines: {node: '>=4'} cli-spinners@2.9.2: @@ -1892,21 +1915,21 @@ packages: engines: {node: '>=12'} clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + resolution: {integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=} engines: {node: '>=0.8'} color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + resolution: {integrity: sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=} color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + resolution: {integrity: sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=} engines: {node: '>=7.0.0'} color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + resolution: {integrity: sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=} color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} @@ -1920,10 +1943,10 @@ packages: engines: {node: '>=18'} commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + resolution: {integrity: sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=} commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + resolution: {integrity: sha1-n9YCvZNilOnp70aj9NaWQESxgGg=} engines: {node: '>= 6'} commander@7.2.0: @@ -1931,7 +1954,7 @@ packages: engines: {node: '>= 10'} compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + resolution: {integrity: sha1-r1PMprBw1MPAdQ+9dyhqbXzEb7o=} engines: {node: '>= 0.6'} compression@1.8.1: @@ -1939,10 +1962,10 @@ packages: engines: {node: '>= 0.8.0'} concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} connect@3.7.0: - resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + resolution: {integrity: sha1-XUk0iRDKpeB6AYALAw0MNfIEhPg=} engines: {node: '>= 0.10.0'} convert-source-map@2.0.0: @@ -1968,7 +1991,7 @@ packages: engines: {node: '>= 8'} crypto-random-string@2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + resolution: {integrity: sha1-7yp6lm7BEIM4g2m6oC6+rSKbMNU=} engines: {node: '>=8'} css-in-js-utils@3.1.0: @@ -2001,7 +2024,7 @@ packages: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + resolution: {integrity: sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=} peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2009,7 +2032,7 @@ packages: optional: true debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + resolution: {integrity: sha1-clgLfpFF+zm2Z2+cXl+xALk0F5o=} peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2030,7 +2053,7 @@ packages: engines: {node: '>=0.10'} deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + resolution: {integrity: sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=} engines: {node: '>=4.0.0'} deepmerge@4.3.1: @@ -2053,7 +2076,7 @@ packages: engines: {node: '>= 0.4'} depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + resolution: {integrity: sha1-tpYWPMdXVg0JzyLMj60Vcbeedt8=} engines: {node: '>= 0.8'} destroy@1.2.0: @@ -2081,7 +2104,7 @@ packages: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + resolution: {integrity: sha1-mytnDQCkMWZ6inW6Kc0bmICc51E=} dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} @@ -2096,16 +2119,16 @@ packages: engines: {node: '>= 0.4'} ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} electron-to-chromium@1.5.283: resolution: {integrity: sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==} emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + resolution: {integrity: sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=} encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} engines: {node: '>= 0.8'} encodeurl@2.0.0: @@ -2151,22 +2174,22 @@ packages: engines: {node: '>=6'} escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + resolution: {integrity: sha1-owME6Z2qMuI7L9IPUbq9B8/8o0Q=} engines: {node: '>=8'} escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + resolution: {integrity: sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ=} engines: {node: '>=10'} esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + resolution: {integrity: sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=} engines: {node: '>=4'} hasBin: true @@ -2174,11 +2197,11 @@ packages: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} engines: {node: '>= 0.6'} event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + resolution: {integrity: sha1-XU0+vflYPWOlMzzi3rdICrKwV4k=} engines: {node: '>=6'} exec-async@2.2.0: @@ -2206,6 +2229,26 @@ packages: expo: '*' react-native: '*' + expo-dev-client@6.0.20: + resolution: {integrity: sha512-5XjoVlj1OxakNxy55j/AUaGPrDOlQlB6XdHLLWAw61w5ffSpUDHDnuZzKzs9xY1eIaogOqTOQaAzZ2ddBkdXLA==} + peerDependencies: + expo: '*' + + expo-dev-launcher@6.0.20: + resolution: {integrity: sha512-a04zHEeT9sB0L5EB38fz7sNnUKJ2Ar1pXpcyl60Ki8bXPNCs9rjY7NuYrDkP/irM8+1DklMBqHpyHiLyJ/R+EA==} + peerDependencies: + expo: '*' + + expo-dev-menu-interface@2.0.0: + resolution: {integrity: sha512-BvAMPt6x+vyXpThsyjjOYyjwfjREV4OOpQkZ0tNl+nGpsPfcY9mc6DRACoWnH9KpLzyIt3BOgh3cuy/h/OxQjw==} + peerDependencies: + expo: '*' + + expo-dev-menu@7.0.18: + resolution: {integrity: sha512-4kTdlHrnZCAWCT6tZRQHSSjZ7vECFisL4T+nsG/GJDo/jcHNaOVGV5qPV9wzlTxyMk3YOPggRw4+g7Ownrg5eA==} + peerDependencies: + expo: '*' + expo-file-system@19.0.21: resolution: {integrity: sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg==} peerDependencies: @@ -2219,6 +2262,9 @@ packages: react: '*' react-native: '*' + expo-json-utils@0.15.0: + resolution: {integrity: sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==} + expo-keep-awake@15.0.8: resolution: {integrity: sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ==} peerDependencies: @@ -2244,6 +2290,11 @@ packages: expo: '*' react: '*' + expo-manifests@1.0.10: + resolution: {integrity: sha512-oxDUnURPcL4ZsOBY6X1DGWGuoZgVAFzp6PISWV7lPP2J0r8u1/ucuChBgpK7u1eLGFp6sDIPwXyEUCkI386XSQ==} + peerDependencies: + expo: '*' + expo-modules-autolinking@3.0.24: resolution: {integrity: sha512-TP+6HTwhL7orDvsz2VzauyQlXJcAWyU3ANsZ7JGL4DQu8XaZv/A41ZchbtAYLfozNA2Ya1Hzmhx65hXryBMjaQ==} hasBin: true @@ -2310,6 +2361,11 @@ packages: react: '*' react-native: '*' + expo-updates-interface@2.0.0: + resolution: {integrity: sha512-pTzAIufEZdVPKql6iMi5ylVSPqV1qbEopz9G6TSECQmnNde2nwq42PxdFBaUEd8IZJ/fdJLQnOT3m6+XJ5s7jg==} + peerDependencies: + expo: '*' + expo-web-browser@15.0.10: resolution: {integrity: sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg==} peerDependencies: @@ -2337,16 +2393,19 @@ packages: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: {integrity: sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=} fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: {integrity: sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} fbjs-css-vars@1.0.2: - resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==} + resolution: {integrity: sha1-IWVRE2rgL+JVkyw+yHdfGOLAeLg=} fbjs@3.0.5: resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} @@ -2365,15 +2424,15 @@ packages: engines: {node: '>=8'} filter-obj@1.1.0: - resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + resolution: {integrity: sha1-mzERErxsYSehbgFsbF1/GeCAXFs=} engines: {node: '>=0.10.0'} finalhandler@1.1.2: - resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + resolution: {integrity: sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=} engines: {node: '>= 0.8'} find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + resolution: {integrity: sha1-l6/n1s3AvFkoWEt8jXsW6KmqXRk=} engines: {node: '>=8'} flow-enums-runtime@0.0.6: @@ -2391,11 +2450,11 @@ packages: engines: {node: '>=8'} fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} engines: {node: '>= 0.6'} fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -2410,11 +2469,11 @@ packages: engines: {node: '>= 0.4'} gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + resolution: {integrity: sha1-MqbudsPX9S1GsrGuXZP+qFgKJeA=} engines: {node: '>=6.9.0'} get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + resolution: {integrity: sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=} engines: {node: 6.* || 8.* || >= 10.*} get-intrinsic@1.3.0: @@ -2426,7 +2485,7 @@ packages: engines: {node: '>=6'} get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + resolution: {integrity: sha1-jeLYA8/0TfO8bEVuZmizbDkm4Ro=} engines: {node: '>=8.0.0'} get-proto@1.0.1: @@ -2446,7 +2505,7 @@ packages: deprecated: Glob versions prior to v9 are no longer supported global-dirs@0.1.1: - resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + resolution: {integrity: sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=} engines: {node: '>=4'} gopd@1.2.0: @@ -2457,11 +2516,11 @@ packages: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} engines: {node: '>=4'} has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + resolution: {integrity: sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=} engines: {node: '>=8'} has-property-descriptors@1.0.2: @@ -2491,6 +2550,9 @@ packages: hermes-parser@0.32.0: resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha1-7OCsr3HWLClpwuxZ/v9CpLGoW0U=} + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -2518,7 +2580,7 @@ packages: optional: true ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + resolution: {integrity: sha1-jrehCmP/8l0VpXsAFYbRd9Gw01I=} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} @@ -2534,15 +2596,15 @@ packages: engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} engines: {node: '>=0.8.19'} inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + resolution: {integrity: sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -2551,7 +2613,7 @@ packages: resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + resolution: {integrity: sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=} is-arguments@1.2.0: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} @@ -2577,7 +2639,7 @@ packages: hasBin: true is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + resolution: {integrity: sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=} engines: {node: '>=8'} is-generator-function@1.1.2: @@ -2589,11 +2651,11 @@ packages: engines: {node: '>= 0.4'} is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + resolution: {integrity: sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=} engines: {node: '>=0.12.0'} is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + resolution: {integrity: sha1-ReQuN/zPH0Dajl927iFRWEDAkoc=} engines: {node: '>=8'} is-regex@1.2.1: @@ -2605,11 +2667,11 @@ packages: engines: {node: '>= 0.4'} is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + resolution: {integrity: sha1-dKTHbnfKn9P5MvKQwX6jJs0VcnE=} engines: {node: '>=8'} isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} @@ -2659,7 +2721,7 @@ packages: resolution: {integrity: sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==} js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + resolution: {integrity: sha1-GSA/tZmR35jjoocFDUZHzerzJJk=} js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} @@ -2678,7 +2740,10 @@ packages: hasBin: true json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + resolution: {integrity: sha1-fEeAWpQxmSjgV3dAXcEuH3pO4C0=} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} @@ -2686,7 +2751,7 @@ packages: hasBin: true kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + resolution: {integrity: sha1-p5yezIbuHOP6YgbRIWxQHxR/wH4=} engines: {node: '>=6'} lan-network@0.1.7: @@ -2694,7 +2759,7 @@ packages: hasBin: true leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + resolution: {integrity: sha1-d4kd6DQGTMy6gq54QrtrFKE+1/I=} engines: {node: '>=6'} lighthouse-logger@1.4.2: @@ -2735,24 +2800,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -2774,25 +2843,25 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + resolution: {integrity: sha1-Gvujlq/WdqbUJQTQpno6frn2KqA=} engines: {node: '>=8'} lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=} lodash.throttle@4.1.1: - resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + resolution: {integrity: sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=} log-symbols@2.2.0: - resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} + resolution: {integrity: sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=} engines: {node: '>=4'} loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + resolution: {integrity: sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=} hasBin: true lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + resolution: {integrity: sha1-b6I3xj29xKgsoP2ILkci3F5jTig=} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2802,7 +2871,7 @@ packages: engines: {node: 20 || >=22} lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + resolution: {integrity: sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2818,7 +2887,7 @@ packages: engines: {node: '>= 0.4'} mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + resolution: {integrity: sha1-cRP8QoGRfWPOKbQ0RvcB5owlulA=} mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -2833,11 +2902,11 @@ packages: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} merge-options@3.0.4: - resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} + resolution: {integrity: sha1-hHCcKqKkskwZgfZsF5/lVlzG27c=} engines: {node: '>=10'} merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + resolution: {integrity: sha1-UoI2KaFN0AyXcPtq1H3GMQ8sH2A=} metro-babel-transformer@0.83.3: resolution: {integrity: sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==} @@ -2914,12 +2983,12 @@ packages: engines: {node: '>= 0.6'} mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + resolution: {integrity: sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=} engines: {node: '>=4'} hasBin: true mimic-fn@1.2.0: - resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + resolution: {integrity: sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=} engines: {node: '>=4'} minimatch@10.1.1: @@ -2945,18 +3014,18 @@ packages: engines: {node: '>= 18'} mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + resolution: {integrity: sha1-PrXtYmInVteaXw4qIh3+utdcL34=} engines: {node: '>=10'} hasBin: true ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: {integrity: sha1-V0yBOM4dK1hh8LRFedut1gxmFbI=} mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + resolution: {integrity: sha1-lQCAV6Vsr63CvGPd5/n/aVWUjjI=} nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} @@ -2972,10 +3041,10 @@ packages: engines: {node: '>= 0.6'} nested-error-stacks@2.0.1: - resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + resolution: {integrity: sha1-0syfxSNd2zcfxE1QYjQznI5LCks=} no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + resolution: {integrity: sha1-02H9XJgA9VhVGoNp/A3NRmK2Ek0=} node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -2991,13 +3060,13 @@ packages: engines: {node: '>= 6.13.0'} node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + resolution: {integrity: sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=} node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + resolution: {integrity: sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=} engines: {node: '>=0.10.0'} npm-package-arg@11.0.3: @@ -3008,14 +3077,14 @@ packages: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} nullthrows@1.1.1: - resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + resolution: {integrity: sha1-eBgliEOFaulx6uQgitfX6xmkMbE=} ob1@0.83.3: resolution: {integrity: sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==} engines: {node: '>=20.19.4'} object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} engines: {node: '>=0.10.0'} object-is@1.1.6: @@ -3023,7 +3092,7 @@ packages: engines: {node: '>= 0.4'} object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + resolution: {integrity: sha1-HEfyct8nfzsdrwYWd9nILiMixg4=} engines: {node: '>= 0.4'} object.assign@4.1.7: @@ -3034,7 +3103,7 @@ packages: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} on-finished@2.3.0: - resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + resolution: {integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=} engines: {node: '>= 0.8'} on-finished@2.4.1: @@ -3046,10 +3115,10 @@ packages: engines: {node: '>= 0.8'} once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} onetime@2.0.1: - resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + resolution: {integrity: sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=} engines: {node: '>=4'} open@7.4.2: @@ -3061,27 +3130,27 @@ packages: engines: {node: '>=12'} ora@3.4.0: - resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} + resolution: {integrity: sha1-vwdSSRBZo+8+1MhQl1Md6f280xg=} engines: {node: '>=6'} p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + resolution: {integrity: sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE=} engines: {node: '>=6'} p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + resolution: {integrity: sha1-4drMvnjQ0TiMoYxk/qOOPlfjcGs=} engines: {node: '>=10'} p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + resolution: {integrity: sha1-o0KLtwiLOmApL2aRkni3wpetTwc=} engines: {node: '>=8'} p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + resolution: {integrity: sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=} engines: {node: '>=6'} parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + resolution: {integrity: sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=} engines: {node: '>=6'} parse-json@5.2.0: @@ -3093,22 +3162,22 @@ packages: engines: {node: '>=10'} parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + resolution: {integrity: sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=} engines: {node: '>= 0.8'} path-dirname@1.0.2: - resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} + resolution: {integrity: sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=} path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + resolution: {integrity: sha1-UTvb4tO5XXdi6METfvoZXGxhtbM=} engines: {node: '>=8'} path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + resolution: {integrity: sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U=} engines: {node: '>=8'} path-parse@1.0.7: @@ -3119,7 +3188,7 @@ packages: engines: {node: 20 || >=22} path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + resolution: {integrity: sha1-hO0BwKe6OAr+CdkKjBgNzZ0DBDs=} engines: {node: '>=8'} pathe@2.0.3: @@ -3149,14 +3218,9 @@ packages: engines: {node: '>=10.4.0'} pngjs@3.4.0: - resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + resolution: {integrity: sha1-mcp9clll+2VYFOr2XzjxK72/VV8=} engines: {node: '>=4.0.0'} - pnpm@10.28.2: - resolution: {integrity: sha512-QYcvA3rSL3NI47Heu69+hnz9RI8nJtnPdMCPGVB8MdLI56EVJbmD/rwt9kC1Q43uYCPrsfhO1DzC1lTSvDJiZA==} - engines: {node: '>=18.12'} - hasBin: true - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3185,11 +3249,11 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + resolution: {integrity: sha1-foz42PW48jnBvGi+tOt4Vn1XLvg=} engines: {node: '>=0.4.0'} promise@7.3.1: - resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + resolution: {integrity: sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=} promise@8.3.0: resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} @@ -3203,7 +3267,7 @@ packages: engines: {node: '>=6'} qrcode-terminal@0.11.0: - resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} + resolution: {integrity: sha1-/8bCii/Av7RwUrR+I/T0RqX7254=} hasBin: true query-string@7.1.3: @@ -3214,11 +3278,11 @@ packages: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + resolution: {integrity: sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=} engines: {node: '>= 0.6'} rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + resolution: {integrity: sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=} hasBin: true react-devtools-core@6.1.5: @@ -3254,12 +3318,21 @@ packages: typescript: optional: true + react-is@16.13.1: + resolution: {integrity: sha1-eJcppNw23imZ3BVt1sHZwYzqVqQ=} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} + react-native-gesture-handler@2.30.0: + resolution: {integrity: sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA==} + peerDependencies: + react: '*' + react-native: '*' + react-native-is-edge-to-edge@1.2.1: resolution: {integrity: sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==} peerDependencies: @@ -3370,7 +3443,7 @@ packages: engines: {node: '>=4'} regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + resolution: {integrity: sha1-uTRtiCfo9aMve6KWN9OYtpAUhIo=} regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -3387,11 +3460,11 @@ packages: hasBin: true require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} engines: {node: '>=0.10.0'} require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + resolution: {integrity: sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk=} engines: {node: '>=0.10.0'} requireg@0.2.2: @@ -3399,15 +3472,15 @@ packages: engines: {node: '>= 4.0.0'} resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + resolution: {integrity: sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=} engines: {node: '>=4'} resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + resolution: {integrity: sha1-w1IlhD3493bfIcV1V7wIfp39/Gk=} engines: {node: '>=8'} resolve-global@1.0.0: - resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + resolution: {integrity: sha1-oqed9K8so/Sb93753azTItrRklU=} engines: {node: '>=8'} resolve-workspace-root@2.0.1: @@ -3423,14 +3496,14 @@ packages: hasBin: true resolve@1.7.1: - resolution: {integrity: sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==} + resolution: {integrity: sha1-qt1lY3T9KYruiVvAJrgpdBhnf9M=} restore-cursor@2.0.0: - resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + resolution: {integrity: sha1-n37ih/gv0ybU/RYpI9YhKe7g368=} engines: {node: '>=4'} rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + resolution: {integrity: sha1-8aVAK6YiCtUswSgrrBrjqkn9Bho=} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true @@ -3443,7 +3516,7 @@ packages: resolution: {integrity: sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==} safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + resolution: {integrity: sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=} safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} @@ -3480,7 +3553,7 @@ packages: engines: {node: '>= 0.8.0'} serialize-error@2.1.0: - resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + resolution: {integrity: sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=} engines: {node: '>=0.10.0'} serve-static@1.16.3: @@ -3495,24 +3568,24 @@ packages: engines: {node: '>= 0.4'} setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + resolution: {integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=} setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + resolution: {integrity: sha1-ZsmiSnP5/CjL5msJ/tPTPcrxtCQ=} sf-symbols-typescript@2.2.0: resolution: {integrity: sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==} engines: {node: '>=10'} shallowequal@1.1.0: - resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + resolution: {integrity: sha1-GI1SHelbkIdAT9TctosT3wrk5/g=} shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + resolution: {integrity: sha1-zNCvT4g1+9wmW4JGGq8MNmY/NOo=} engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + resolution: {integrity: sha1-rhbxZE2HPsrYQ7AwexQzYtTEIXI=} engines: {node: '>=8'} shell-quote@1.8.3: @@ -3532,10 +3605,10 @@ packages: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + resolution: {integrity: sha1-E01oEpd1ZDfMBcoBNw06elcQde0=} slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + resolution: {integrity: sha1-ZTm+hwwWWtvVJAIg2+Nh8bxNRjQ=} engines: {node: '>=8'} slugify@1.6.6: @@ -3553,19 +3626,19 @@ packages: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} engines: {node: '>=0.10.0'} source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + resolution: {integrity: sha1-dHIq8y6WFOnCh6jQu95IteLxomM=} engines: {node: '>=0.10.0'} split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + resolution: {integrity: sha1-9hCv7uOxK84dDDBCXnY5i3gkml8=} engines: {node: '>=6'} sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} @@ -3582,7 +3655,7 @@ packages: engines: {node: '>=6'} statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} engines: {node: '>= 0.6'} statuses@2.0.2: @@ -3593,11 +3666,11 @@ packages: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} stream-buffers@2.2.0: - resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} + resolution: {integrity: sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=} engines: {node: '>= 0.10.0'} strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + resolution: {integrity: sha1-ucczDHBChi9rFC3CdLvMWGbONUY=} engines: {node: '>=4'} string-width@4.2.3: @@ -3605,7 +3678,7 @@ packages: engines: {node: '>=8'} strip-ansi@5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + resolution: {integrity: sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=} engines: {node: '>=6'} strip-ansi@6.0.1: @@ -3613,7 +3686,7 @@ packages: engines: {node: '>=8'} strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} engines: {node: '>=0.10.0'} structured-headers@0.4.1: @@ -3628,11 +3701,11 @@ packages: hasBin: true supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + resolution: {integrity: sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=} engines: {node: '>=4'} supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + resolution: {integrity: sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=} engines: {node: '>=8'} supports-color@8.1.1: @@ -3648,7 +3721,7 @@ packages: engines: {node: '>= 0.4'} svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + resolution: {integrity: sha1-/cLinhOVFzYUC3bLEiyO5mMOtrU=} svgo@3.3.2: resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} @@ -3660,11 +3733,11 @@ packages: engines: {node: '>=18'} temp-dir@2.0.0: - resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + resolution: {integrity: sha1-vekrBb3+sVFugEycAK1FF38xMh4=} engines: {node: '>=8'} terminal-link@2.1.1: - resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + resolution: {integrity: sha1-FKZKJ6s8Dfkz6lRvulXy0HjtyZQ=} engines: {node: '>=8'} terser@5.46.0: @@ -3673,18 +3746,18 @@ packages: hasBin: true test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + resolution: {integrity: sha1-BKhphmHYBepvopO2y55jrARO8V4=} engines: {node: '>=8'} thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + resolution: {integrity: sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=} engines: {node: '>=0.8'} thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + resolution: {integrity: sha1-iTLmhqQGYDigFt2eLKRq3Zg4qV8=} throat@5.0.0: - resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + resolution: {integrity: sha1-xRmSNYA6rRh1SmZ9ZZtecs4Wdks=} tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -3705,7 +3778,7 @@ packages: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + resolution: {integrity: sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=} engines: {node: '>=8.0'} toidentifier@1.0.1: @@ -3713,7 +3786,7 @@ packages: engines: {node: '>=0.6'} tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -3722,7 +3795,7 @@ packages: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + resolution: {integrity: sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=} engines: {node: '>=4'} type-fest@0.21.3: @@ -3730,7 +3803,7 @@ packages: engines: {node: '>=10'} type-fest@0.7.1: - resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + resolution: {integrity: sha1-jdpl/q8D7Xjwo/lnjxhpFH98XEg=} engines: {node: '>=8'} typescript@5.9.3: @@ -3766,11 +3839,11 @@ packages: engines: {node: '>=4'} unique-string@2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + resolution: {integrity: sha1-OcZFH4GvsnSd4rIz4/fF6IQ72J0=} engines: {node: '>=8'} unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} engines: {node: '>= 0.8'} update-browserslist-db@1.2.3: @@ -3813,11 +3886,11 @@ packages: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=} engines: {node: '>= 0.4.0'} uuid@7.0.3: - resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + resolution: {integrity: sha1-xcnyyM8l3Ao3LE3xRBxB9b0MaAs=} hasBin: true validate-npm-package-name@5.0.1: @@ -3825,7 +3898,7 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} engines: {node: '>= 0.8'} vaul@1.1.2: @@ -3909,10 +3982,10 @@ packages: optional: true vlq@1.0.1: - resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + resolution: {integrity: sha1-wAP258C0we3WI/1u5Qu8DWod5Gg=} void-elements@3.1.0: - resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + resolution: {integrity: sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=} engines: {node: '>=0.10.0'} walker@1.0.8: @@ -3922,13 +3995,13 @@ packages: resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=} webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} webidl-conversions@5.0.0: - resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} + resolution: {integrity: sha1-rlnIoAsSFUOirMZcBDT1ew/BGv8=} engines: {node: '>=8'} whatwg-fetch@3.6.20: @@ -3939,14 +4012,14 @@ packages: engines: {node: '>=10'} whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} which-typed-array@1.1.20: resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + resolution: {integrity: sha1-fGqN0KY2oDJ+ELWckobu6T8/UbE=} engines: {node: '>= 8'} hasBin: true @@ -3959,11 +4032,11 @@ packages: resolution: {integrity: sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==} wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + resolution: {integrity: sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM=} engines: {node: '>=10'} wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} @@ -4005,7 +4078,7 @@ packages: optional: true xcode@3.0.1: - resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} + resolution: {integrity: sha1-PvtiqsZBqyxwJFj5oDAmlhRqpTw=} engines: {node: '>=10.0.0'} xml2js@0.6.0: @@ -4013,11 +4086,11 @@ packages: engines: {node: '>=4.0.0'} xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + resolution: {integrity: sha1-vpuuHIoEbnazESdyY0fQrXACvrM=} engines: {node: '>=4.0'} xmlbuilder@15.1.1: - resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + resolution: {integrity: sha1-nc3OSe6mbY0QtCyulKecPI0MLsU=} engines: {node: '>=8.0'} y18n@5.0.8: @@ -4025,7 +4098,7 @@ packages: engines: {node: '>=10'} yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + resolution: {integrity: sha1-27fa+b/YusmrRev2ArjLrQ1dCP0=} yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} @@ -4045,7 +4118,7 @@ packages: engines: {node: '>=12'} yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + resolution: {integrity: sha1-ApTrPe4FAo0x7hpfosVWpqrxChs=} engines: {node: '>=10'} snapshots: @@ -4662,6 +4735,10 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@egjs/hammerjs@2.0.17': + dependencies: + '@types/hammerjs': 2.0.46 + '@esbuild/aix-ppc64@0.27.2': optional: true @@ -4807,7 +4884,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.19.0 optionalDependencies: - expo-router: 6.0.22(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native-reanimated@4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-router: 6.0.22(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.30.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - bufferutil @@ -5723,6 +5800,8 @@ snapshots: dependencies: '@types/node': 25.1.0 + '@types/hammerjs@2.0.46': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -5817,6 +5896,13 @@ snapshots: agent-base@7.1.4: {} + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + anser@1.4.10: {} ansi-escapes@4.3.2: @@ -6451,6 +6537,35 @@ snapshots: transitivePeerDependencies: - supports-color + expo-dev-client@6.0.20(expo@54.0.32): + dependencies: + expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-dev-launcher: 6.0.20(expo@54.0.32) + expo-dev-menu: 7.0.18(expo@54.0.32) + expo-dev-menu-interface: 2.0.0(expo@54.0.32) + expo-manifests: 1.0.10(expo@54.0.32) + expo-updates-interface: 2.0.0(expo@54.0.32) + transitivePeerDependencies: + - supports-color + + expo-dev-launcher@6.0.20(expo@54.0.32): + dependencies: + ajv: 8.17.1 + expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-dev-menu: 7.0.18(expo@54.0.32) + expo-manifests: 1.0.10(expo@54.0.32) + transitivePeerDependencies: + - supports-color + + expo-dev-menu-interface@2.0.0(expo@54.0.32): + dependencies: + expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + + expo-dev-menu@7.0.18(expo@54.0.32): + dependencies: + expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-dev-menu-interface: 2.0.0(expo@54.0.32) + expo-file-system@19.0.21(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0)): dependencies: expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -6463,6 +6578,8 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) + expo-json-utils@0.15.0: {} + expo-keep-awake@15.0.8(expo@54.0.32)(react@19.1.0): dependencies: expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -6490,6 +6607,14 @@ snapshots: react: 19.1.0 rtl-detect: 1.1.2 + expo-manifests@1.0.10(expo@54.0.32): + dependencies: + '@expo/config': 12.0.13 + expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-json-utils: 0.15.0 + transitivePeerDependencies: + - supports-color + expo-modules-autolinking@3.0.24: dependencies: '@expo/spawn-async': 1.7.2 @@ -6519,7 +6644,7 @@ snapshots: transitivePeerDependencies: - supports-color - expo-router@6.0.22(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native-reanimated@4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-router@6.0.22(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.30.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@expo/metro-runtime': 6.1.2(expo@54.0.32)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@expo/schema-utils': 0.1.8 @@ -6553,6 +6678,7 @@ snapshots: vaul: 1.1.2(@types/react@19.1.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) optionalDependencies: react-dom: 19.1.0(react@19.1.0) + react-native-gesture-handler: 2.30.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-reanimated: 4.1.6(@babel/core@7.28.6)(react-native-worklets@0.5.1(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: @@ -6576,6 +6702,10 @@ snapshots: react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-updates-interface@2.0.0(expo@54.0.32): + dependencies: + expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-web-browser@15.0.10(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0)): dependencies: expo: 54.0.32(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(expo-router@6.0.22)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -6622,6 +6752,8 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-uri@3.1.0: {} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -6769,6 +6901,10 @@ snapshots: dependencies: hermes-estree: 0.32.0 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -6988,6 +7124,8 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@1.0.0: {} + json5@2.2.3: {} kleur@3.0.3: {} @@ -7502,8 +7640,6 @@ snapshots: pngjs@3.4.0: {} - pnpm@10.28.2: {} - possible-typed-array-names@1.1.0: {} postcss-value-parser@4.2.0: {} @@ -7600,10 +7736,20 @@ snapshots: react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) typescript: 5.9.3 + react-is@16.13.1: {} + react-is@18.3.1: {} react-is@19.2.4: {} + react-native-gesture-handler@2.30.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + dependencies: + '@egjs/hammerjs': 2.0.17 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) + react-native-is-edge-to-edge@1.2.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 diff --git a/client/src/i18n/locales/zh-CN.json b/client/src/i18n/locales/zh-CN.json index 2e9aab6..41a300e 100644 --- a/client/src/i18n/locales/zh-CN.json +++ b/client/src/i18n/locales/zh-CN.json @@ -3,6 +3,7 @@ "ok": "确定", "cancel": "取消", "error": "错误", + "notice": "提示", "openLinkError": "无法打开链接", "back": "返回" }, diff --git a/client/src/storage/appStorage.ts b/client/src/storage/appStorage.ts index 3d0f399..455038a 100644 --- a/client/src/storage/appStorage.ts +++ b/client/src/storage/appStorage.ts @@ -69,14 +69,29 @@ export async function setReaction(contentId: string, reaction: Reaction): Promis await setJson(KEY_CONTENT_REACTIONS, next); } -export async function getFavorites(): Promise { - return await getJson(KEY_FAVORITES_ITEMS, []); +export type FavoriteItem = { + id: string; + date: string; + themeMode: ThemeMode; + background: string; // 颜色值或图片路径 +}; + +export async function getFavorites(): Promise { + return await getJson(KEY_FAVORITES_ITEMS, []); } -export async function addFavorite(contentId: string): Promise { +export async function addFavorite(item: FavoriteItem): Promise { const list = await getFavorites(); - if (list.includes(contentId)) return; - await setJson(KEY_FAVORITES_ITEMS, [...list, contentId]); + if (list.some(i => i.id === item.id)) return; + const newList = [item, ...list]; + console.log('Adding to favorites, new list size:', newList.length); + await setJson(KEY_FAVORITES_ITEMS, newList); +} + +export async function removeFavorite(contentId: string): Promise { + const list = await getFavorites(); + const next = list.filter(item => item.id !== contentId); + await setJson(KEY_FAVORITES_ITEMS, next); } export async function getConsentAccepted(): Promise {