feat: 更新应用配置和引入新依赖

- 修改 app.json,禁用平板支持以优化用户体验
- 在 package.json 和 package-lock.json 中新增 react-native-toast-message 依赖,支持消息提示功能
- 在多个组件中集成 Toast 组件,提升用户交互反馈
- 更新训练计划相关逻辑,优化状态管理和数据处理
- 调整样式以适应新功能的展示和交互
This commit is contained in:
2025-08-16 09:42:33 +08:00
parent 3312250f2d
commit 5a4d86ff7d
16 changed files with 192 additions and 255 deletions

View File

@@ -160,11 +160,6 @@ export default function HomeScreen() {
React.useEffect(() => {
let canceled = false;
async function load() {
if (!isLoggedIn) {
console.log('fetchRecommendations not logged in');
setItems(getFallbackItems());
return;
}
try {
const cards = await fetchRecommendations();
@@ -295,7 +290,7 @@ export default function HomeScreen() {
<Pressable
style={[styles.featureCard, styles.featureCardPrimary]}
onPress={() => router.push('/ai-posture-assessment')}
onPress={() => pushIfAuthedElseLogin('/ai-posture-assessment')}
>
<View style={styles.featureIconWrapper}>
<Image

View File

@@ -12,6 +12,7 @@ import { store } from '@/store';
import { rehydrateUser, setPrivacyAgreed } from '@/store/userSlice';
import React from 'react';
import RNExitApp from 'react-native-exit-app';
import Toast from 'react-native-toast-message';
import { Provider } from 'react-redux';
@@ -56,6 +57,7 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
onAgree={handlePrivacyAgree}
onDisagree={handlePrivacyDisagree}
/>
<Toast />
</>
);
}
@@ -91,6 +93,7 @@ export default function RootLayout() {
<Stack.Screen name="+not-found" />
</Stack>
<StatusBar style="dark" />
<Toast />
</ThemeProvider>
</Bootstrapper>
</Provider>

View File

@@ -13,6 +13,7 @@ import { Colors } from '@/constants/Colors';
import { useAppDispatch } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
import { login } from '@/store/userSlice';
import Toast from 'react-native-toast-message';
export default function LoginScreen() {
const router = useRouter();
@@ -111,6 +112,11 @@ export default function LoginScreen() {
throw new Error('未获取到 Apple 身份令牌');
}
await dispatch(login({ appleIdentityToken: identityToken })).unwrap();
Toast.show({
text1: '登录成功',
type: 'success',
});
// 登录成功后处理重定向
const to = searchParams?.redirectTo as string | undefined;
const paramsJson = searchParams?.redirectParams as string | undefined;

View File

@@ -31,7 +31,6 @@ export default function SplashScreen() {
// 如果出现错误,默认显示引导页面
// setTimeout(() => {
// router.replace('/onboarding');
// setIsLoading(false);
// }, 1000);
}
setIsLoading(false);

View File

@@ -29,7 +29,6 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type WeightUnit = 'kg' | 'lb';
type HeightUnit = 'cm' | 'ft';
@@ -50,7 +49,6 @@ const STORAGE_KEY = '@user_profile';
export default function EditProfileScreen() {
const colorScheme = useColorScheme();
const colors = Colors[colorScheme ?? 'light'];
const insets = useSafeAreaInsets();
const dispatch = useAppDispatch();
const accountProfile = useAppSelector((s) => (s as any)?.user?.profile as any);
const userId: string | undefined = useMemo(() => {

View File

@@ -1,8 +1,8 @@
import { Ionicons } from '@expo/vector-icons';
import MaskedView from '@react-native-masked-view/masked-view';
import { LinearGradient } from 'expo-linear-gradient';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useEffect, useMemo, useState } from 'react';
import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Alert, FlatList, Modal, Pressable, SafeAreaView, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native';
import Animated, {
FadeInUp,
@@ -21,6 +21,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors, palette } from '@/constants/Colors';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
import { TrainingPlan } from '@/services/trainingPlanApi';
import {
addExercise,
clearExercises,
@@ -29,7 +30,7 @@ import {
loadExercises,
toggleCompletion
} from '@/store/scheduleExerciseSlice';
import { activatePlan, clearError, deletePlan, loadPlans, type TrainingPlan } from '@/store/trainingPlanSlice';
import { activatePlan, clearError, deletePlan, loadPlans } from '@/store/trainingPlanSlice';
import { buildClassicalSession } from '@/utils/classicalSession';
// Tab 类型定义
@@ -248,13 +249,14 @@ export default function TrainingPlanScreen() {
const router = useRouter();
const dispatch = useAppDispatch();
const params = useLocalSearchParams<{ planId?: string; tab?: string }>();
const { plans, currentId, loading, error } = useAppSelector((s) => s.trainingPlan);
const { plans, loading, error } = useAppSelector((s) => s.trainingPlan);
const { exercises, error: scheduleError } = useAppSelector((s) => s.scheduleExercise);
console.log('plans', plans);
// Tab 状态管理 - 支持从URL参数设置初始tab
const initialTab: TabType = params.tab === 'schedule' ? 'schedule' : 'list';
const [activeTab, setActiveTab] = useState<TabType>(initialTab);
const [selectedPlanId, setSelectedPlanId] = useState<string | null>(params.planId || currentId || null);
const [selectedPlanId, setSelectedPlanId] = useState<string | null>(params.planId || null);
// 一键排课配置
const [genVisible, setGenVisible] = useState(false);
@@ -274,9 +276,21 @@ export default function TrainingPlanScreen() {
}
}, [selectedPlanId, dispatch]);
useEffect(() => {
dispatch(loadPlans());
}, [dispatch]);
// 每次页面获得焦点时,如果当前有选中的计划,重新加载其排课数据
useFocusEffect(
useCallback(() => {
if (selectedPlanId) {
dispatch(loadExercises(selectedPlanId));
}
}, [selectedPlanId, dispatch])
);
// 每次页面获得焦点时重新加载训练计划数据
useFocusEffect(
useCallback(() => {
dispatch(loadPlans());
}, [dispatch])
);
useEffect(() => {
if (error) {
@@ -314,7 +328,7 @@ export default function TrainingPlanScreen() {
const handleTabChange = (tab: TabType) => {
if (tab === 'schedule' && !selectedPlanId && plans.length > 0) {
// 如果没有选中计划但要切换到排课页面,自动选择当前激活的计划或第一个计划
const targetPlan = plans.find(p => p.id === currentId) || plans[0];
const targetPlan = plans.find(p => p.isActive) || plans[0];
setSelectedPlanId(targetPlan.id);
}
setActiveTab(tab);
@@ -426,7 +440,7 @@ export default function TrainingPlanScreen() {
key={p.id}
plan={p}
index={index}
isActive={p.id === currentId}
isActive={p.isActive}
onPress={() => handlePlanSelect(p)}
onDelete={() => dispatch(deletePlan(p.id))}
onActivate={() => handleActivate(p.id)}
@@ -1098,7 +1112,7 @@ const styles = StyleSheet.create({
// 主内容区域
mainContent: {
flex: 1,
paddingBottom: 100, // 为底部 tab 留出空间
paddingBottom: 60, // 为底部 tab 留出空间
},
// 排课页面样式

View File

@@ -8,6 +8,7 @@ import { ThemedView } from '@/components/ThemedView';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { palette } from '@/constants/Colors';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { PlanGoal } from '@/services/trainingPlanApi';
import {
clearError,
loadPlans,
@@ -21,7 +22,6 @@ import {
setStartDateNextMonday,
setStartWeight,
toggleDayOfWeek,
type PlanGoal
} from '@/store/trainingPlanSlice';
const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'];