mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
Enhance category and tag selection functionality in HomeScreen
This commit is contained in:
@@ -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>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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={() => {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user