feat: Update scroll experience

This commit is contained in:
zimplexing
2025-07-25 15:39:23 +08:00
parent c9587d7070
commit 5992a89db4
5 changed files with 317 additions and 208 deletions

View File

@@ -1,11 +1,12 @@
import React, { useEffect } from "react";
import { View, FlatList, StyleSheet, ActivityIndicator } from "react-native";
import { View, StyleSheet, ActivityIndicator } from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import useFavoritesStore from "@/stores/favoritesStore";
import { Favorite } from "@/services/storage";
import VideoCard from "@/components/VideoCard.tv";
import { api } from "@/services/api";
import CustomScrollView from "@/components/CustomScrollView";
export default function FavoritesScreen() {
const { favorites, loading, error, fetchFavorites } = useFavoritesStore();
@@ -14,31 +15,7 @@ export default function FavoritesScreen() {
fetchFavorites();
}, [fetchFavorites]);
if (loading) {
return (
<ThemedView style={styles.centered}>
<ActivityIndicator size="large" />
</ThemedView>
);
}
if (error) {
return (
<ThemedView style={styles.centered}>
<ThemedText type="subtitle">{error}</ThemedText>
</ThemedView>
);
}
if (favorites.length === 0) {
return (
<ThemedView style={styles.centered}>
<ThemedText type="subtitle"></ThemedText>
</ThemedView>
);
}
const renderItem = ({ item }: { item: Favorite & { key: string } }) => {
const renderItem = ({ item }: { item: Favorite & { key: string }; index: number }) => {
const [source, id] = item.key.split("+");
return (
<VideoCard
@@ -60,12 +37,13 @@ export default function FavoritesScreen() {
<View style={styles.headerContainer}>
<ThemedText style={styles.headerTitle}></ThemedText>
</View>
<FlatList
<CustomScrollView
data={favorites}
renderItem={renderItem}
keyExtractor={(item) => item.key}
numColumns={5}
contentContainerStyle={styles.list}
loading={loading}
error={error}
emptyMessage="暂无收藏"
/>
</ThemedView>
);

View File

@@ -9,15 +9,17 @@ import { Search, Settings, LogOut, Heart } from "lucide-react-native";
import { StyledButton } from "@/components/StyledButton";
import useHomeStore, { RowItem, Category } from "@/stores/homeStore";
import useAuthStore from "@/stores/authStore";
import CustomScrollView from "@/components/CustomScrollView";
const NUM_COLUMNS = 5;
const { width } = Dimensions.get("window");
const ITEM_WIDTH = width / NUM_COLUMNS - 24;
// Threshold for triggering load more data (in pixels)
const LOAD_MORE_THRESHOLD = 200;
export default function HomeScreen() {
const router = useRouter();
const colorScheme = "dark";
const flatListRef = useRef<FlatList>(null);
const [selectedTag, setSelectedTag] = useState<string | null>(null);
const {
@@ -43,7 +45,6 @@ export default function HomeScreen() {
useEffect(() => {
if (selectedCategory && !selectedCategory.tags) {
fetchInitialData();
flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
} else if (selectedCategory?.tags && !selectedCategory.tag) {
// Category with tags selected, but no specific tag yet. Select the first one.
const defaultTag = selectedCategory.tags[0];
@@ -55,7 +56,6 @@ export default function HomeScreen() {
useEffect(() => {
if (selectedCategory && selectedCategory.tag) {
fetchInitialData();
flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
}
}, [fetchInitialData, selectedCategory, selectedCategory.tag]);
@@ -86,7 +86,7 @@ export default function HomeScreen() {
);
};
const renderContentItem = ({ item }: { item: RowItem }) => (
const renderContentItem = ({ item, index }: { item: RowItem; index: number }) => (
<View style={styles.itemContainer}>
<VideoCard
id={item.id}
@@ -196,21 +196,17 @@ export default function HomeScreen() {
</ThemedText>
</View>
) : (
<FlatList
ref={flatListRef}
<CustomScrollView
data={contentData}
renderItem={renderContentItem}
keyExtractor={(item, index) => `${item.source}-${item.id}-${index}`}
numColumns={NUM_COLUMNS}
contentContainerStyle={styles.listContent}
loading={loading}
loadingMore={loadingMore}
error={error}
onEndReached={loadMoreData}
onEndReachedThreshold={0.5}
loadMoreThreshold={LOAD_MORE_THRESHOLD}
emptyMessage={selectedCategory?.tags ? "请选择一个子分类" : "该分类下暂无内容"}
ListFooterComponent={renderFooter}
ListEmptyComponent={
<View style={styles.centerContainer}>
<ThemedText>{selectedCategory?.tags ? "请选择一个子分类" : "该分类下暂无内容"}</ThemedText>
</View>
}
/>
)}
</ThemedView>
@@ -272,7 +268,6 @@ const styles = StyleSheet.create({
},
itemContainer: {
margin: 8,
width: ITEM_WIDTH,
alignItems: "center",
},
});

View File

@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from "react";
import { View, TextInput, StyleSheet, FlatList, Alert, Keyboard } from "react-native";
import { View, TextInput, StyleSheet, Alert, Keyboard, ActivityIndicator } from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import VideoCard from "@/components/VideoCard.tv";
@@ -12,6 +12,7 @@ import { RemoteControlModal } from "@/components/RemoteControlModal";
import { useSettingsStore } from "@/stores/settingsStore";
import { useRouter } from "expo-router";
import { Colors } from "@/constants/Colors";
import CustomScrollView from "@/components/CustomScrollView";
export default function SearchScreen() {
const [keyword, setKeyword] = useState("");
@@ -80,7 +81,7 @@ export default function SearchScreen() {
showRemoteModal();
};
const renderItem = ({ item }: { item: SearchResult }) => (
const renderItem = ({ item, index }: { item: SearchResult; index: number }) => (
<VideoCard
id={item.id.toString()}
source={item.source}
@@ -129,17 +130,13 @@ export default function SearchScreen() {
<ThemedText style={styles.errorText}>{error}</ThemedText>
</View>
) : (
<FlatList
<CustomScrollView
data={results}
renderItem={renderItem}
keyExtractor={(item, index) => `${item.id}-${item.source}-${index}`}
numColumns={5} // Adjust based on your card size and desired layout
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<View style={styles.centerContainer}>
<ThemedText></ThemedText>
</View>
}
numColumns={5}
loading={loading}
error={error}
emptyMessage="输入关键词开始搜索"
/>
)}
<RemoteControlModal />