feat: 更新应用配置和引入新依赖
- 修改 app.json,禁用平板支持以优化用户体验 - 在 package.json 和 package-lock.json 中新增 react-native-toast-message 依赖,支持消息提示功能 - 在多个组件中集成 Toast 组件,提升用户交互反馈 - 更新训练计划相关逻辑,优化状态管理和数据处理 - 调整样式以适应新功能的展示和交互
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,6 @@ export default function SplashScreen() {
|
||||
// 如果出现错误,默认显示引导页面
|
||||
// setTimeout(() => {
|
||||
// router.replace('/onboarding');
|
||||
// setIsLoading(false);
|
||||
// }, 1000);
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 留出空间
|
||||
},
|
||||
|
||||
// 排课页面样式
|
||||
|
||||
@@ -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 = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
|
||||
Reference in New Issue
Block a user