test
This commit is contained in:
parent
8da78d1b0d
commit
11030c5601
3 changed files with 231 additions and 19 deletions
|
|
@ -11,6 +11,7 @@ import { RootStackParamList } from '../../navigation/AppNavigator';
|
|||
|
||||
interface CatalogSectionProps {
|
||||
catalog: CatalogContent;
|
||||
onPosterPress?: (content: StreamingContent) => void;
|
||||
}
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
|
@ -53,12 +54,17 @@ const calculatePosterLayout = (screenWidth: number) => {
|
|||
const posterLayout = calculatePosterLayout(width);
|
||||
const POSTER_WIDTH = posterLayout.posterWidth;
|
||||
|
||||
const CatalogSection = ({ catalog }: CatalogSectionProps) => {
|
||||
const CatalogSection = ({ catalog, onPosterPress }: CatalogSectionProps) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
const handleContentPress = (id: string, type: string) => {
|
||||
navigation.navigate('Metadata', { id, type, addonId: catalog.addon });
|
||||
const content = catalog.items.find(item => item.id === id && item.type === type);
|
||||
if (content && onPosterPress) {
|
||||
onPosterPress(content);
|
||||
} else {
|
||||
navigation.navigate('Metadata', { id, type, addonId: catalog.addon });
|
||||
}
|
||||
};
|
||||
|
||||
const renderContentItem = ({ item, index }: { item: StreamingContent, index: number }) => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import { imageCacheService } from '../../services/imageCacheService';
|
|||
|
||||
interface FeaturedContentProps {
|
||||
featuredContent: StreamingContent | null;
|
||||
isSaved?: boolean;
|
||||
handleSaveToLibrary?: () => Promise<void>;
|
||||
}
|
||||
|
||||
// Cache to store preloaded images
|
||||
|
|
@ -218,6 +220,15 @@ const FeaturedContent = ({ featuredContent }: FeaturedContentProps) => {
|
|||
)}
|
||||
</View>
|
||||
|
||||
{/* Description Section */}
|
||||
{featuredContent.description && (
|
||||
<View style={styles.descriptionContainer}>
|
||||
<Text style={[styles.descriptionText, Platform.isTV && styles.descriptionTextTV]} numberOfLines={Platform.isTV ? 3 : 2}>
|
||||
{featuredContent.description}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Enhanced Metadata Section */}
|
||||
<View style={styles.metadataContainer}>
|
||||
{/* Genres */}
|
||||
|
|
@ -241,6 +252,30 @@ const FeaturedContent = ({ featuredContent }: FeaturedContentProps) => {
|
|||
)}
|
||||
</View>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.actionButtonsContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.playButton}
|
||||
onPress={() => navigation.navigate('Metadata', {
|
||||
id: featuredContent.id,
|
||||
type: featuredContent.type
|
||||
})}
|
||||
hasTVPreferredFocus={Platform.isTV}
|
||||
tvParallaxProperties={Platform.isTV ? {
|
||||
enabled: true,
|
||||
shiftDistanceX: 4.0,
|
||||
shiftDistanceY: 4.0,
|
||||
tiltAngle: 0.05,
|
||||
magnification: 1.1,
|
||||
} : undefined}
|
||||
>
|
||||
<MaterialIcons name="play-arrow" size={Platform.isTV ? 28 : 24} color="#000" />
|
||||
<Text style={styles.playButtonText}>Play</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
</View>
|
||||
|
||||
|
||||
</View>
|
||||
</TVFocusGuideView>
|
||||
|
|
@ -252,9 +287,9 @@ const FeaturedContent = ({ featuredContent }: FeaturedContentProps) => {
|
|||
const styles = StyleSheet.create({
|
||||
featuredContainer: {
|
||||
width: '100%',
|
||||
height: Platform.isTV ? height * 0.75 : height * 0.55,
|
||||
height: Platform.isTV ? height * 0.5 : height * 0.4,
|
||||
marginTop: 0,
|
||||
marginBottom: Platform.isTV ? 24 : 12,
|
||||
marginBottom: Platform.isTV ? 16 : 8,
|
||||
position: 'relative',
|
||||
borderRadius: Platform.isTV ? 0 : 12,
|
||||
overflow: 'hidden',
|
||||
|
|
@ -324,23 +359,25 @@ const styles = StyleSheet.create({
|
|||
marginBottom: Platform.isTV ? 24 : 16,
|
||||
paddingHorizontal: 0,
|
||||
position: 'relative',
|
||||
height: Platform.isTV ? 160 : 160,
|
||||
height: Platform.isTV ? 120 : 100,
|
||||
width: '100%',
|
||||
marginLeft: Platform.isTV ? -200 : 0,
|
||||
marginLeft: 0,
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
featuredLogo: {
|
||||
width: width * 0.9,
|
||||
height: 160,
|
||||
width: Platform.isTV ? 300 : 250,
|
||||
height: Platform.isTV ? 120 : 100,
|
||||
marginBottom: 0,
|
||||
alignSelf: 'flex-start',
|
||||
position: Platform.isTV ? 'absolute' : 'relative',
|
||||
left: Platform.isTV ? 0 : 'auto',
|
||||
position: 'relative',
|
||||
left: 0,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
featuredLogoTV: {
|
||||
width: width * 0.8,
|
||||
height: 200,
|
||||
maxWidth: 900,
|
||||
position: 'absolute',
|
||||
width: 300,
|
||||
height: 120,
|
||||
maxWidth: 300,
|
||||
position: 'relative',
|
||||
left: 0,
|
||||
},
|
||||
featuredTitleText: {
|
||||
|
|
@ -389,6 +426,23 @@ const styles = StyleSheet.create({
|
|||
yearContainer: {
|
||||
marginTop: 8,
|
||||
},
|
||||
descriptionContainer: {
|
||||
marginBottom: Platform.isTV ? 24 : 16,
|
||||
maxWidth: Platform.isTV ? width * 0.5 : width * 0.8,
|
||||
},
|
||||
descriptionText: {
|
||||
fontSize: 14,
|
||||
color: '#FFFFFF',
|
||||
opacity: 0.9,
|
||||
lineHeight: 20,
|
||||
textShadowColor: 'rgba(0,0,0,0.6)',
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
},
|
||||
descriptionTextTV: {
|
||||
fontSize: 18,
|
||||
lineHeight: 26,
|
||||
},
|
||||
yearText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
|
|
@ -417,6 +471,34 @@ const styles = StyleSheet.create({
|
|||
fontSize: Platform.isTV ? 18 : 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
actionButtonsContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: Platform.isTV ? 20 : 16,
|
||||
marginTop: Platform.isTV ? 24 : 16,
|
||||
},
|
||||
playButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#FFFFFF',
|
||||
paddingHorizontal: Platform.isTV ? 48 : 32,
|
||||
paddingVertical: Platform.isTV ? 16 : 12,
|
||||
borderRadius: Platform.isTV ? 12 : 8,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
minWidth: Platform.isTV ? 200 : 140,
|
||||
},
|
||||
playButtonText: {
|
||||
color: '#000',
|
||||
fontSize: Platform.isTV ? 18 : 16,
|
||||
fontWeight: '700',
|
||||
marginLeft: 8,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default React.memo(FeaturedContent);
|
||||
|
|
@ -106,6 +106,117 @@ const SkeletonCatalog = React.memo(() => {
|
|||
});
|
||||
|
||||
const HomeScreen = () => {
|
||||
const styles = StyleSheet.create<any>({
|
||||
stickyHeroContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 90,
|
||||
},
|
||||
loadingMainContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 90,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 12,
|
||||
fontSize: 14,
|
||||
},
|
||||
loadingMoreCatalogs: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
backgroundColor: 'rgba(0,0,0,0.2)',
|
||||
borderRadius: 8,
|
||||
},
|
||||
loadingMoreText: {
|
||||
marginLeft: 12,
|
||||
fontSize: 14,
|
||||
},
|
||||
catalogPlaceholder: {
|
||||
marginBottom: 24,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
placeholderHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
placeholderTitle: {
|
||||
width: 150,
|
||||
height: 20,
|
||||
borderRadius: 4,
|
||||
},
|
||||
placeholderPosters: {
|
||||
flexDirection: 'row',
|
||||
paddingVertical: 8,
|
||||
gap: 8,
|
||||
},
|
||||
placeholderPoster: {
|
||||
width: POSTER_WIDTH,
|
||||
aspectRatio: 2/3,
|
||||
borderRadius: 4,
|
||||
marginRight: 2,
|
||||
},
|
||||
emptyCatalog: {
|
||||
padding: 32,
|
||||
alignItems: 'center',
|
||||
margin: 16,
|
||||
borderRadius: 16,
|
||||
},
|
||||
addCatalogButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 30,
|
||||
marginTop: 16,
|
||||
elevation: 3,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 3,
|
||||
},
|
||||
addCatalogButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
marginLeft: 8,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
featuredContainer: {
|
||||
width: '100%',
|
||||
height: height * 0.6,
|
||||
marginTop: Platform.OS === 'ios' ? 0 : 0,
|
||||
marginBottom: 8,
|
||||
position: 'relative',
|
||||
},
|
||||
featuredBanner: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
featuredGradient: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'space-between',
|
||||
}
|
||||
});
|
||||
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const { currentTheme } = useTheme();
|
||||
|
|
@ -513,7 +624,7 @@ const HomeScreen = () => {
|
|||
case 'catalog':
|
||||
return (
|
||||
<Animated.View entering={FadeIn.duration(300)}>
|
||||
<CatalogSection catalog={item.catalog} />
|
||||
<CatalogSection catalog={item.catalog} onPosterPress={handlePosterPress} />
|
||||
</Animated.View>
|
||||
);
|
||||
case 'placeholder':
|
||||
|
|
@ -581,6 +692,12 @@ const HomeScreen = () => {
|
|||
), [catalogsLoading, catalogs, loadedCatalogCount, totalCatalogsRef.current, navigation, currentTheme.colors]);
|
||||
|
||||
// Memoize the main content section
|
||||
const [selectedContent, setSelectedContent] = useState<StreamingContent | null>(null);
|
||||
|
||||
const handlePosterPress = useCallback((content: StreamingContent) => {
|
||||
setSelectedContent(content);
|
||||
}, []);
|
||||
|
||||
const renderMainContent = useMemo(() => {
|
||||
if (isLoading) return null;
|
||||
|
||||
|
|
@ -597,7 +714,7 @@ const HomeScreen = () => {
|
|||
keyExtractor={item => item.key}
|
||||
contentContainerStyle={[
|
||||
styles.scrollContent,
|
||||
{ paddingTop: Platform.OS === 'ios' ? 100 : 90 }
|
||||
{ paddingTop: 0 }
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
|
|
@ -612,8 +729,8 @@ const HomeScreen = () => {
|
|||
autoscrollToTopThreshold: 10
|
||||
}}
|
||||
getItemLayout={(data, index) => ({
|
||||
length: index === 0 ? 400 : 280, // Approximate heights for different item types
|
||||
offset: index === 0 ? 0 : 400 + (index - 1) * 280,
|
||||
length: 280,
|
||||
offset: index * 280,
|
||||
index,
|
||||
})}
|
||||
/>
|
||||
|
|
@ -670,7 +787,14 @@ const calculatePosterLayout = (screenWidth: number) => {
|
|||
const posterLayout = calculatePosterLayout(width);
|
||||
const POSTER_WIDTH = posterLayout.posterWidth;
|
||||
|
||||
const styles = StyleSheet.create<any>({
|
||||
const styles = StyleSheet.create<any>({
|
||||
stickyHeroContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue