feat: 更新应用版本和主题设置
- 将应用版本更新至 1.0.3,修改相关配置文件 - 强制全局使用浅色主题,确保一致的用户体验 - 在训练计划功能中新增激活计划的 API 接口,支持用户激活训练计划 - 优化打卡功能,支持自动同步打卡记录至服务器 - 更新样式以适应新功能的展示和交互
This commit is contained in:
@@ -2,9 +2,11 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { addExercise, syncCheckin } from '@/store/checkinSlice';
|
||||
import { EXERCISE_LIBRARY, getCategories, searchExercises } from '@/utils/exerciseLibrary';
|
||||
import { fetchExerciseConfig, normalizeToLibraryItems } from '@/services/exercises';
|
||||
import { addExercise } from '@/store/checkinSlice';
|
||||
import { EXERCISE_LIBRARY, getCategories } from '@/utils/exerciseLibrary';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -34,6 +36,8 @@ export default function SelectExerciseScreen() {
|
||||
const [showCustomReps, setShowCustomReps] = useState(false);
|
||||
const [customRepsInput, setCustomRepsInput] = useState('');
|
||||
const [showCategoryPicker, setShowCategoryPicker] = useState(false);
|
||||
const [serverLibrary, setServerLibrary] = useState<{ key: string; name: string; description: string; category: string }[] | null>(null);
|
||||
const [serverCategories, setServerCategories] = useState<string[] | null>(null);
|
||||
|
||||
const controlsOpacity = useRef(new Animated.Value(0)).current;
|
||||
|
||||
@@ -42,8 +46,40 @@ export default function SelectExerciseScreen() {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let aborted = false;
|
||||
const CACHE_KEY = '@exercise_config_v1';
|
||||
(async () => {
|
||||
try {
|
||||
const cached = await AsyncStorage.getItem(CACHE_KEY);
|
||||
if (cached && !aborted) {
|
||||
const parsed = JSON.parse(cached);
|
||||
const items = normalizeToLibraryItems(parsed);
|
||||
if (items.length) {
|
||||
setServerLibrary(items);
|
||||
const cats = Array.from(new Set(items.map((i) => i.category)));
|
||||
setServerCategories(cats);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
const resp = await fetchExerciseConfig();
|
||||
console.log('fetchExerciseConfig', resp);
|
||||
if (aborted) return;
|
||||
const items = normalizeToLibraryItems(resp);
|
||||
setServerLibrary(items);
|
||||
const cats = Array.from(new Set(items.map((i) => i.category)));
|
||||
setServerCategories(cats);
|
||||
try { await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(resp)); } catch {}
|
||||
} catch (err) {}
|
||||
})();
|
||||
return () => { aborted = true; };
|
||||
}, []);
|
||||
|
||||
const categories = useMemo(() => ['全部', ...getCategories()], []);
|
||||
const categories = useMemo(() => {
|
||||
const base = serverCategories ?? getCategories();
|
||||
return ['全部', ...base];
|
||||
}, [serverCategories]);
|
||||
const mainCategories = useMemo(() => {
|
||||
const preferred = ['全部', '核心与腹部', '脊柱与后链', '侧链与髋', '平衡与支撑'];
|
||||
const exists = (name: string) => categories.includes(name);
|
||||
@@ -53,13 +89,17 @@ export default function SelectExerciseScreen() {
|
||||
while (picked.length < 5 && rest.length) picked.push(rest.shift() as string);
|
||||
return picked;
|
||||
}, [categories]);
|
||||
const library = useMemo(() => serverLibrary ?? EXERCISE_LIBRARY, [serverLibrary]);
|
||||
const filtered = useMemo(() => {
|
||||
const base = searchExercises(keyword);
|
||||
const kw = keyword.trim().toLowerCase();
|
||||
const base = kw
|
||||
? library.filter((e) => e.name.toLowerCase().includes(kw) || (e.description || '').toLowerCase().includes(kw))
|
||||
: library;
|
||||
if (category === '全部') return base;
|
||||
return base.filter((e) => e.category === category);
|
||||
}, [keyword, category]);
|
||||
}, [keyword, category, library]);
|
||||
|
||||
const selected = useMemo(() => EXERCISE_LIBRARY.find((e) => e.key === selectedKey) || null, [selectedKey]);
|
||||
const selected = useMemo(() => library.find((e) => e.key === selectedKey) || null, [selectedKey, library]);
|
||||
|
||||
useEffect(() => {
|
||||
Animated.timing(controlsOpacity, {
|
||||
@@ -82,20 +122,7 @@ export default function SelectExerciseScreen() {
|
||||
},
|
||||
}));
|
||||
console.log('addExercise', currentDate, selected.key, sets, reps);
|
||||
// 同步到后端(读取最新 store 需要在返回后由首页触发 load,或此处直接上报)
|
||||
// 简单做法:直接上报新增项(其余项由后端合并/覆盖)
|
||||
dispatch(syncCheckin({
|
||||
date: currentDate,
|
||||
items: [
|
||||
{
|
||||
key: selected.key,
|
||||
name: selected.name,
|
||||
category: selected.category,
|
||||
sets: Math.max(1, sets),
|
||||
reps: reps && reps > 0 ? reps : undefined,
|
||||
},
|
||||
],
|
||||
}));
|
||||
// 自动同步将由中间件处理,无需手动调用 syncCheckin
|
||||
router.back();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user