From 187a75373572b47eebde2adc389d092d103e4357 Mon Sep 17 00:00:00 2001 From: zimplexing Date: Mon, 28 Jul 2025 10:28:02 +0800 Subject: [PATCH 1/2] feat: Enhance UI with fade animations and implement data caching in home store --- app/index.tsx | 44 +++++++++++++++------ components/LoginModal.tsx | 8 +++- components/VideoCard.tv.tsx | 45 ++++++++++++++------- stores/homeStore.ts | 79 +++++++++++++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 38 deletions(-) diff --git a/app/index.tsx b/app/index.tsx index 34222f4..a62e59a 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useCallback, useRef, useState } from "react"; -import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Dimensions } from "react-native"; +import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Dimensions, Animated } from "react-native"; import { ThemedView } from "@/components/ThemedView"; import { ThemedText } from "@/components/ThemedText"; import { api } from "@/services/api"; @@ -21,6 +21,7 @@ export default function HomeScreen() { const router = useRouter(); const colorScheme = "dark"; const [selectedTag, setSelectedTag] = useState(null); + const fadeAnim = useRef(new Animated.Value(0)).current; const { categories, @@ -59,6 +60,18 @@ export default function HomeScreen() { } }, [fetchInitialData, selectedCategory, selectedCategory.tag]); + useEffect(() => { + if (!loading && contentData.length > 0) { + Animated.timing(fadeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }).start(); + } else if (loading) { + fadeAnim.setValue(0); + } + }, [loading, contentData.length, fadeAnim]); + const handleCategorySelect = (category: Category) => { setSelectedTag(null); selectCategory(category); @@ -196,18 +209,20 @@ export default function HomeScreen() { ) : ( - + + + )} ); @@ -266,6 +281,9 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, paddingBottom: 20, }, + contentContainer: { + flex: 1, + }, itemContainer: { margin: 8, alignItems: "center", diff --git a/components/LoginModal.tsx b/components/LoginModal.tsx index 77a3c1f..6391f1c 100644 --- a/components/LoginModal.tsx +++ b/components/LoginModal.tsx @@ -62,8 +62,12 @@ const LoginModal = () => { "本应用仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。", [{ text: "确定" }] ); - } catch { - Toast.show({ type: "error", text1: "登录失败", text2: "用户名或密码错误" }); + } catch (error) { + Toast.show({ + type: "error", + text1: "登录失败", + text2: error instanceof Error ? error.message : "用户名或密码错误", + }); } finally { setIsLoading(false); } diff --git a/components/VideoCard.tv.tsx b/components/VideoCard.tv.tsx index e248514..a953fde 100644 --- a/components/VideoCard.tv.tsx +++ b/components/VideoCard.tv.tsx @@ -1,6 +1,5 @@ -import React, { useState, useCallback, useRef, forwardRef } from "react"; -import { View, Text, Image, StyleSheet, TouchableOpacity, Alert } from "react-native"; -import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated"; +import React, { useState, useEffect, useCallback, useRef, forwardRef } from "react"; +import { View, Text, Image, StyleSheet, TouchableOpacity, Alert, Animated } from "react-native"; import { useRouter } from "expo-router"; import { Star, Play } from "lucide-react-native"; import { PlayRecordManager } from "@/services/storage"; @@ -46,16 +45,15 @@ const VideoCard = forwardRef( ) => { const router = useRouter(); const [isFocused, setIsFocused] = useState(false); + const [fadeAnim] = useState(new Animated.Value(0)); const longPressTriggered = useRef(false); - const scale = useSharedValue(1); + const scale = useRef(new Animated.Value(1)).current; - const animatedStyle = useAnimatedStyle(() => { - return { - transform: [{ scale: scale.value }], - }; - }); + const animatedStyle = { + transform: [{ scale }], + }; const handlePress = () => { if (longPressTriggered.current) { @@ -78,15 +76,32 @@ const VideoCard = forwardRef( const handleFocus = useCallback(() => { setIsFocused(true); - scale.value = withSpring(1.05, { damping: 15, stiffness: 200 }); + Animated.spring(scale, { + toValue: 1.05, + damping: 15, + stiffness: 200, + useNativeDriver: true, + }).start(); onFocus?.(); }, [scale, onFocus]); const handleBlur = useCallback(() => { setIsFocused(false); - scale.value = withSpring(1.0); + Animated.spring(scale, { + toValue: 1.0, + useNativeDriver: true, + }).start(); }, [scale]); + useEffect(() => { + Animated.timing(fadeAnim, { + toValue: 1, + duration: 400, + delay: Math.random() * 200, // 随机延迟创造交错效果 + useNativeDriver: true, + }).start(); + }, [fadeAnim]); + const handleLongPress = () => { // Only allow long press for items with progress (play records) if (progress === undefined) return; @@ -128,7 +143,7 @@ const VideoCard = forwardRef( const isContinueWatching = progress !== undefined && progress > 0 && progress < 1; return ( - + Promise; } +// 内存缓存,应用生命周期内有效 +const dataCache = new Map(); + const useHomeStore = create((set, get) => ({ categories: initialCategories, selectedCategory: initialCategories[0], @@ -83,6 +86,29 @@ const useHomeStore = create((set, get) => ({ fetchInitialData: async () => { const { apiBaseUrl } = useSettingsStore.getState(); await useAuthStore.getState().checkLoginStatus(apiBaseUrl); + + const { selectedCategory } = get(); + const cacheKey = `${selectedCategory.title}-${selectedCategory.tag || ''}`; + + // 最近播放不缓存,始终实时获取 + if (selectedCategory.type === 'record') { + set({ loading: true, contentData: [], pageStart: 0, hasMore: true, error: null }); + await get().loadMoreData(); + return; + } + + // 检查缓存 + if (dataCache.has(cacheKey)) { + set({ + loading: false, + contentData: dataCache.get(cacheKey)!, + pageStart: dataCache.get(cacheKey)!.length, + hasMore: false, + error: null + }); + return; + } + set({ loading: true, contentData: [], pageStart: 0, hasMore: true, error: null }); await get().loadMoreData(); }, @@ -133,11 +159,25 @@ const useHomeStore = create((set, get) => ({ id: item.title, source: "douban", })) as RowItem[]; - set((state) => ({ - contentData: pageStart === 0 ? newItems : [...state.contentData, ...newItems], - pageStart: state.pageStart + result.list.length, - hasMore: true, - })); + + const cacheKey = `${selectedCategory.title}-${selectedCategory.tag || ''}`; + + if (pageStart === 0) { + // 缓存新数据 + dataCache.set(cacheKey, newItems); + set((state) => ({ + contentData: newItems, + pageStart: result.list.length, + hasMore: true, + })); + } else { + // 增量加载时不缓存,直接追加 + set((state) => ({ + contentData: [...state.contentData, ...newItems], + pageStart: state.pageStart + result.list.length, + hasMore: true, + })); + } } } else if (selectedCategory.tags) { // It's a container category, do not load content, but clear current content @@ -158,10 +198,29 @@ const useHomeStore = create((set, get) => ({ selectCategory: (category: Category) => { const currentCategory = get().selectedCategory; - // Only fetch new data if the category or tag actually changes + const cacheKey = `${category.title}-${category.tag || ''}`; + + // 只有当分类或标签真正变化时才处理 if (currentCategory.title !== category.title || currentCategory.tag !== category.tag) { set({ selectedCategory: category, contentData: [], pageStart: 0, hasMore: true, error: null }); - get().fetchInitialData(); + + // 最近播放始终实时获取 + if (category.type === 'record') { + get().fetchInitialData(); + return; + } + + // 检查缓存,有则直接使用,无则请求 + if (dataCache.has(cacheKey)) { + set({ + contentData: dataCache.get(cacheKey)!, + pageStart: dataCache.get(cacheKey)!.length, + hasMore: false, + loading: false + }); + } else { + get().fetchInitialData(); + } } }, @@ -199,7 +258,11 @@ const useHomeStore = create((set, get) => ({ } return {}; }); - get().fetchInitialData(); + + // 如果当前是最近播放分类,强制刷新数据 + if (get().selectedCategory.type === "record") { + get().fetchInitialData(); + } }, })); From 10bfbbbf8eb4ade51427ccee3506c1009053bb44 Mon Sep 17 00:00:00 2001 From: zimplexing Date: Tue, 29 Jul 2025 19:15:57 +0800 Subject: [PATCH 2/2] fix: ensure data is refreshed for all categories by calling fetchInitialData unconditionally --- stores/homeStore.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/stores/homeStore.ts b/stores/homeStore.ts index abe7642..3939e90 100644 --- a/stores/homeStore.ts +++ b/stores/homeStore.ts @@ -259,10 +259,7 @@ const useHomeStore = create((set, get) => ({ return {}; }); - // 如果当前是最近播放分类,强制刷新数据 - if (get().selectedCategory.type === "record") { - get().fetchInitialData(); - } + get().fetchInitialData(); }, }));