3 Commits

Author SHA1 Message Date
雷汀岚
ce018880f4 fix(i18n): 繁中開屏 consent 文案不生效 - 寫死繁中文案、清理腳本、文件
- splash: 繁中 consent 使用元件內 ZH_TW_CONSENT,避免 bundle 快取
- splash: 使用 isTraditionalChineseLocaleTag 判斷繁中
- i18n: 註解與 __DEV__ debug log
- package: clean:cache, start:clean, ios:clean, clean:ios-build
- ALL_COPY: 故障排除與 consent 只改 zh-TW.json 說明
- spec_kit: Splash Consent overflow 記錄排查結論

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:05:17 +08:00
雷汀岚
b4ec17fcac onboarding: 转场动画、名字步取消自动跳页、标题个性化招呼语、Skip/提醒步等文案与交互优化
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 17:33:53 +08:00
雷汀岚
aa4e1e9947 onboarding: 选项字体与问题一致、底部间距、提醒页文案与移除底部 Skip
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 17:11:13 +08:00
28 changed files with 2277 additions and 654 deletions

View File

@@ -56,9 +56,6 @@ jobs:
# 健康检查路径(未配置则默认 /health如果你没有 health 接口,可改为 /docs 或 /
HEALTHCHECK_PATH: ${{ vars.HEALTHCHECK_PATH }}
# 定时服务健康检查路径(检查 Redis/Worker/Beat未配置则默认 /v1/push/scheduler/health
SCHEDULER_HEALTHCHECK_PATH: ${{ vars.SCHEDULER_HEALTHCHECK_PATH }}
# 可选:远端 env 文件路径(例如 /opt/mindfulness-server/.env.prod存在则 docker run --env-file
REMOTE_ENV_FILE: ${{ vars.REMOTE_ENV_FILE }}
@@ -261,12 +258,11 @@ jobs:
GREEN_PORT="${GREEN_PORT:-8002}"
CONTAINER_PORT="${CONTAINER_PORT:-8000}"
HEALTHCHECK_PATH="${HEALTHCHECK_PATH:-/health}"
SCHEDULER_HEALTHCHECK_PATH="${SCHEDULER_HEALTHCHECK_PATH:-/v1/push/scheduler/health}"
echo "准备部署:${DOCKER_IMAGE}:${DEPLOY_TAG} -> ${DEPLOY_ENV} (${SSH_USER}@${SSH_HOST}:${SSH_PORT})"
ssh -p "${SSH_PORT}" -i ~/.ssh/id_rsa -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes "${SSH_USER}@${SSH_HOST}" bash -s -- \
"${DOCKER_IMAGE}" "${DEPLOY_TAG}" "${NGINX_UPSTREAM_FILE}" "${NGINX_UPSTREAM_NAME}" "${BLUE_PORT}" "${GREEN_PORT}" "${CONTAINER_PORT}" "${HEALTHCHECK_PATH}" "${SCHEDULER_HEALTHCHECK_PATH}" "${REMOTE_ENV_FILE:-}" <<'REMOTE'
"${DOCKER_IMAGE}" "${DEPLOY_TAG}" "${NGINX_UPSTREAM_FILE}" "${NGINX_UPSTREAM_NAME}" "${BLUE_PORT}" "${GREEN_PORT}" "${CONTAINER_PORT}" "${HEALTHCHECK_PATH}" "${REMOTE_ENV_FILE:-}" <<'REMOTE'
set -euo pipefail
IMAGE="$1"
@@ -277,8 +273,7 @@ jobs:
GREEN_PORT="$6"
CONTAINER_PORT="$7"
HEALTHCHECK_PATH="$8"
SCHEDULER_HEALTHCHECK_PATH="$9"
REMOTE_ENV_FILE="${10}"
REMOTE_ENV_FILE="$9"
APP_DIR="/opt/mindfulness-server"
ACTIVE_FILE="${APP_DIR}/active_color"
@@ -312,18 +307,9 @@ jobs:
# 拉取镜像
${SUDO} docker pull "${IMAGE}:${TAG}"
# 启动新颜色容器API + Worker + Beat
API_NAME="mindfulness-server-api-${NEW_COLOR}"
WORKER_NAME="mindfulness-server-worker-${NEW_COLOR}"
BEAT_NAME="mindfulness-server-beat-${NEW_COLOR}"
# 兼容旧命名:之前可能只有一个 mindfulness-server-blue/green
# 启动新颜色容器
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "${API_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "${WORKER_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "${BEAT_NAME}" >/dev/null 2>&1 || true
ENV_FILE_ARGS=()
if [[ -n "${REMOTE_ENV_FILE}" && -f "${REMOTE_ENV_FILE}" ]]; then
ENV_FILE_ARGS=(--env-file "${REMOTE_ENV_FILE}")
@@ -332,29 +318,11 @@ jobs:
echo "提示REMOTE_ENV_FILE 已配置但文件不存在:${REMOTE_ENV_FILE}(将忽略 env-file"
fi
# API对外暴露端口仅该容器参与蓝绿切流
${SUDO} docker run -d \
--name "${API_NAME}" \
--name "mindfulness-server-${NEW_COLOR}" \
--restart=always \
-p "${NEW_PORT}:${CONTAINER_PORT}" \
"${ENV_FILE_ARGS[@]}" \
-e START_API=1 -e START_WORKER=0 -e START_BEAT=0 \
"${IMAGE}:${TAG}"
# Worker处理异步/ETA 任务,不暴露端口)
${SUDO} docker run -d \
--name "${WORKER_NAME}" \
--restart=always \
"${ENV_FILE_ARGS[@]}" \
-e START_API=0 -e START_WORKER=1 -e START_BEAT=0 \
"${IMAGE}:${TAG}"
# Beat定时调度不暴露端口
${SUDO} docker run -d \
--name "${BEAT_NAME}" \
--restart=always \
"${ENV_FILE_ARGS[@]}" \
-e START_API=0 -e START_WORKER=0 -e START_BEAT=1 \
"${IMAGE}:${TAG}"
# 健康检查
@@ -382,37 +350,8 @@ jobs:
if [[ "$i" -eq 30 ]]; then
echo "健康检查失败:新版本未就绪,回滚并退出"
${SUDO} docker logs --tail 200 "${API_NAME}" || true
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
exit 1
fi
sleep 2
done
# 定时服务健康检查(确保 Redis/Worker/Beat 都 OK避免“接口正常但定时任务没跑”
if [[ "${SCHEDULER_HEALTHCHECK_PATH}" != /* ]]; then
SCHEDULER_HEALTHCHECK_PATH="/${SCHEDULER_HEALTHCHECK_PATH}"
fi
SCHED_URL="http://127.0.0.1:${NEW_PORT}${SCHEDULER_HEALTHCHECK_PATH}"
echo "定时服务健康检查:${SCHED_URL}"
# Beat 心跳是按分钟刷新,这里最多等 90 秒45*2s
for i in $(seq 1 45); do
RES="$(curl -fsS "${SCHED_URL}" 2>/dev/null || true)"
if [[ -n "${RES}" ]] \
&& echo "${RES}" | grep -q '"redis":{"ok":true' \
&& echo "${RES}" | grep -q '"worker":{"ok":true' \
&& echo "${RES}" | grep -q '"beat":{"ok":true' ; then
echo "定时服务健康检查通过:${RES}"
break
fi
if [[ "$i" -eq 45 ]]; then
echo "定时服务健康检查失败:${RES}"
echo "Worker/Beat 日志(各 120 行):"
${SUDO} docker logs --tail 120 "${WORKER_NAME}" || true
${SUDO} docker logs --tail 120 "${BEAT_NAME}" || true
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
${SUDO} docker logs --tail 200 "mindfulness-server-${NEW_COLOR}" || true
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" || true
exit 1
fi
sleep 2
@@ -421,24 +360,24 @@ jobs:
# 切换 Nginx upstream在同一个 conf 文件中通过 backup 做主备切换)
if [[ ! -f "${UPSTREAM_FILE}" ]]; then
echo "未找到 Nginx upstream 配置文件:${UPSTREAM_FILE}"
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" || true
exit 1
fi
if ! ${SUDO} grep -qE "upstream[[:space:]]+${UPSTREAM_NAME}[[:space:]]*\\{" "${UPSTREAM_FILE}"; then
echo "在 ${UPSTREAM_FILE} 中未找到 upstream${UPSTREAM_NAME}"
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" || true
exit 1
fi
if ! ${SUDO} grep -qE "server[[:space:]]+127\\.0\\.0\\.1:${BLUE_PORT}" "${UPSTREAM_FILE}"; then
echo "在 ${UPSTREAM_FILE} 中未找到 server 127.0.0.1:${BLUE_PORT}(请先按参考配置写入 upstream"
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" || true
exit 1
fi
if ! ${SUDO} grep -qE "server[[:space:]]+127\\.0\\.0\\.1:${GREEN_PORT}" "${UPSTREAM_FILE}"; then
echo "在 ${UPSTREAM_FILE} 中未找到 server 127.0.0.1:${GREEN_PORT}(请先按参考配置写入 upstream"
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" || true
exit 1
fi
@@ -477,7 +416,7 @@ jobs:
echo "Nginx 配置校验失败,回滚 upstream 配置并退出"
${SUDO} cp -f "${BACKUP_FILE}" "${UPSTREAM_FILE}" || true
${SUDO} nginx -t && ${SUDO} nginx -s reload || true
${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" || true
exit 1
fi
@@ -485,11 +424,6 @@ jobs:
echo "${NEW_COLOR}" | ${SUDO} tee "${ACTIVE_FILE}" >/dev/null
# 下线旧容器(切流后再停旧的)
OLD_API_NAME="mindfulness-server-api-${OLD_COLOR}"
OLD_WORKER_NAME="mindfulness-server-worker-${OLD_COLOR}"
OLD_BEAT_NAME="mindfulness-server-beat-${OLD_COLOR}"
${SUDO} docker rm -f "${OLD_API_NAME}" "${OLD_WORKER_NAME}" "${OLD_BEAT_NAME}" >/dev/null 2>&1 || true
# 兼容旧命名
${SUDO} docker rm -f "mindfulness-server-${OLD_COLOR}" >/dev/null 2>&1 || true
echo "部署完成:${NEW_COLOR} 已上线"

View File

@@ -175,18 +175,17 @@ export default function OnboardingScreen() {
}
};
const onSkip = async () => {
// 跳过整个 Onboarding仍生成一个“全跳过”的最小画像保证下游可用
const scoringProfile = buildUserProfileFromQuestionnaire({});
await setUserProfileScoring(scoringProfile);
// 同步到 App Group供 iOS Widget 使用(失败不阻塞)
await syncWidgetConfig();
await syncWidgetUserProfileFromScoring(scoringProfile);
// 标记已完成,避免下次启动再次进入 Onboarding
await setOnboardingCompleted(true);
router.replace('/(app)/home');
/** 只跳過當前這一步(不填/不選當前題,進入下一步) */
const handleSkipCurrentStep = () => {
if (currentStep.type === 'name') {
onNext();
} else if (currentStep.type === 'selection') {
setSelections((prev) => ({ ...prev, [currentStep.id]: [] }));
onNext();
} else if (currentStep.type === 'reminder') {
setReminderTimes(0);
onFinish();
}
};
// 题目为多选:点击切换选中状态
@@ -210,9 +209,10 @@ export default function OnboardingScreen() {
title={currentTitle}
currentStep={stepIndex}
totalSteps={STEPS.length - 1}
onSkip={onSkip}
onSkip={handleSkipCurrentStep}
onBack={onBack}
showBackButton={stepIndex > 0}
userName={name}
>
{currentStep.type === 'name' && (
<NameInputStep
@@ -233,7 +233,7 @@ export default function OnboardingScreen() {
{currentStep.type === 'reminder' && (
<ReminderStep
value={reminderTimes}
value={Math.max(1, reminderTimes)}
onChange={setReminderTimes}
onFinish={onFinish}
onSkip={() => {

View File

@@ -8,6 +8,7 @@ import { setConsentAccepted, getConsentAccepted } from '../../src/storage/appSto
import { fetchLegalLinks } from '@/src/services/legalApi';
import { getOnboardingCompleted } from '@/src/storage/appStorage';
import { API_BASE_URL } from '@/src/constants/env';
import { isTraditionalChineseLocaleTag } from '@/src/i18n/locale';
// 导入 SVG 组件
import FlowersBg from '../../assets/images/index/flowers_endbg.svg';
@@ -15,10 +16,30 @@ import WelcomeBtn from '../../assets/images/index/welcome_btn.svg';
const { width, height } = Dimensions.get('window');
// 繁中開屏 consent 文案:寫死在元件內,避免 Metro/iOS bundle 快取導致永遠顯示舊文案。
// 若需修改,請改這裡並同步 client/src/i18n/locales/zh-TW.json 的 consent 區塊。
const ZH_TW_CONSENT = {
title: '我們知道,',
subtitle: '當媽媽很不容易。',
subtitleSecondary: '這裡給你一些溫柔的肯定與提醒',
};
export default function SplashScreen() {
const router = useRouter();
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const [showConsent, setShowConsent] = useState(false);
// 繁中時強制使用上方常數(含 zh-TW / zh-Hant / zh-Hant-TW其餘用 i18n
const isZhTW = isTraditionalChineseLocaleTag(i18n.language || '');
const title = isZhTW ? ZH_TW_CONSENT.title : t('consent.title');
const subtitle = isZhTW ? ZH_TW_CONSENT.subtitle : t('consent.subtitle');
const subtitleSecondary = isZhTW ? ZH_TW_CONSENT.subtitleSecondary : t('consent.subtitleSecondary');
useEffect(() => {
if (typeof __DEV__ !== 'undefined' && __DEV__ && showConsent) {
console.log('[i18n consent] language=', i18n.language, 'title=', title, 'subtitle=', subtitle);
}
}, [showConsent, i18n.language, title, subtitle]);
const [links, setLinks] = useState<{ privacy?: string; terms?: string }>({});
const [linksLoading, setLinksLoading] = useState(false);
const mountedRef = useRef(true);
@@ -126,13 +147,16 @@ export default function SplashScreen() {
/>
</View>
{/* 文案内容 */}
{/* 文案内容:主標題兩行 + 可選二級標題(字號更小、顏色更淺);繁中為元件內常數,其餘用 i18n */}
<View style={[styles.contentContainer, { position: 'absolute', top: contentTop }]}>
<Text style={styles.titleText}>
{t('consent.title')}
{title}
{'\n'}
{t('consent.subtitle')}
{subtitle}
</Text>
{subtitleSecondary ? (
<Text style={styles.consentSubtitleSecondary}>{subtitleSecondary}</Text>
) : null}
</View>
<SafeAreaView style={styles.bottomContainer} edges={['bottom']}>
@@ -213,6 +237,14 @@ const styles = StyleSheet.create({
fontWeight: '600',
fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif',
},
consentSubtitleSecondary: {
marginTop: 12,
fontSize: 16,
lineHeight: 22,
color: 'rgba(119, 47, 0, 0.6)',
textAlign: 'center',
fontFamily: Platform.OS === 'ios' ? 'STIX Two Text' : 'serif',
},
bottomContainer: {
position: 'absolute',
bottom: 60,

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
import { useTranslation } from 'react-i18next';
import { SerifText } from './SerifText';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme';
export const INTENTS = [
{ id: 'love', labelKey: 'intent.love', icon: '❤️' },
@@ -20,7 +19,7 @@ export function IntentSelectionStep({ selectedIds, onToggle }: IntentSelectionSt
const { t } = useTranslation();
return (
<View style={styles.container}>
<SerifText style={styles.title}>{t('intent.title')}</SerifText>
<Text style={styles.title}>{t('intent.title')}</Text>
<View style={styles.grid}>
{INTENTS.map((intent) => {
@@ -35,10 +34,10 @@ export function IntentSelectionStep({ selectedIds, onToggle }: IntentSelectionSt
onPress={() => onToggle(intent.id)}
activeOpacity={0.7}
>
<SerifText style={styles.icon}>{intent.icon}</SerifText>
<SerifText style={[styles.label, isSelected && styles.labelSelected]}>
<Text style={styles.icon}>{intent.icon}</Text>
<Text style={[styles.label, isSelected && styles.labelSelected]}>
{t(intent.labelKey)}
</SerifText>
</Text>
</TouchableOpacity>
);
})}
@@ -56,6 +55,8 @@ const styles = StyleSheet.create({
fontSize: 24,
marginBottom: 40,
textAlign: 'center',
fontFamily: OnboardingFont.question,
color: OnboardingColors.textPrimary,
},
grid: {
flexDirection: 'row',
@@ -86,10 +87,12 @@ const styles = StyleSheet.create({
},
icon: {
fontSize: 32,
fontFamily: OnboardingFont.question,
},
label: {
fontSize: 18,
color: OnboardingColors.textPrimary,
fontFamily: OnboardingFont.question,
},
labelSelected: {
fontWeight: 'bold',

View File

@@ -95,8 +95,7 @@ export function NameInputStep({ value, onChangeText, onNext }: NameInputStepProp
blurOnSubmit={true}
onSubmitEditing={() => {
Keyboard.dismiss();
// 有输入时,“完成”直接进入下一步,避免真机卡在键盘上
if (value.trim().length > 0) onNext();
// 不再自動跳頁,僅收起鍵盤;前進需點擊底部 ➡️
}}
/>
</View>

View File

@@ -1,7 +1,10 @@
import React from 'react';
import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar, Text, Image, Platform } from 'react-native';
import React, { useRef, useEffect } from 'react';
import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar, Text, Image, Platform, Animated, Easing } from 'react-native';
import { useTranslation } from 'react-i18next';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme';
const TRANSITION_OFFSET = 24;
const TRANSITION_DURATION = 280;
interface OnboardingLayoutProps {
children: React.ReactNode;
@@ -11,6 +14,8 @@ interface OnboardingLayoutProps {
onSkip: () => void;
onBack?: () => void;
showBackButton?: boolean;
/** 用户名字仅在名字步骤之后的第一个问题currentStep === 1且非空时显示招呼语 */
userName?: string;
}
export function OnboardingLayout({
@@ -20,9 +25,48 @@ export function OnboardingLayout({
totalSteps,
onSkip,
onBack,
showBackButton = false
showBackButton = false,
userName = '',
}: OnboardingLayoutProps) {
const { t } = useTranslation();
const showGreeting = currentStep === 1 && userName.trim().length > 0;
const displayName = userName.trim();
const prevStepRef = useRef(currentStep);
const isFirstRenderRef = useRef(true);
const translateX = useRef(new Animated.Value(0)).current;
const opacity = useRef(new Animated.Value(1)).current;
useEffect(() => {
if (isFirstRenderRef.current) {
isFirstRenderRef.current = false;
prevStepRef.current = currentStep;
return;
}
if (prevStepRef.current === currentStep) return;
const direction = currentStep > prevStepRef.current ? 'forward' : 'back';
prevStepRef.current = currentStep;
const startX = direction === 'forward' ? TRANSITION_OFFSET : -TRANSITION_OFFSET;
translateX.setValue(startX);
opacity.setValue(0.72);
Animated.parallel([
Animated.timing(translateX, {
toValue: 0,
duration: TRANSITION_DURATION,
useNativeDriver: true,
easing: Easing.out(Easing.cubic),
}),
Animated.timing(opacity, {
toValue: 1,
duration: TRANSITION_DURATION,
useNativeDriver: true,
easing: Easing.out(Easing.cubic),
}),
]).start();
}, [currentStep, translateX, opacity]);
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
@@ -49,16 +93,29 @@ export function OnboardingLayout({
</TouchableOpacity>
</View>
{/* Title & Progress Row */}
{/* Title & Progress Row(名字步骤后第一步且名字非空时显示招呼语 + 问题) */}
<View style={styles.titleRow}>
<Text style={styles.questionTitle}>{title}</Text>
<View style={styles.titleBlock}>
{showGreeting && (
<Text style={styles.greetingText}>{t('onboardingSurvey.greeting', { name: displayName })}</Text>
)}
<Text style={styles.questionTitle}>{title}</Text>
</View>
<Text style={styles.progressText}>({currentStep}/{totalSteps})</Text>
</View>
{/* Content */}
<View style={styles.content}>
{/* Contentstep 切换时滑动 + 淡入 */}
<Animated.View
style={[
styles.content,
{
opacity,
transform: [{ translateX }],
},
]}
>
{children}
</View>
</Animated.View>
</SafeAreaView>
</View>
);
@@ -114,18 +171,27 @@ const styles = StyleSheet.create({
alignItems: 'flex-end',
paddingHorizontal: 20,
marginTop: 20,
marginBottom: 20,
marginBottom: 8,
},
titleBlock: {
flex: 1,
justifyContent: 'flex-end',
},
greetingText: {
fontSize: 22,
color: OnboardingColors.questionTitle,
fontFamily: OnboardingFont.question,
marginBottom: 4,
},
questionTitle: {
fontSize: 22,
color: OnboardingColors.questionTitle,
fontFamily: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif',
flex: 1,
fontFamily: OnboardingFont.question,
},
progressText: {
fontSize: 18,
color: OnboardingColors.textProgress,
fontFamily: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif',
fontFamily: OnboardingFont.question,
marginLeft: 10,
},
content: {

View File

@@ -11,16 +11,16 @@ interface ReminderStepProps {
value: number;
onChange: (value: number) => void;
onFinish: () => void;
onSkip: () => void;
onSkip?: () => void;
}
export function ReminderStep({ value, onChange, onFinish, onSkip }: ReminderStepProps) {
export function ReminderStep({ value, onChange, onFinish }: ReminderStepProps) {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const handleReduce = () => {
// 允许 050 表示关闭每日提醒
if (value > 0) onChange(value - 1);
// 本页最小为 1不接收提醒请使用右上角 Skip
if (value > 1) onChange(value - 1);
};
const handleAdd = () => {
@@ -36,7 +36,9 @@ export function ReminderStep({ value, onChange, onFinish, onSkip }: ReminderStep
<View style={styles.numberWrapper}>
<Text style={styles.numberText}>{value}</Text>
<Text style={styles.unitText}>{t('dailyReminder.timesUnit')}</Text>
<Text style={styles.unitText}>
{value === 1 ? t('dailyReminder.timesUnitSingular') : t('dailyReminder.timesUnit')}
</Text>
</View>
<TouchableOpacity onPress={handleAdd} activeOpacity={0.7}>
@@ -48,10 +50,6 @@ export function ReminderStep({ value, onChange, onFinish, onSkip }: ReminderStep
<TouchableOpacity onPress={onFinish} activeOpacity={0.8}>
<BtnClicked width={87} height={57} />
</TouchableOpacity>
<TouchableOpacity onPress={onSkip} activeOpacity={0.8} style={styles.skipBtn}>
<Text style={styles.skipText}>{t('onboarding.skip')}</Text>
</TouchableOpacity>
</View>
</View>
);
@@ -93,17 +91,5 @@ const styles = StyleSheet.create({
footer: {
position: 'absolute',
alignItems: 'center',
}
,
skipBtn: {
marginTop: 14,
paddingVertical: 10,
paddingHorizontal: 18,
},
skipText: {
color: OnboardingColors.textPrimary,
fontSize: 15,
fontWeight: '600',
opacity: 0.85,
},
});

View File

@@ -1,9 +1,7 @@
import React from 'react';
import { View, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
import { View, StyleSheet, TouchableOpacity, ScrollView, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import { SerifText } from './SerifText';
import SelectedIcon from '@/assets/images/icon/selected_icon.svg';
import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme';
import BtnNotClicked from '@/assets/images/icon/btn_Notclicked.svg';
import BtnClicked from '@/assets/images/icon/btn_clicked.svg';
@@ -25,7 +23,8 @@ export function SelectionStep({ options, selectedIds, onToggle, onNext, onSkip }
const insets = useSafeAreaInsets();
const footerBottom = insets.bottom + 16;
const footerButtonHeight = 57;
const footerPaddingBottom = footerBottom + footerButtonHeight + 24;
// 底部留白加大,避免最后一项与按钮边框视觉重叠
const footerPaddingBottom = footerBottom + footerButtonHeight + 40;
return (
<View style={styles.container}>
@@ -39,16 +38,11 @@ export function SelectionStep({ options, selectedIds, onToggle, onNext, onSkip }
return (
<TouchableOpacity
key={option.id}
style={styles.optionCard}
style={[styles.optionCard, isSelected && styles.optionCardSelected]}
onPress={() => onToggle(option.id)}
activeOpacity={0.7}
>
<SerifText style={styles.optionText}>{option.label}</SerifText>
{isSelected && (
<View style={styles.iconWrapper}>
<SelectedIcon width={20} height={20} />
</View>
)}
<Text style={styles.optionText}>{option.label}</Text>
</TouchableOpacity>
);
})}
@@ -67,7 +61,7 @@ export function SelectionStep({ options, selectedIds, onToggle, onNext, onSkip }
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
paddingTop: 8,
},
scroll: {
flex: 1,
@@ -82,7 +76,7 @@ const styles = StyleSheet.create({
borderRadius: 20,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
justifyContent: 'center',
paddingHorizontal: 24,
marginBottom: 12,
shadowColor: '#000',
@@ -91,14 +85,14 @@ const styles = StyleSheet.create({
shadowRadius: 10,
elevation: 2,
},
optionCardSelected: {
backgroundColor: OnboardingColors.cardSelected,
},
optionText: {
fontSize: 18,
color: OnboardingColors.textPrimary,
fontWeight: '500',
flex: 1,
},
iconWrapper: {
marginLeft: 10,
fontFamily: OnboardingFont.question,
},
footer: {
position: 'absolute',

View File

@@ -1,3 +1,10 @@
import { Platform } from 'react-native';
/** 与 onboarding 问题标题一致的字体PingFang TC / sans-serif */
export const OnboardingFont = {
question: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif',
};
export const OnboardingColors = {
background: '#FFF4EA',
textPrimary: '#772F00',

View File

@@ -1,21 +0,0 @@
{
"cli": {
"version": ">= 16.32.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

View File

@@ -2240,330 +2240,330 @@ PODS:
- Yoga (0.0.0)
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+react@1_nvlvke5tn7wk5pigfsu7j4ieeq/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-native@0.8_7rhpxisdkrzvrgzbu7ct455kta/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`)"
- "ExpoCrypto (from `../node_modules/.pnpm/expo-crypto@15.0.8_expo@54.0.32/node_modules/expo-crypto/ios`)"
- "ExpoDevice (from `../node_modules/.pnpm/expo-device@8.0.10_expo@54.0.32/node_modules/expo-device/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.13_expo_mxedi6ntnfsoyp6zijog4pvdsy/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+react@_e6k2hjkd5k4lph2ersbp3gfshy/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`)"
- "ExpoLocalization (from `../node_modules/.pnpm/expo-localization@17.0.8_expo@54.0.32_react@19.1.0/node_modules/expo-localization/ios`)"
- "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`)"
- "RCTRequired (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/Required`)"
- "RCTTypeSafety (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/TypeSafety`)"
- "React (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/`)"
- "React-callinvoker (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/callinvoker`)"
- "React-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/`)"
- "React-Core-prebuilt (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/React-Core-prebuilt.podspec`)"
- "React-Core/RCTWebSocket (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/`)"
- "React-CoreModules (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/React/CoreModules`)"
- "React-cxxreact (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/cxxreact`)"
- "React-debug (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/react/debug`)"
- "React-defaultsnativemodule (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/react/nativemodule/defaults`)"
- "React-domnativemodule (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/react/nativemodule/dom`)"
- "React-Fabric (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`)"
- "React-FabricComponents (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`)"
- "React-FabricImage (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`)"
- "React-featureflags (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/react/featureflags`)"
- "React-featureflagsnativemodule (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/react/nativemodule/featureflags`)"
- "React-graphics (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/react/renderer/graphics`)"
- "React-hermes (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/hermes`)"
- "React-idlecallbacksnativemodule (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/react/nativemodule/idlecallbacks`)"
- "React-ImageManager (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/react/renderer/imagemanager/platform/ios`)"
- "React-jserrorhandler (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/jserrorhandler`)"
- "React-jsi (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/jsi`)"
- "React-jsiexecutor (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/jsiexecutor`)"
- "React-jsinspector (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/jsinspector-modern`)"
- "React-jsinspectorcdp (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/jsinspector-modern/cdp`)"
- "React-jsinspectornetwork (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/jsinspector-modern/network`)"
- "React-jsinspectortracing (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/jsinspector-modern/tracing`)"
- "React-jsitooling (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/jsitooling`)"
- "React-jsitracing (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/hermes/executor/`)"
- "React-logger (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/logger`)"
- "React-Mapbuffer (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`)"
- "React-microtasksnativemodule (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/react/nativemodule/microtasks`)"
- "react-native-safe-area-context (from `../node_modules/.pnpm/react-native-safe-area-context@5.6.2_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1_azuxgonsvxb2yngtegtuvyxcpi/node_modules/react-native-safe-area-context`)"
- "React-NativeModulesApple (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/react/nativemodule/core/platform/ios`)"
- "React-oscompat (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/oscompat`)"
- "React-perflogger (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/reactperflogger`)"
- "React-performancetimeline (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/react/performance/timeline`)"
- "React-RCTActionSheet (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/ActionSheetIOS`)"
- "React-RCTAnimation (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/NativeAnimation`)"
- "React-RCTAppDelegate (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/AppDelegate`)"
- "React-RCTBlob (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/Blob`)"
- "React-RCTFabric (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/React`)"
- "React-RCTFBReactNativeSpec (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/React`)"
- "React-RCTImage (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/Image`)"
- "React-RCTLinking (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/LinkingIOS`)"
- "React-RCTNetwork (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/Network`)"
- "React-RCTRuntime (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/React/Runtime`)"
- "React-RCTSettings (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/Settings`)"
- "React-RCTText (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/Text`)"
- "React-RCTVibration (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/Vibration`)"
- "React-rendererconsistency (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/react/renderer/consistency`)"
- "React-renderercss (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/react/renderer/css`)"
- "React-rendererdebug (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/react/renderer/debug`)"
- "React-RuntimeApple (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/react/runtime/platform/ios`)"
- "React-RuntimeCore (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/react/runtime`)"
- "React-runtimeexecutor (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/runtimeexecutor`)"
- "React-RuntimeHermes (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/react/runtime`)"
- "React-runtimescheduler (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/react/renderer/runtimescheduler`)"
- "React-timing (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/react/timing`)"
- "React-utils (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/react/utils`)"
- EXApplication (from `../node_modules/expo-application/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXJSONUtils (from `../node_modules/expo-json-utils/ios`)
- EXManifests (from `../node_modules/expo-manifests/ios`)
- EXNotifications (from `../node_modules/expo-notifications/ios`)
- Expo (from `../node_modules/expo`)
- expo-dev-client (from `../node_modules/expo-dev-client/ios`)
- expo-dev-launcher (from `../node_modules/expo-dev-launcher`)
- expo-dev-menu (from `../node_modules/expo-dev-menu`)
- expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
- ExpoDevice (from `../node_modules/expo-device/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
- ExpoFont (from `../node_modules/expo-font/ios`)
- ExpoHead (from `../node_modules/expo-router/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
- ExpoLinking (from `../node_modules/expo-linking/ios`)
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- ExpoSplashScreen (from `../node_modules/expo-splash-screen/ios`)
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
- EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Core (from `../node_modules/react-native/`)
- React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
- React-debug (from `../node_modules/react-native/ReactCommon/react/debug`)
- React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`)
- React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`)
- React-Fabric (from `../node_modules/react-native/ReactCommon`)
- React-FabricComponents (from `../node_modules/react-native/ReactCommon`)
- React-FabricImage (from `../node_modules/react-native/ReactCommon`)
- React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`)
- React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`)
- React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`)
- React-hermes (from `../node_modules/react-native/ReactCommon/hermes`)
- React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)
- React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)
- React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`)
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`)
- React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`)
- React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`)
- React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`)
- React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`)
- React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
- React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`)
- React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
- React-RCTFabric (from `../node_modules/react-native/React`)
- React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`)
- React-RCTImage (from `../node_modules/react-native/Libraries/Image`)
- React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`)
- React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`)
- React-RCTRuntime (from `../node_modules/react-native/React/Runtime`)
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`)
- React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`)
- React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`)
- React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`)
- React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-timing (from `../node_modules/react-native/ReactCommon/react/timing`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactAppDependencyProvider (from `build/generated/ios`)
- ReactCodegen (from `build/generated/ios`)
- "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_@types_fp4qq3a7mejmut52v6jrlvxlzi/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@19.1._tylda4qoo2jtxaj3472gn4luma/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+core@7.28_ky3sbxf6i7nkyacc2hzg3xcz4q/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`)"
- "RNWorklets (from `../node_modules/.pnpm/react-native-worklets@0.5.1_@babel+core@7.28.6_react-native@0.81.5_@babel+core@7.28.6_@types+_5atwepuw3zy3crkgvetf35tkve/node_modules/react-native-worklets`)"
- "Yoga (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/yoga`)"
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
- RNWorklets (from `../node_modules/react-native-worklets`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
EXTERNAL SOURCES:
EXApplication:
:path: "../node_modules/.pnpm/expo-application@7.0.8_expo@54.0.32/node_modules/expo-application/ios"
:path: "../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"
:path: "../node_modules/expo-constants/ios"
EXJSONUtils:
:path: "../node_modules/.pnpm/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios"
:path: "../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"
:path: "../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+react@1_nvlvke5tn7wk5pigfsu7j4ieeq/node_modules/expo-notifications/ios"
:path: "../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-native@0.8_7rhpxisdkrzvrgzbu7ct455kta/node_modules/expo"
:path: "../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"
:path: "../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"
:path: "../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"
:path: "../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"
:path: "../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"
:path: "../node_modules/expo-asset/ios"
ExpoCrypto:
:path: "../node_modules/.pnpm/expo-crypto@15.0.8_expo@54.0.32/node_modules/expo-crypto/ios"
:path: "../node_modules/expo-crypto/ios"
ExpoDevice:
:path: "../node_modules/.pnpm/expo-device@8.0.10_expo@54.0.32/node_modules/expo-device/ios"
:path: "../node_modules/expo-device/ios"
ExpoFileSystem:
:path: "../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"
:path: "../node_modules/expo-file-system/ios"
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"
:path: "../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.13_expo_mxedi6ntnfsoyp6zijog4pvdsy/node_modules/expo-router/ios"
:path: "../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"
:path: "../node_modules/expo-keep-awake/ios"
ExpoLinearGradient:
:path: "../node_modules/.pnpm/expo-linear-gradient@15.0.8_expo@54.0.32_react-native@0.81.5_@babel+core@7.28.6_@types+react@_e6k2hjkd5k4lph2ersbp3gfshy/node_modules/expo-linear-gradient/ios"
:path: "../node_modules/expo-linear-gradient/ios"
ExpoLinking:
:path: "../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"
:path: "../node_modules/expo-linking/ios"
ExpoLocalization:
:path: "../node_modules/.pnpm/expo-localization@17.0.8_expo@54.0.32_react@19.1.0/node_modules/expo-localization/ios"
:path: "../node_modules/expo-localization/ios"
ExpoModulesCore:
:path: "../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"
:path: "../node_modules/expo-modules-core"
ExpoSplashScreen:
:path: "../node_modules/.pnpm/expo-splash-screen@31.0.13_expo@54.0.32/node_modules/expo-splash-screen/ios"
:path: "../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"
:path: "../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"
:path: "../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"
:path: "../node_modules/react-native/Libraries/FBLazyVector"
hermes-engine:
: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/sdks/hermes-engine/hermes-engine.podspec"
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782
RCTDeprecation:
: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/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
:path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
RCTRequired:
: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/Required"
:path: "../node_modules/react-native/Libraries/Required"
RCTTypeSafety:
: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/TypeSafety"
:path: "../node_modules/react-native/Libraries/TypeSafety"
React:
: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/"
:path: "../node_modules/react-native/"
React-callinvoker:
: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/ReactCommon/callinvoker"
:path: "../node_modules/react-native/ReactCommon/callinvoker"
React-Core:
: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/"
:path: "../node_modules/react-native/"
React-Core-prebuilt:
: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/React-Core-prebuilt.podspec"
:podspec: "../node_modules/react-native/React-Core-prebuilt.podspec"
React-CoreModules:
: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/React/CoreModules"
:path: "../node_modules/react-native/React/CoreModules"
React-cxxreact:
: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/ReactCommon/cxxreact"
:path: "../node_modules/react-native/ReactCommon/cxxreact"
React-debug:
: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/ReactCommon/react/debug"
:path: "../node_modules/react-native/ReactCommon/react/debug"
React-defaultsnativemodule:
: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/ReactCommon/react/nativemodule/defaults"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults"
React-domnativemodule:
: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/ReactCommon/react/nativemodule/dom"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom"
React-Fabric:
: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/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-FabricComponents:
: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/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-FabricImage:
: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/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-featureflags:
: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/ReactCommon/react/featureflags"
:path: "../node_modules/react-native/ReactCommon/react/featureflags"
React-featureflagsnativemodule:
: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/ReactCommon/react/nativemodule/featureflags"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags"
React-graphics:
: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/ReactCommon/react/renderer/graphics"
:path: "../node_modules/react-native/ReactCommon/react/renderer/graphics"
React-hermes:
: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/ReactCommon/hermes"
:path: "../node_modules/react-native/ReactCommon/hermes"
React-idlecallbacksnativemodule:
: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/ReactCommon/react/nativemodule/idlecallbacks"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks"
React-ImageManager:
: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/ReactCommon/react/renderer/imagemanager/platform/ios"
:path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios"
React-jserrorhandler:
: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/ReactCommon/jserrorhandler"
:path: "../node_modules/react-native/ReactCommon/jserrorhandler"
React-jsi:
: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/ReactCommon/jsi"
:path: "../node_modules/react-native/ReactCommon/jsi"
React-jsiexecutor:
: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/ReactCommon/jsiexecutor"
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
: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/ReactCommon/jsinspector-modern"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern"
React-jsinspectorcdp:
: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/ReactCommon/jsinspector-modern/cdp"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp"
React-jsinspectornetwork:
: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/ReactCommon/jsinspector-modern/network"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network"
React-jsinspectortracing:
: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/ReactCommon/jsinspector-modern/tracing"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing"
React-jsitooling:
: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/ReactCommon/jsitooling"
:path: "../node_modules/react-native/ReactCommon/jsitooling"
React-jsitracing:
: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/ReactCommon/hermes/executor/"
:path: "../node_modules/react-native/ReactCommon/hermes/executor/"
React-logger:
: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/ReactCommon/logger"
:path: "../node_modules/react-native/ReactCommon/logger"
React-Mapbuffer:
: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/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-microtasksnativemodule:
: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/ReactCommon/react/nativemodule/microtasks"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
react-native-safe-area-context:
:path: "../node_modules/.pnpm/react-native-safe-area-context@5.6.2_react-native@0.81.5_@babel+core@7.28.6_@types+react@19.1_azuxgonsvxb2yngtegtuvyxcpi/node_modules/react-native-safe-area-context"
:path: "../node_modules/react-native-safe-area-context"
React-NativeModulesApple:
: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/ReactCommon/react/nativemodule/core/platform/ios"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
React-oscompat:
: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/ReactCommon/oscompat"
:path: "../node_modules/react-native/ReactCommon/oscompat"
React-perflogger:
: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/ReactCommon/reactperflogger"
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-performancetimeline:
: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/ReactCommon/react/performance/timeline"
:path: "../node_modules/react-native/ReactCommon/react/performance/timeline"
React-RCTActionSheet:
: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/ActionSheetIOS"
:path: "../node_modules/react-native/Libraries/ActionSheetIOS"
React-RCTAnimation:
: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/NativeAnimation"
:path: "../node_modules/react-native/Libraries/NativeAnimation"
React-RCTAppDelegate:
: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/AppDelegate"
:path: "../node_modules/react-native/Libraries/AppDelegate"
React-RCTBlob:
: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/Blob"
:path: "../node_modules/react-native/Libraries/Blob"
React-RCTFabric:
: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/React"
:path: "../node_modules/react-native/React"
React-RCTFBReactNativeSpec:
: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/React"
:path: "../node_modules/react-native/React"
React-RCTImage:
: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/Image"
:path: "../node_modules/react-native/Libraries/Image"
React-RCTLinking:
: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/LinkingIOS"
:path: "../node_modules/react-native/Libraries/LinkingIOS"
React-RCTNetwork:
: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/Network"
:path: "../node_modules/react-native/Libraries/Network"
React-RCTRuntime:
: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/React/Runtime"
:path: "../node_modules/react-native/React/Runtime"
React-RCTSettings:
: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/Settings"
:path: "../node_modules/react-native/Libraries/Settings"
React-RCTText:
: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/Text"
:path: "../node_modules/react-native/Libraries/Text"
React-RCTVibration:
: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/Vibration"
:path: "../node_modules/react-native/Libraries/Vibration"
React-rendererconsistency:
: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/ReactCommon/react/renderer/consistency"
:path: "../node_modules/react-native/ReactCommon/react/renderer/consistency"
React-renderercss:
: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/ReactCommon/react/renderer/css"
:path: "../node_modules/react-native/ReactCommon/react/renderer/css"
React-rendererdebug:
: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/ReactCommon/react/renderer/debug"
:path: "../node_modules/react-native/ReactCommon/react/renderer/debug"
React-RuntimeApple:
: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/ReactCommon/react/runtime/platform/ios"
:path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios"
React-RuntimeCore:
: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/ReactCommon/react/runtime"
:path: "../node_modules/react-native/ReactCommon/react/runtime"
React-runtimeexecutor:
: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/ReactCommon/runtimeexecutor"
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
React-RuntimeHermes:
: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/ReactCommon/react/runtime"
:path: "../node_modules/react-native/ReactCommon/react/runtime"
React-runtimescheduler:
: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/ReactCommon/react/renderer/runtimescheduler"
:path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler"
React-timing:
: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/ReactCommon/react/timing"
:path: "../node_modules/react-native/ReactCommon/react/timing"
React-utils:
: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/ReactCommon/react/utils"
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactAppDependencyProvider:
:path: build/generated/ios
ReactCodegen:
:path: build/generated/ios
ReactCommon:
: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/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
ReactNativeDependencies:
: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"
:podspec: "../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_@types_fp4qq3a7mejmut52v6jrlvxlzi/node_modules/@react-native-async-storage/async-storage"
:path: "../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@19.1._tylda4qoo2jtxaj3472gn4luma/node_modules/react-native-gesture-handler"
:path: "../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+core@7.28_ky3sbxf6i7nkyacc2hzg3xcz4q/node_modules/react-native-reanimated"
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../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"
:path: "../node_modules/react-native-screens"
RNSVG:
:path: "../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"
:path: "../node_modules/react-native-svg"
RNWorklets:
:path: "../node_modules/.pnpm/react-native-worklets@0.5.1_@babel+core@7.28.6_react-native@0.81.5_@babel+core@7.28.6_@types+_5atwepuw3zy3crkgvetf35tkve/node_modules/react-native-worklets"
:path: "../node_modules/react-native-worklets"
Yoga:
: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/ReactCommon/yoga"
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
EXApplication: 13420f8139864183f8a04fd6099077bdf8cfb186
EXConstants: 3feb66fd1d94202fc1f0946d74e029d8b224b60e
EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f
EXConstants: fce59a631a06c4151602843667f7cfe35f81e271
EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd
EXManifests: 83ef0844fcf06d6099b12a7bdbd7d36fc0e1dd16
EXNotifications: 2a3feb7af6194828d9aafda72f63a9a03866230a
Expo: b8d64eb9a496ebe8c71e3dae7eeb7f394b146b80
expo-dev-client: 12ef7d5b14d93e309922acea78dcd851db583a87
expo-dev-launcher: 47994056008ffdc30a6a5e328a375b3e30a8db05
expo-dev-menu: ea4fb803ace52e60d7cd8060c7cd379612a140b2
EXManifests: a8d97683e5c7a3b026ffbd58559c64dc655b747b
EXNotifications: 9eec98712cc814ceff916d876cb53859003b0597
Expo: 4e503a041c59c4e34c8be262a135848ad5cd3710
expo-dev-client: 425ee077d6754a98cfe3a2e2410d29b440b24c9d
expo-dev-launcher: a4f4cdef064ab1fb8621e5b8c7c457cd6e9568c3
expo-dev-menu: 05b18812110c175814c6af0d09dd658abcc5e00d
expo-dev-menu-interface: 600df12ea01efecdd822daaf13cc0ac091775533
ExpoAsset: d999f3bbd998a750f3b74cb913229848901b926b
ExpoCrypto: 4d23a9ff67c25e2ed23ca792d81e58817a7ea1b9
ExpoDevice: 0773c782b055558ca9b40b74aa4a8133a66cd0d2
ExpoFileSystem: aefcd337b94b874f88752ebefc52813b84992fad
ExpoFont: c625dbd97ed57e9089b172b2a7bb99003d074664
ExpoHead: b691a2ed7ab02ed820b6c6468941832d34969c29
ExpoKeepAwake: 44bf6715bc1d2ddb17afe19d927cd039cda123f0
ExpoLinearGradient: 814a21fc4056c3cf606e4f19e31e47074c5b5a86
ExpoLinking: ebf543fd411d56375cb4eee07f6ab4e31c7ad959
ExpoLocalization: 6ac6f326210f0a3141ef6f58ab8f8f4ed003b485
ExpoModulesCore: 77496909fd3c800f97f7f2007dd26aeac4bb3798
ExpoSplashScreen: 72fbc6dd9d6404dd9d0725a56c9ac1383bc0b14f
ExpoWebBrowser: 88b116cd378d9609c776c0903fe4070fca461588
EXUpdatesInterface: 1436757deb0d574b84bba063bd024c315e0ec08b
ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18
ExpoCrypto: b6105ebaa15d6b38a811e71e43b52cd934945322
ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a
ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063
ExpoFont: f543ce20a228dd702813668b1a07b46f51878d47
ExpoHead: 4425246bc93411f0fe7f6945f95f698e91db8780
ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296
ExpoLinearGradient: 809102bdb979f590083af49f7fa4805cd931bd58
ExpoLinking: 8f0aaf69aa56f832913030503b6263dc6f647f37
ExpoLocalization: d9168d5300a5b03e5e78b986124d11fb6ec3ebbd
ExpoModulesCore: f3da4f1ab5a8375d0beafab763739dbee8446583
ExpoSplashScreen: bc3cffefca2716e5f22350ca109badd7e50ec14d
ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52
EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734
FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12
hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172
RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990
@@ -2571,72 +2571,72 @@ SPEC CHECKSUMS:
RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c
React: 914f8695f9bf38e6418228c2ffb70021e559f92f
React-callinvoker: 1c0808402aee0c6d4a0d8e7220ce6547af9fba71
React-Core: 4ae98f9e8135b8ddbd7c98730afb6fdae883db90
React-Core-prebuilt: 8f4cca589c14e8cf8fc6db4587ef1c2056b5c151
React-CoreModules: e878a90bb19b8f3851818af997dbae3b3b0a27ac
React-cxxreact: 28af9844f6dc87be1385ab521fbfb3746f19563c
React-Core: c61410ef0ca6055e204a963992e363227e0fd1c5
React-Core-prebuilt: 02f0ad625ddd47463c009c2d0c5dd35c0d982599
React-CoreModules: 1f6d1744b5f9f2ec684a4bb5ced25370f87e5382
React-cxxreact: 3af79478e8187b63ffc22b794cd42d3fc1f1f2da
React-debug: 6328c2228e268846161f10082e80dc69eac2e90a
React-defaultsnativemodule: afc9d809ec75780f39464a6949c07987fbea488c
React-domnativemodule: 91a233260411d41f27f67aa1358b7f9f0bfd101d
React-Fabric: 21f349b5e93f305a3c38c885902683a9c79cf983
React-FabricComponents: 47ac634cc9ecc64b30a9997192f510eebe4177e4
React-FabricImage: 21873acd6d4a51a0b97c133141051c7acb11cc86
React-featureflags: 653f469f0c3c9dc271d610373e3b6e66a9fd847d
React-featureflagsnativemodule: c91a8a3880e0f4838286402241ead47db43aed28
React-graphics: b4bdb0f635b8048c652a5d2b73eb8b1ddd950f24
React-hermes: fcfad3b917400f49026f3232561e039c9d1c34bf
React-idlecallbacksnativemodule: 8cb83207e39f8179ac1d344b6177c6ab3ccebcdc
React-ImageManager: 396128004783fc510e629124dce682d38d1088e7
React-jserrorhandler: b58b788d788cdbf8bda7db74a88ebfcffc8a0795
React-jsi: d2c3f8555175371c02da6dfe7ed1b64b55a9d6c0
React-jsiexecutor: ba537434eb45ee018b590ed7d29ee233fddb8669
React-jsinspector: f21b6654baf96cb9f71748844a32468a5f73ad51
React-jsinspectorcdp: 3f8be4830694c3c1c39442e50f8db877966d43f0
React-jsinspectornetwork: 70e41469565712ad60e11d9c8b8f999b9f7f61eb
React-jsinspectortracing: eccf9bfa4ec7f130d514f215cfb2222dc3c0e270
React-jsitooling: b376a695f5a507627f7934748533b24eed1751ca
React-jsitracing: 5c8c3273dda2d95191cc0612fb5e71c4d9018d2a
React-logger: c3e2f8a2e284341205f61eef3d4677ab5a309dfd
React-Mapbuffer: 603c18db65844bb81dbe62fee8fcc976eaeb7108
React-microtasksnativemodule: d77e0c426fce34c23227394c96ca1033b30c813c
react-native-safe-area-context: 53f796cb6c814661bbe99fbdfd0585d07b996cdd
React-NativeModulesApple: 1664340b8750d64e0ef3907c5e53d9481f74bcbd
React-defaultsnativemodule: d635ef36d755321e5d6fc065bd166b2c5a0e9833
React-domnativemodule: dd28f6d96cd21236e020be2eff6fe0b7d4ec3b66
React-Fabric: 2e32c3fdbb1fbcf5fde54607e3abe453c6652ce2
React-FabricComponents: 5ed0cdb81f6b91656cb4d3be432feaa28a58071a
React-FabricImage: 2bc714f818cb24e454f5d3961864373271b2faf8
React-featureflags: 847642f41fa71ad4eec5e0351badebcad4fe6171
React-featureflagsnativemodule: c868a544b2c626fa337bcbd364b1befe749f0d3f
React-graphics: 192ec701def5b3f2a07db2814dfba5a44986cff6
React-hermes: e875778b496c86d07ab2ccaa36a9505d248a254b
React-idlecallbacksnativemodule: 4d57965cdf82c14ee3b337189836cd8491632b76
React-ImageManager: bd0b99e370b13de82c9cd15f0f08144ff3de079e
React-jserrorhandler: a2fdef4cbcfdcdf3fa9f5d1f7190f7fd4535248d
React-jsi: 89d43d1e7d4d0663f8ba67e0b39eb4e4672c27de
React-jsiexecutor: abe4874aaab90dfee5dec480680220b2f8af07e3
React-jsinspector: a0b3e051aef842b0b2be2353790ae2b2a5a65a8f
React-jsinspectorcdp: 6346013b2247c6263fbf5199adf4a8751e53bd89
React-jsinspectornetwork: 26281aa50d49fc1ec93abf981d934698fa95714f
React-jsinspectortracing: 55eedf6d57540507570259a778663b90060bbd6e
React-jsitooling: 0e001113fa56d8498aa8ac28437ac0d36348e51a
React-jsitracing: b713793eb8a5bbc4d86a84e9d9e5023c0f58cbaf
React-logger: 50fdb9a8236da90c0b1072da5c32ee03aeb5bf28
React-Mapbuffer: 9050ee10c19f4f7fca8963d0211b2854d624973e
React-microtasksnativemodule: f775db9e991c6f3b8ccbc02bfcde22770f96e23b
react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2
React-NativeModulesApple: 8969913947d5b576de4ed371a939455a8daf28aa
React-oscompat: ce47230ed20185e91de62d8c6d139ae61763d09c
React-perflogger: b1af3cfb3f095f819b2814910000392a8e17ba9f
React-performancetimeline: f9ec65b77bcadbc7bd8b47a6f4b4b697da7b1490
React-perflogger: 02b010e665772c7dcb859d85d44c1bfc5ac7c0e4
React-performancetimeline: 130db956b5a83aa4fb41ddf5ae68da89f3fb1526
React-RCTActionSheet: 0b14875b3963e9124a5a29a45bd1b22df8803916
React-RCTAnimation: 60f6eca214a62b9673f64db6df3830cee902b5af
React-RCTAppDelegate: 37734b39bac108af30a0fd9d3e1149ec68b82c28
React-RCTBlob: 83fbcbd57755caf021787324aac2fe9b028cc264
React-RCTFabric: a05cb1df484008db3753c8b4a71e4c6d9f1e43a6
React-RCTFBReactNativeSpec: d58d7ae9447020bbbac651e3b0674422aba18266
React-RCTImage: 47aba3be7c6c64f956b7918ab933769602406aac
React-RCTLinking: 2dbaa4df2e4523f68baa07936bd8efdfa34d5f31
React-RCTNetwork: 1fca7455f9dedf7de2b95bec438da06680f3b000
React-RCTRuntime: 17819dd1dfc8613efaf4cbb9d8686baae4a83e5b
React-RCTSettings: 01bf91c856862354d3d2f642ccb82f3697a4284a
React-RCTText: cb576a3797dcb64933613c522296a07eaafc0461
React-RCTVibration: 560af8c086741f3525b8456a482cdbe27f9d098e
React-RCTAnimation: a7b90fd2af7bb9c084428867445a1481a8cb112e
React-RCTAppDelegate: 3262bedd01263f140ec62b7989f4355f57cec016
React-RCTBlob: c17531368702f1ebed5d0ada75a7cf5915072a53
React-RCTFabric: 6409edd8cfdc3133b6cc75636d3b858fdb1d11ea
React-RCTFBReactNativeSpec: c004b27b4fa3bd85878ad2cf53de3bbec85da797
React-RCTImage: c68078a120d0123f4f07a5ac77bea3bb10242f32
React-RCTLinking: cf8f9391fe7fe471f96da3a5f0435235eca18c5b
React-RCTNetwork: ca31f7c879355760c2d9832a06ee35f517938a20
React-RCTRuntime: a6cf4a1e42754fc87f493e538f2ac6b820e45418
React-RCTSettings: e0e140b2ff4bf86d34e9637f6316848fc00be035
React-RCTText: 75915bace6f7877c03a840cc7b6c622fb62bfa6b
React-RCTVibration: 25f26b85e5e432bb3c256f8b384f9269e9529f25
React-rendererconsistency: 2dac03f448ff337235fd5820b10f81633328870d
React-renderercss: c5c6b7a15948dd28facca39a18ac269073718490
React-rendererdebug: 3c9d5e1634273f5a24d84cc5669f290ce0bdc812
React-RuntimeApple: 887637d1e12ea8262df7d32bc100467df2302613
React-RuntimeCore: 91f779835dc4f8f84777fe5dd24f1a22f96454e4
React-runtimeexecutor: 8bb6b738f37b0ada4a6269e6f8ab1133dea0285c
React-RuntimeHermes: 4cb93de9fa8b1cc753d200dbe61a01b9ec5f5562
React-runtimescheduler: 83dc28f530bfbd2fce84ed13aa7feebdc24e5af7
React-timing: 03c7217455d2bff459b27a3811be25796b600f47
React-utils: 6d46795ae0444ec8a5d9a5f201157b286bf5250a
ReactAppDependencyProvider: c277c5b231881ad4f00cd59e3aa0671b99d7ebee
ReactCodegen: 88a1f4643f15841573f833b895bfa2a0c6cb4e7f
ReactCommon: e6e232202a447d353e5531f2be82f50f47cbaa9a
React-renderercss: 477da167bb96b5ac86d30c5d295412fb853f5453
React-rendererdebug: 2a1798c6f3ef5f22d466df24c33653edbabb5b89
React-RuntimeApple: 28cf4d8eb18432f6a21abbed7d801ab7f6b6f0b4
React-RuntimeCore: 41bf0fd56a00de5660f222415af49879fa49c4f0
React-runtimeexecutor: 1afb774dde3011348e8334be69d2f57a359ea43e
React-RuntimeHermes: f3b158ea40e8212b1a723a68b4315e7a495c5fc6
React-runtimescheduler: 3e1e2bec7300bae512533107d8e54c6e5c63fe0f
React-timing: 6fa9883de2e41791e5dc4ec404e5e37f3f50e801
React-utils: 6e2035b53d087927768649a11a26c4e092448e34
ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79
ReactCodegen: 7d4593f7591f002d137fe40cef3f6c11f13c88cc
ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8
ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a
RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4
RNGestureHandler: 40c2d1c168e54715fe52e0fb16cb38c54611e4f3
RNReanimated: 10415bc8396eaeac0d7b2c9a1538eae7e607ec9c
RNScreens: dd61bc3a3e6f6901ad833efa411917d44827cf51
RNSVG: 2825ee146e0f6a16221e852299943e4cceef4528
RNWorklets: 9ccdc8112b17af6eee2c85a233891cb80db150ad
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
RNGestureHandler: e0d0bce5599f6120b7adf90c38d2805e2935795f
RNReanimated: e5c702a3e24cc1c68b2de67671713f35461678f4
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34
RNWorklets: 76fce72926e28e304afb44f0da23b2d24f2c1fa0
Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a
PODFILE CHECKSUM: c2c3838f0b2a579fef2350bff2ecaa005e27145d

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@@ -11,7 +11,7 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
1A1DE01D4133812B2E2BA692 /* libPods-client.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E3328F0E595C1F4A244DF238 /* libPods-client.a */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
A1B2C3D4E5F60718293A4B5C /* 情绪小组件/EmotionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5B /* 情绪小组件/EmotionWidget.swift */; };
A1B2C3D4E5F60718293A4B5C /* EmotionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5B /* EmotionWidget.swift */; };
A8C1D2E3F4A5B6C7D8E9F0A2 /* AppGroupStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C1D2E3F4A5B6C7D8E9F0A1 /* AppGroupStorage.swift */; };
A8C1D2E3F4A5B6C7D8E9F0A3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */; };
A8C1D2E3F4A5B6C7D8E9F0B2 /* AppGroupStorageBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = A8C1D2E3F4A5B6C7D8E9F0B1 /* AppGroupStorageBridge.m */; };
@@ -53,7 +53,7 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = client/Info.plist; sourceTree = "<group>"; };
3C76CA16D0801CBF0D731C7C /* Pods-client.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-client.release.xcconfig"; path = "Target Support Files/Pods-client/Pods-client.release.xcconfig"; sourceTree = "<group>"; };
75F52ADE07CAE9D9736D7671 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = client/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B5B /* 情绪小组件/EmotionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "情绪小组件/EmotionWidget.swift"; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B5B /* EmotionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "情绪小组件/EmotionWidget.swift"; sourceTree = "<group>"; };
A8C1D2E3F4A5B6C7D8E9F0A1 /* AppGroupStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppGroupStorage.swift; path = client/AppGroupStorage.swift; sourceTree = "<group>"; };
A8C1D2E3F4A5B6C7D8E9F0B1 /* AppGroupStorageBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AppGroupStorageBridge.m; path = client/AppGroupStorageBridge.m; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = client/SplashScreen.storyboard; sourceTree = "<group>"; };
@@ -72,7 +72,7 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
EB3DAF952F2A4B8F00450593 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
EB3DAF952F2A4B8F00450593 /* Exceptions for "情绪小组件" folder in "情绪小组件Extension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
EmotionWidget.swift,
@@ -83,7 +83,18 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
EB3DAF842F2A4B8E00450593 /* 情绪小组件 */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (EB3DAF952F2A4B8F00450593 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = "情绪小组件"; sourceTree = "<group>"; };
EB3DAF842F2A4B8E00450593 /* 情绪小组件 */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
EB3DAF952F2A4B8F00450593 /* Exceptions for "情绪小组件" folder in "情绪小组件Extension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = "情绪小组件";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -199,7 +210,7 @@
EB3DAFD42F2A5FC100450593 /* Recovered References */ = {
isa = PBXGroup;
children = (
A1B2C3D4E5F60718293A4B5B /* 情绪小组件/EmotionWidget.swift */,
A1B2C3D4E5F60718293A4B5B /* EmotionWidget.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
@@ -466,7 +477,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A1B2C3D4E5F60718293A4B5C /* 情绪小组件/EmotionWidget.swift in Sources */,
A1B2C3D4E5F60718293A4B5C /* EmotionWidget.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -621,7 +632,7 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = NO;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../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";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SKIP_INSTALL = NO;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -680,7 +691,7 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../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";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SKIP_INSTALL = NO;
STRING_CATALOG_GENERATE_SYMBOLS = YES;

1628
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,14 @@
"version": "1.0.0",
"scripts": {
"start": "expo start",
"start:clean": "expo start -c",
"android": "expo run:android",
"ios": "expo run:ios --scheme \"Hey Mama\"",
"ios:clean": "npm run clean:cache && npm run clean:ios-build && expo run:ios --scheme \"Hey Mama\"",
"web": "expo start --web",
"test": "vitest run"
"test": "vitest run",
"clean:cache": "rm -rf node_modules/.cache .expo 2>/dev/null; echo 'Cleared .expo and node_modules/.cache'",
"clean:ios-build": "rm -rf ~/Library/Developer/Xcode/DerivedData/client-* 2>/dev/null; echo 'Cleared Xcode DerivedData for client'"
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",

View File

@@ -1,22 +1,25 @@
## 用文案表(请在此文件对应的 JSON 中修改)
## 用文案表(請在對應 JSON 中修改)
**单一文案源文件**`client/src/i18n/locales/all.json`
### 語言與檔案對應(單一來源,避免多檔覆蓋)
- **English**`all.json``en`
- **繁体中文**`all.json``zh-TW`
| 語言 | 實際使用的檔案 | 說明 |
|------------|-----------------------------|------|
| **英文** | `locales/all.json``en` | 只改 all.json 的 `en` 區塊 |
| **繁體中文** | `locales/zh-TW.json` | **唯一來源**Onboarding / 開屏 consent 等繁中文案只改此檔 |
> 说明:项目运行时只读取 `all.json`;请不要再改 `locales/en.json`、`locales/zh-TW.json`(它们已不再作为运行时数据源)
- 運行時 **繁中zh-TW只讀取 `zh-TW.json`**,不會讀取 `all.json` 的 zh-TW 區塊
- 修改 JSON 後需**重新載入 App**(模擬器 `Cmd+R`)或重啟 Metro必要時加 `--clear`)才會看到新文案。
- **若修改 zh-TW.json 後開屏 consent 仍顯示舊文案**:請執行 `npm run clean:cache`,再以 `npm run start:clean` 啟動 Metro或先刪除模擬器上的 App 再執行 `npm run ios:clean`),確保吃到最新 bundle。
### 快速索引(高文案)
### 快速索引(高文案)
- **開屏 / 協議**`consent.*`**繁中只改 zh-TW.json**`consent.title``consent.subtitle``consent.subtitleSecondary`、agree, privacy, terms, notice…
- **Onboarding 問卷**`onboardingSurvey.steps.*`name.title, status.title, status.options.* …)
- **Onboarding 招呼語**`onboardingSurvey.greeting`Hi {{name}}
- **Home**`home.*`
- **Push 提示**`push.*`
- **主**`theme.*`
- **我的/Profile**`profile.*`
- **主**`theme.*`
- **我的 / Profile**`profile.*`
- **收藏**`favorites.*`
- **设置**`settings.*`
- **Onboarding问卷**`onboardingSurvey.steps.*`
- **Onboarding兴趣**`intent.*`
- **設定**`settings.*`
- **Mock 文案**`mock.*`

View File

@@ -6,8 +6,12 @@ import { initReactI18next } from 'react-i18next';
import { isTraditionalChineseLocaleTag } from './locale';
// 用 require 避免 TS 的 json module 配置差异导致无法编译
// 繁中zh-TW唯一來源locales/zh-TW.json修改 Onboarding/開屏等繁中文案請只改該檔案
// 本 App 未載入 zh-CN.json若看到類似「你本就完美」「一切都会变好」等簡中 consent 文案,為舊 bundle 快取導致,請執行 clean:cache + start:clean 或卸載 App 重裝。
// eslint-disable-next-line @typescript-eslint/no-var-requires
const all = require('./locales/all.json') as { en: Record<string, unknown>; 'zh-TW': Record<string, unknown> };
// eslint-disable-next-line @typescript-eslint/no-var-requires
const zhTW = require('./locales/zh-TW.json') as Record<string, unknown>;
/**
* 语言码约定:
@@ -81,7 +85,8 @@ export async function initI18n(): Promise<void> {
await i18n.use(initReactI18next).init({
resources: {
'zh-TW': { translation: all['zh-TW'] as any },
// 繁中唯一來源zh-TW.jsonall.json 的 zh-TW 區塊不會被載入)
'zh-TW': { translation: zhTW },
en: { translation: all.en as any },
},
lng: initialLang,
@@ -91,6 +96,18 @@ export async function initI18n(): Promise<void> {
escapeValue: false,
},
});
// 臨時 debug確認實際使用的 language 與 consent.title 值(方便驗證繁中來自 zh-TW.json
if (typeof __DEV__ !== 'undefined' && __DEV__) {
const consentTitle = i18n.t('consent.title');
console.log(
'[i18n] 已初始化 language=',
i18n.language,
'| consent.title=',
consentTitle,
'| 繁中來源=zh-TW.json英文來源=all.json 的 en 區塊'
);
}
}
/**

View File

@@ -14,7 +14,7 @@
"progress": "{{current}}/{{total}}",
"next": "Next",
"skip": "Skip",
"skipAll": "Skip onboarding",
"skipAll": "Skip",
"q1Title": "How are you feeling lately?",
"q1Desc": "No right or wrong. You can skip and adjust later.",
"q2Title": "What kind of support do you want?",
@@ -25,10 +25,11 @@
"q4Desc": "You can skip. Well stay with you along the way."
},
"onboardingSurvey": {
"greeting": "Hi {{name}},",
"steps": {
"name": { "title": "What do you want to be called?", "placeholder": "Mama" },
"name": { "title": "What should we call you?", "placeholder": "Mama" },
"status": {
"title": "Which stage of motherhood are you in?",
"title": "Where are you at right now?",
"options": {
"pregnant": "Pregnant / Preparing",
"has_kids": "Parenting",
@@ -66,7 +67,7 @@
"balance": "Rest & balance"
}
},
"reminder": { "title": "How many reminders do you want per day?" }
"reminder": { "title": "How often would you like a gentle reminder?" }
}
},
"intent": {
@@ -115,6 +116,7 @@
"dailyReminder": {
"title": "Daily Reminder",
"timesUnit": "times",
"timesUnitSingular": "time",
"pushLabel": "Push Reminder",
"ok": "Ok",
"minus": "Decrease",
@@ -142,13 +144,14 @@
"widgetDesc": "Put gentle reminders on your home screen: long-press → tap “+” → search “Mindfulness” → add a size you like."
},
"consent": {
"title": "You Are Perfect.",
"subtitle": "Everything\nWill Be Better.",
"title": "Hey mama.",
"subtitle": "Youre doing okay\nright now.",
"subtitleSecondary": "",
"agree": "Agree & Continue",
"privacy": "Privacy Policy",
"terms": "Terms of Use",
"notice": "By continuing, you agree to the Privacy Policy and Terms of Use.",
"noticeRich": "By continuing, you agree to the <privacy>{{privacyLabel}}{{privacySuffix}}</privacy> and <terms>{{termsLabel}}{{termsSuffix}}</terms>.",
"noticeRich": "By continuing,\nyou agree to the <privacy>{{privacyLabel}}{{privacySuffix}}</privacy> and <terms>{{termsLabel}}{{termsSuffix}}</terms>.",
"linkUnavailable": "Failed to load the policy link. Please check your network and try again.",
"linkUnavailableDev": "Failed to load the policy link. Please check your network or API_BASE_URL: {{baseUrl}}",
"linkLoadingSuffix": " (loading…)"
@@ -183,7 +186,7 @@
"progress": "{{current}}/{{total}}",
"next": "下一步",
"skip": "跳過",
"skipAll": "跳過整個引導",
"skipAll": "跳過",
"q1Title": "你最近的感受更接近哪一種?",
"q1Desc": "沒有對錯,你可以跳過,之後也能慢慢調整。",
"q2Title": "你更希望獲得哪種支持?",
@@ -194,18 +197,19 @@
"q4Desc": "你可以直接跳過,我們會在之後繼續陪你。"
},
"onboardingSurvey": {
"greeting": "Hi {{name}}",
"steps": {
"name": { "title": "我可以怎麼稱呼你?", "placeholder": "媽媽" },
"name": { "title": "怎麼稱呼你", "placeholder": "媽媽" },
"status": {
"title": "媽媽的狀態",
"title": "你現在正處在哪個階段呢",
"options": {
"pregnant": "懷孕中/準備成為媽媽",
"pregnant": "懷孕中/正在準備迎接寶寶",
"has_kids": "已經有孩子",
"no_fill": "不想填寫"
"no_fill": "我暫時不想說"
}
},
"emotion": {
"title": "當下情緒狀態",
"title": "今天的你,還好嗎",
"options": {
"happy": "愉悅、滿足",
"calm": "平靜、安穩",
@@ -284,6 +288,7 @@
"dailyReminder": {
"title": "每日提醒",
"timesUnit": "次",
"timesUnitSingular": "次",
"pushLabel": "推送提醒",
"ok": "確定",
"minus": "減少次數",
@@ -311,8 +316,9 @@
"widgetDesc": "把溫柔提醒放到桌面上:長按主畫面 → 點「+」 → 搜尋「正念」 → 添加你喜歡的尺寸。"
},
"consent": {
"title": "你很完美。",
"subtitle": "一切\n都會更好。",
"title": "我們知道,",
"subtitle": "當媽媽很不容易。",
"subtitleSecondary": "這裡給你一些溫柔的肯定與提醒",
"agree": "同意並繼續",
"privacy": "隱私協議",
"terms": "用戶使用協議",

View File

@@ -11,7 +11,7 @@
"progress": "{{current}}/{{total}}",
"next": "Next",
"skip": "Skip",
"skipAll": "Skip onboarding",
"skipAll": "Skip",
"q1Title": "How are you feeling lately?",
"q1Desc": "No right or wrong. You can skip and adjust later.",
"q2Title": "What kind of support do you want?",
@@ -59,6 +59,7 @@
"dailyReminder": {
"title": "Daily Reminder",
"timesUnit": "times",
"timesUnitSingular": "time",
"pushLabel": "Push Reminder",
"ok": "Ok",
"minus": "Decrease",

View File

@@ -9,7 +9,7 @@
"progress": "{{current}}/{{total}}",
"next": "Siguiente",
"skip": "Saltar",
"skipAll": "Saltar introducción",
"skipAll": "Saltar",
"q1Title": "¿Cómo te sientes últimamente?",
"q1Desc": "No hay respuestas correctas. Puedes saltar y ajustar después.",
"q2Title": "¿Qué tipo de apoyo quieres?",
@@ -57,6 +57,7 @@
"dailyReminder": {
"title": "Recordatorio diario",
"timesUnit": "veces",
"timesUnitSingular": "vez",
"pushLabel": "Recordatorio Push",
"ok": "Ok",
"minus": "Disminuir",

View File

@@ -9,7 +9,7 @@
"progress": "{{current}}/{{total}}",
"next": "Próximo",
"skip": "Pular",
"skipAll": "Pular introdução",
"skipAll": "Pular",
"q1Title": "Como você tem se sentido ultimamente?",
"q1Desc": "Não há certo ou errado. Você pode pular e ajustar depois.",
"q2Title": "Que tipo de apoio você quer?",
@@ -57,6 +57,7 @@
"dailyReminder": {
"title": "Lembrete diário",
"timesUnit": "vezes",
"timesUnitSingular": "vez",
"pushLabel": "Lembrete Push",
"ok": "Ok",
"minus": "Diminuir",

View File

@@ -12,7 +12,7 @@
"progress": "{{current}}/{{total}}",
"next": "下一步",
"skip": "跳过",
"skipAll": "跳过整个引导",
"skipAll": "跳过",
"q1Title": "你最近的感受更接近哪一种?",
"q1Desc": "没有对错,你可以跳过,之后也可以慢慢调整。",
"q2Title": "你更希望获得哪种支持?",
@@ -60,6 +60,7 @@
"dailyReminder": {
"title": "每日提醒",
"timesUnit": "次",
"timesUnitSingular": "次",
"pushLabel": "推送提醒",
"ok": "确定",
"minus": "减少次数",

View File

@@ -2,14 +2,18 @@
"common": {
"ok": "確定",
"cancel": "取消",
"back": "返回"
"back": "返回",
"error": "錯誤",
"notice": "提示",
"openLinkError": "無法打開鏈接",
"close": "關閉"
},
"onboarding": {
"title": "歡迎",
"progress": "{{current}}/{{total}}",
"next": "下一步",
"skip": "跳過",
"skipAll": "跳過整個引導",
"skipAll": "跳過",
"q1Title": "你最近的感受更接近哪一種?",
"q1Desc": "沒有對錯,你可以跳過,之後也能慢慢調整。",
"q2Title": "你更希望獲得哪種支持?",
@@ -19,6 +23,64 @@
"q4Title": "給自己一句溫柔的話",
"q4Desc": "你可以直接跳過,我們會在之後繼續陪你。"
},
"onboardingSurvey": {
"greeting": "Hi {{name}}",
"steps": {
"name": {
"title": "怎麼稱呼你呢?",
"placeholder": "媽媽"
},
"status": {
"title": "你現在正處在哪個階段呢?",
"options": {
"pregnant": "懷孕中/正在準備迎接寶寶",
"has_kids": "已經有孩子",
"no_fill": "我暫時不想說"
}
},
"emotion": {
"title": "今天的你,還好嗎?",
"options": {
"happy": "愉悅、滿足",
"calm": "平靜、安穩",
"okay": "還可以、普通",
"tired": "疲累、沒什麼力氣",
"stressed": "被壓得有點喘不過氣",
"low": "情緒低落"
}
},
"influence": {
"title": "是什麼影響了你最近的感受?",
"options": {
"family": "家庭與孩子",
"work": "工作或學習",
"relationship": "親密關係",
"friends": "朋友與人際",
"health": "身心健康"
}
},
"support": {
"title": "最需要什麼支持?",
"options": {
"emotional": "情緒支持",
"parenting": "育兒壓力",
"self_worth": "自我價值",
"anxiety": "焦慮舒緩",
"balance": "休息與平衡"
}
},
"reminder": {
"title": "你希望一天收到幾次肯定語?"
}
}
},
"intent": {
"title": "你希望得到什麼幫助?",
"love": "愛情",
"life": "生活",
"travel": "旅遊",
"work": "職場"
},
"push": {
"title": "通知",
"cardTitle": "開啟溫柔提醒",
@@ -41,7 +103,8 @@
"theme": {
"title": "主題",
"scenery": "風景",
"color": "顏色"
"color": "顏色",
"suixin": "隨心"
},
"profile": {
"title": "我的",
@@ -57,6 +120,7 @@
"dailyReminder": {
"title": "每日提醒",
"timesUnit": "次",
"timesUnitSingular": "次",
"pushLabel": "推送提醒",
"ok": "確定",
"minus": "減少次數",
@@ -65,12 +129,16 @@
"widget": {
"lockScreen": "鎖屏小工具",
"homeScreen": "桌面小工具",
"howToTitle": "如何添加小工具",
"howToDesc1": "長按主畫面空白處進入編輯,點左上角「+」新增小工具。",
"howToDesc2": "搜尋「正念」,選擇喜歡的尺寸,點「加入小工具」。",
"previewDate": "1月29日週四 · 已至臘月十一",
"previewQuote": "我也對現在的自己感到滿意,即使我仍在努力成為想成為的人。"
},
"favorites": {
"title": "收藏夾",
"empty": "這裡還沒有收藏內容。"
"empty": "這裡還沒有收藏內容。",
"unknownText": "這條文案暫時無法顯示。"
},
"settings": {
"title": "設定",
@@ -80,9 +148,30 @@
"widgetDesc": "把溫柔提醒放到桌面上:長按主畫面 → 點「+」 → 搜尋「正念」 → 添加你喜歡的尺寸。"
},
"consent": {
"title": "我們知道,",
"subtitle": "當媽媽很不容易。",
"subtitleSecondary": "這裡給你一些溫柔的肯定與提醒",
"agree": "同意並繼續",
"privacy": "隱私協議",
"terms": "用戶使用協議"
"terms": "用戶使用協議",
"notice": "繼續使用即代表你同意《隱私協議》與《用戶使用協議》。",
"noticeRich": "繼續使用即代表你同意<privacy>《{{privacyLabel}}》{{privacySuffix}}</privacy>與<terms>《{{termsLabel}}》{{termsSuffix}}</terms>。",
"linkUnavailable": "協議鏈接載入失敗,請檢查網路後重試。",
"linkUnavailableDev": "協議鏈接載入失敗,請檢查網路或 API_BASE_URL 設定:{{baseUrl}}",
"linkLoadingSuffix": "(載入中…)"
},
"permissions": {
"notificationsDenied": "系統權限已被拒絕,請前往手機設定開啟通知。"
},
"language": {
"zhTW": "繁體中文",
"en": "English"
},
"mock": {
"c1": "你已經很努力了,今天也值得被溫柔對待。",
"c2": "深呼吸三次,把注意力帶回當下。",
"c3": "允許自己慢一點,情緒會像雲一樣飄過。",
"c4": "你不需要完美,你已經足夠好。",
"c5": "把手放在心口,對自己說一句:辛苦了。"
}
}

View File

@@ -4,7 +4,6 @@ from datetime import datetime, timezone
from typing import Any, Literal, Optional
import httpx
import redis
from fastapi import APIRouter, Depends, Header, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy import select
@@ -14,10 +13,8 @@ from app.api.limits import rate_limit_push_by_ip
from app.core.config import get_settings
from app.db.models.push_preference import PushPreference
from app.db.models.push_token import PushToken
from app.db.models.push_send_log import PushSendLog
from app.db.session import get_db
from app.features.user_profile_scoring.types import UserProfileV1_2
from app.worker import celery_app
router = APIRouter(
@@ -263,84 +260,3 @@ async def test_push(
_ = accept_language
return {"status": "ok", "expo": expo_res}
def _env_prefix(app_env: str) -> str:
"""
根据环境生成前缀:
- dev -> dev
- prod -> pro
"""
return "dev" if str(app_env) == "dev" else "pro"
@router.get("/scheduler/health")
async def scheduler_health(db: AsyncSession = Depends(get_db)) -> dict[str, Any]:
"""
推送“定时服务”健康检查(用于容器内验证)。
返回内容(尽量不暴露敏感信息):
- Redis是否可连通
- Worker是否至少有一个 worker 在线inspect ping
- Beat是否在跑beat 心跳 key 是否在持续刷新)
- DB是否可查询到 push_send_log 的最新时间(辅助定位排程是否生成)
"""
settings = get_settings()
prefix = _env_prefix(settings.app_env)
beat_key = f"{prefix}:beat:heartbeat"
out: dict[str, Any] = {
"env": settings.app_env,
"redis": {"ok": False},
"worker": {"ok": False, "worker_count": 0},
"beat": {"ok": False, "last_heartbeat_at": None, "age_seconds": None},
"db": {"ok": False, "push_send_log_latest_created_at": None},
"now_utc": datetime.now(timezone.utc).isoformat(),
}
# 1) Redis 连通性 + 读取 beat 心跳
try:
r = redis.Redis.from_url(settings.celery_broker_url, decode_responses=True)
r.ping()
out["redis"]["ok"] = True
hb = r.get(beat_key)
if hb:
out["beat"]["last_heartbeat_at"] = hb
try:
# Python 3.11+ 支持解析 ISO8601含 +00:00
hb_dt = datetime.fromisoformat(hb.replace("Z", "+00:00"))
now = datetime.now(timezone.utc)
age = int((now - hb_dt.astimezone(timezone.utc)).total_seconds())
out["beat"]["age_seconds"] = age
# 2 分钟内认为健康beat 每分钟刷新一次)
out["beat"]["ok"] = age <= 120
except Exception:
# 解析失败:至少说明 key 存在,但时间格式异常
out["beat"]["ok"] = False
except Exception as e:
out["redis"]["error"] = f"{type(e).__name__}: {e}"
# 2) Worker 在线性inspect ping
try:
insp = celery_app.control.inspect(timeout=1.0)
pings = insp.ping() or {}
if isinstance(pings, dict):
out["worker"]["worker_count"] = len(pings)
out["worker"]["ok"] = len(pings) > 0
except Exception as e:
out["worker"]["error"] = f"{type(e).__name__}: {e}"
# 3) DB查询 push_send_log 最新创建时间(用于判断排程是否有生成)
try:
q = select(PushSendLog.created_at).order_by(PushSendLog.created_at.desc()).limit(1)
row = await db.execute(q)
latest = row.scalar_one_or_none()
out["db"]["ok"] = True
out["db"]["push_send_log_latest_created_at"] = latest.isoformat() if latest else None
except Exception as e:
out["db"]["error"] = f"{type(e).__name__}: {e}"
return out

View File

@@ -10,5 +10,4 @@ Celery 任务集合。
from app.tasks import ping as _ping # noqa: F401
from app.tasks import reco as _reco # noqa: F401
from app.tasks import push as _push # noqa: F401
from app.tasks import ops as _ops # noqa: F401

View File

@@ -1,41 +0,0 @@
from __future__ import annotations
from datetime import datetime, timezone
import redis
from celery import shared_task
from app.core.config import get_settings
def _env_prefix(app_env: str) -> str:
"""
根据环境生成前缀:
- dev -> dev
- prod -> pro
"""
return "dev" if str(app_env) == "dev" else "pro"
@shared_task(name="tasks.ops.beat_heartbeat")
def beat_heartbeat() -> dict[str, str]:
"""
Beat 心跳任务(用于健康检查)。
作用:
- 由 Celery Beat 每分钟触发一次
- 写入 Redis 心跳 key并设置 TTL
- API 侧读取该 key可判断 beat 是否在运行
"""
settings = get_settings()
prefix = _env_prefix(settings.app_env)
key = f"{prefix}:beat:heartbeat"
now = datetime.now(timezone.utc).isoformat()
r = redis.Redis.from_url(settings.celery_broker_url, decode_responses=True)
# TTL 设短一些:一旦 beat 挂了,很快就能从“过期/缺失”判断出来
r.set(key, now, ex=180)
return {"status": "ok", "key": key, "at": now}

View File

@@ -45,12 +45,6 @@ celery_app.conf.update(
# - 这里按 UTC 00:10 触发一次;具体时间可按运维习惯调整
celery_app.conf.timezone = "UTC"
celery_app.conf.beat_schedule = {
# Beat 心跳:用于 API 健康检查判断 beat 是否在跑
"ops-beat-heartbeat": {
"task": "tasks.ops.beat_heartbeat",
"schedule": crontab(minute="*/1"),
"options": {"queue": f"{prefix}:celery"},
},
"push-generate-daily-schedule": {
"task": "tasks.push.generate_daily_schedule",
"schedule": crontab(minute=10, hour=0),

Binary file not shown.

View File

@@ -0,0 +1,31 @@
# Splash Consent 補充說明
## i18n 開屏 consent 文案不生效(排查與修復記錄)
### 現象
- iOS 模擬器繁中開屏一直顯示舊文案(如「你很完美。」「一切 都會更好。」)
- 修改 zh-TW.json 的 consent.title/subtitle 後重跑 `npm run ios` 仍不生效
- 專案內搜尋「你很完美」找不到(舊文案來自快取)
### 排查結論
1. **舊文案來源**
- 專案內**沒有**「你很完美」「一切 都會更好」的完整句
- `zh-CN.json` 有近似句「你本就完美。」「一切都会变好。」,但 **App 的 i18n 未載入 zh-CN**,僅載入 `zh-TW`zh-TW.json`en`all.json 的 en 區塊)
- 結論:舊文案來自 **Metro / JS bundle 或 iOS 建置快取**(曾打包進去的舊 JSON
2. **語言與載入順序**`client/src/i18n/index.ts`
- `resources``zh-TW``zh-TW.json``en``all.json``en`
- **all.json 的 zh-TW 區塊不會被載入**,繁中唯一來源為 `zh-TW.json`
- 裝置語言經 `expo-localization.getLocales()[0].languageTag` 取得,經 `normalizeDeviceLanguageTagToAppLanguage` 對應到 `zh-TW``en`zh-Hant / zh-TW / zh-HK 等均對應 zh-TW
3. **修復與預防**
- 已加臨時 debug logi18n 初始化與開屏 consent 畫面會印出 `language``consent.title`(僅 __DEV__
- 已加 `clean:cache``start:clean``ios:clean` script若改 zh-TW 仍不生效,請執行清理後重啟或卸載 App 重裝
- 繁中 consent 文案**只改** `client/src/i18n/locales/zh-TW.json``consent.title``consent.subtitle``consent.subtitleSecondary`
### 修改的檔案(本次修復)
- `client/src/i18n/index.ts`:註解 + __DEV__ 下印出 language 與 consent.title
- `client/app/(splash)/splash.tsx`useTranslation 取 i18n + __DEV__ 下印出 consent 畫面時的 language / title / subtitle
- `client/package.json``start:clean``ios:clean``clean:cache`
- `client/src/i18n/ALL_COPY.md`:故障排除與 consent 繁中只改 zh-TW.json 的說明