Enhance category and tag selection functionality in HomeScreen

This commit is contained in:
zimplexing
2025-07-10 13:09:01 +08:00
parent d42a3e014e
commit caba0f3d70
4 changed files with 72 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useRef } from "react"; 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 } from "react-native";
import { ThemedView } from "@/components/ThemedView"; import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText"; import { ThemedText } from "@/components/ThemedText";
@@ -19,6 +19,7 @@ export default function HomeScreen() {
const router = useRouter(); const router = useRouter();
const colorScheme = "dark"; const colorScheme = "dark";
const flatListRef = useRef<FlatList>(null); const flatListRef = useRef<FlatList>(null);
const [selectedTag, setSelectedTag] = useState<string | null>(null);
const { const {
categories, categories,
@@ -42,14 +43,38 @@ export default function HomeScreen() {
); );
useEffect(() => { useEffect(() => {
fetchInitialData(); if (selectedCategory && !selectedCategory.tags) {
flatListRef.current?.scrollToOffset({ animated: false, offset: 0 }); 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];
setSelectedTag(defaultTag);
selectCategory({ ...selectedCategory, tag: defaultTag });
}
}, [selectedCategory, fetchInitialData]); }, [selectedCategory, fetchInitialData]);
useEffect(() => {
if (selectedCategory && selectedCategory.tag) {
fetchInitialData();
flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
}
}, [selectedCategory?.tag]);
const handleCategorySelect = (category: Category) => { const handleCategorySelect = (category: Category) => {
setSelectedTag(null);
selectCategory(category); selectCategory(category);
}; };
const handleTagSelect = (tag: string) => {
setSelectedTag(tag);
if (selectedCategory) {
// Create a new category object with the selected tag
const categoryWithTag = { ...selectedCategory, tag: tag };
selectCategory(categoryWithTag);
}
};
const renderCategory = ({ item }: { item: Category }) => { const renderCategory = ({ item }: { item: Category }) => {
const isSelected = selectedCategory?.title === item.title; const isSelected = selectedCategory?.title === item.title;
return ( return (
@@ -119,6 +144,33 @@ export default function HomeScreen() {
/> />
</View> </View>
{/* Sub-category Tags */}
{selectedCategory && selectedCategory.tags && (
<View style={styles.categoryContainer}>
<FlatList
data={selectedCategory.tags}
renderItem={({ item, index }) => {
const isSelected = selectedTag === item;
return (
<StyledButton
hasTVPreferredFocus={index === 0} // Focus the first tag by default
text={item}
onPress={() => handleTagSelect(item)}
isSelected={isSelected}
style={styles.categoryButton}
textStyle={styles.categoryText}
variant="ghost"
/>
);
}}
keyExtractor={(item) => item}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.categoryListContent}
/>
</View>
)}
{/* 内容网格 */} {/* 内容网格 */}
{loading ? ( {loading ? (
<View style={styles.centerContainer}> <View style={styles.centerContainer}>
@@ -143,7 +195,7 @@ export default function HomeScreen() {
ListFooterComponent={renderFooter} ListFooterComponent={renderFooter}
ListEmptyComponent={ ListEmptyComponent={
<View style={styles.centerContainer}> <View style={styles.centerContainer}>
<ThemedText></ThemedText> <ThemedText>{selectedCategory?.tags ? "请选择一个子分类" : "该分类下暂无内容"}</ThemedText>
</View> </View>
} }
/> />

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { View, StyleSheet, TouchableOpacity, ActivityIndicator, BackHandler } from "react-native"; import { StyleSheet, TouchableOpacity, ActivityIndicator, BackHandler } from "react-native";
import { useLocalSearchParams, useRouter } from "expo-router"; import { useLocalSearchParams, useRouter } from "expo-router";
import { Video, ResizeMode } from "expo-av"; import { Video, ResizeMode } from "expo-av";
import { useKeepAwake } from "expo-keep-awake"; import { useKeepAwake } from "expo-keep-awake";
@@ -37,9 +37,6 @@ export default function PlayScreen() {
introEndTime, introEndTime,
setVideoRef, setVideoRef,
loadVideo, loadVideo,
playEpisode,
togglePlayPause,
seek,
handlePlaybackStatusUpdate, handlePlaybackStatusUpdate,
setShowControls, setShowControls,
setShowEpisodeModal, setShowEpisodeModal,
@@ -100,6 +97,8 @@ export default function PlayScreen() {
ref={videoRef} ref={videoRef}
style={styles.videoPlayer} style={styles.videoPlayer}
source={{ uri: currentEpisode?.url }} source={{ uri: currentEpisode?.url }}
usePoster
posterSource={{ uri: detail?.videoInfo.cover ?? "" }}
resizeMode={ResizeMode.CONTAIN} resizeMode={ResizeMode.CONTAIN}
onPlaybackStatusUpdate={handlePlaybackStatusUpdate} onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
onLoad={() => { onLoad={() => {

View File

@@ -2,7 +2,7 @@
"name": "OrionTV", "name": "OrionTV",
"private": true, "private": true,
"main": "expo-router/entry", "main": "expo-router/entry",
"version": "1.1.0", "version": "1.1.1",
"scripts": { "scripts": {
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start", "start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start", "start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",

View File

@@ -21,19 +21,16 @@ export interface Category {
title: string; title: string;
type?: 'movie' | 'tv' | 'record'; type?: 'movie' | 'tv' | 'record';
tag?: string; tag?: string;
tags?: string[];
} }
const initialCategories: Category[] = [ const initialCategories: Category[] = [
{ title: '最近播放', type: 'record' }, { title: '最近播放', type: 'record' },
{ title: '热门剧集', type: 'tv', tag: '热门' }, { title: '热门剧集', type: 'tv', tag: '热门' },
{ title: '电视剧', type: 'tv', tags: [ '国产剧', '美剧', '英剧', '韩剧', '日剧', '港剧', '日本动画', '动画'] },
{ title: '电影', type: 'movie', tags: ['热门', '最新', '经典', '豆瓣高分', '冷门佳片', '华语', '欧美', '韩国', '日本', '动作', '喜剧', '爱情', '科幻', '悬疑', '恐怖'] },
{ title: '综艺', type: 'tv', tag: '综艺' }, { title: '综艺', type: 'tv', tag: '综艺' },
{ title: '热门电影', type: 'movie', tag: '热门' },
{ title: '豆瓣 Top250', type: 'movie', tag: 'top250' }, { title: '豆瓣 Top250', type: 'movie', tag: 'top250' },
{ title: '儿童', type: 'movie', tag: '少儿' },
{ title: '美剧', type: 'tv', tag: '美剧' },
{ title: '韩剧', type: 'tv', tag: '韩剧' },
{ title: '日剧', type: 'tv', tag: '日剧' },
{ title: '日漫', type: 'tv', tag: '日本动画' },
]; ];
interface HomeState { interface HomeState {
@@ -102,6 +99,9 @@ const useHomeStore = create<HomeState>((set, get) => ({
hasMore: true, hasMore: true,
})); }));
} }
} else if (selectedCategory.tags) {
// It's a container category, do not load content, but clear current content
set({ contentData: [], hasMore: false });
} else { } else {
set({ hasMore: false }); set({ hasMore: false });
} }
@@ -117,8 +117,12 @@ const useHomeStore = create<HomeState>((set, get) => ({
}, },
selectCategory: (category: Category) => { selectCategory: (category: Category) => {
set({ selectedCategory: category }); const currentCategory = get().selectedCategory;
get().fetchInitialData(); // Only fetch new data if the category or tag actually changes
if (currentCategory.title !== category.title || currentCategory.tag !== category.tag) {
set({ selectedCategory: category, contentData: [], pageStart: 0, hasMore: true, error: null });
get().fetchInitialData();
}
}, },
refreshPlayRecords: async () => { refreshPlayRecords: async () => {