mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Refactor ContentItem component to improve layout and add title display
This update modifies the ContentItem component by introducing a new itemContainer style for better layout management. A title Text element has been added below the image, enhancing the user interface by displaying the item's name. The overall structure has been adjusted for improved readability and maintainability.
This commit is contained in:
parent
d32e4d3ad0
commit
4b64dc64ee
2 changed files with 86 additions and 55 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform } from 'react-native';
|
||||
import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform, Text } from 'react-native';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
|
@ -90,54 +90,59 @@ const ContentItem = React.memo(({ item, onPress }: ContentItemProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={styles.contentItem}
|
||||
activeOpacity={0.7}
|
||||
onPress={handlePress}
|
||||
onLongPress={handleLongPress}
|
||||
delayLongPress={300}
|
||||
>
|
||||
<View style={styles.contentItemContainer}>
|
||||
<ExpoImage
|
||||
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
|
||||
style={styles.poster}
|
||||
contentFit="cover"
|
||||
cachePolicy="memory"
|
||||
transition={200}
|
||||
placeholder={{ uri: 'https://via.placeholder.com/300x450' }}
|
||||
placeholderContentFit="cover"
|
||||
recyclingKey={item.id}
|
||||
onLoadStart={() => {
|
||||
setImageLoaded(false);
|
||||
setImageError(false);
|
||||
}}
|
||||
onLoadEnd={() => setImageLoaded(true)}
|
||||
onError={() => {
|
||||
setImageError(true);
|
||||
setImageLoaded(true);
|
||||
}}
|
||||
/>
|
||||
{(!imageLoaded || imageError) && (
|
||||
<View style={[styles.loadingOverlay, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
||||
{!imageError ? (
|
||||
<ActivityIndicator color={currentTheme.colors.primary} size="small" />
|
||||
) : (
|
||||
<MaterialIcons name="broken-image" size={24} color={currentTheme.colors.lightGray} />
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{isWatched && (
|
||||
<View style={styles.watchedIndicator}>
|
||||
<MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success} />
|
||||
</View>
|
||||
)}
|
||||
{item.inLibrary && (
|
||||
<View style={styles.libraryBadge}>
|
||||
<MaterialIcons name="bookmark" size={16} color={currentTheme.colors.white} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.itemContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.contentItem}
|
||||
activeOpacity={0.7}
|
||||
onPress={handlePress}
|
||||
onLongPress={handleLongPress}
|
||||
delayLongPress={300}
|
||||
>
|
||||
<View style={styles.contentItemContainer}>
|
||||
<ExpoImage
|
||||
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
|
||||
style={styles.poster}
|
||||
contentFit="cover"
|
||||
cachePolicy="memory"
|
||||
transition={200}
|
||||
placeholder={{ uri: 'https://via.placeholder.com/300x450' }}
|
||||
placeholderContentFit="cover"
|
||||
recyclingKey={item.id}
|
||||
onLoadStart={() => {
|
||||
setImageLoaded(false);
|
||||
setImageError(false);
|
||||
}}
|
||||
onLoadEnd={() => setImageLoaded(true)}
|
||||
onError={() => {
|
||||
setImageError(true);
|
||||
setImageLoaded(true);
|
||||
}}
|
||||
/>
|
||||
{(!imageLoaded || imageError) && (
|
||||
<View style={[styles.loadingOverlay, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
||||
{!imageError ? (
|
||||
<ActivityIndicator color={currentTheme.colors.primary} size="small" />
|
||||
) : (
|
||||
<MaterialIcons name="broken-image" size={24} color={currentTheme.colors.lightGray} />
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{isWatched && (
|
||||
<View style={styles.watchedIndicator}>
|
||||
<MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success} />
|
||||
</View>
|
||||
)}
|
||||
{item.inLibrary && (
|
||||
<View style={styles.libraryBadge}>
|
||||
<MaterialIcons name="bookmark" size={16} color={currentTheme.colors.white} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.title, { color: currentTheme.colors.text }]} numberOfLines={2}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<DropUpMenu
|
||||
visible={menuVisible}
|
||||
|
|
@ -150,6 +155,9 @@ const ContentItem = React.memo(({ item, onPress }: ContentItemProps) => {
|
|||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
itemContainer: {
|
||||
width: POSTER_WIDTH,
|
||||
},
|
||||
contentItem: {
|
||||
width: POSTER_WIDTH,
|
||||
aspectRatio: 2/3,
|
||||
|
|
@ -164,6 +172,7 @@ const styles = StyleSheet.create({
|
|||
shadowRadius: 6,
|
||||
borderWidth: 0.5,
|
||||
borderColor: 'rgba(255,255,255,0.12)',
|
||||
marginBottom: 8,
|
||||
},
|
||||
contentItemContainer: {
|
||||
width: '100%',
|
||||
|
|
@ -201,6 +210,13 @@ const styles = StyleSheet.create({
|
|||
borderRadius: 8,
|
||||
padding: 4,
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
marginTop: 4,
|
||||
textAlign: 'center',
|
||||
fontFamily: 'SpaceMono-Regular',
|
||||
}
|
||||
});
|
||||
|
||||
export default ContentItem;
|
||||
|
|
@ -188,6 +188,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
const { addonId, type, id, name: originalName, genreFilter } = route.params;
|
||||
const [items, setItems] = useState<Meta[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [paginating, setPaginating] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
|
|
@ -198,6 +199,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
const colors = currentTheme.colors;
|
||||
const styles = createStyles(colors);
|
||||
const isDarkMode = true;
|
||||
const isInitialRender = React.useRef(true);
|
||||
|
||||
const { getCustomName, isLoadingCustomNames } = useCustomCatalogNames();
|
||||
|
||||
|
|
@ -262,7 +264,10 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
try {
|
||||
if (shouldRefresh) {
|
||||
setRefreshing(true);
|
||||
} else if (pageNum === 1) {
|
||||
setHasMore(true); // Reset hasMore on refresh
|
||||
} else if (pageNum > 1) {
|
||||
setPaginating(true);
|
||||
} else {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
|
|
@ -360,6 +365,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
setHasMore(false);
|
||||
} else {
|
||||
foundItems = true;
|
||||
setHasMore(true); // Ensure hasMore is true if we found items
|
||||
}
|
||||
|
||||
if (shouldRefresh || pageNum === 1) {
|
||||
|
|
@ -442,8 +448,11 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
index === self.findIndex((t) => t.id === item.id)
|
||||
);
|
||||
|
||||
if (uniqueItems.length === 0) {
|
||||
if (uniqueItems.length === 0 && allItems.length === 0) {
|
||||
setHasMore(false);
|
||||
} else {
|
||||
foundItems = true;
|
||||
setHasMore(true); // Ensure hasMore is true if we found items
|
||||
}
|
||||
|
||||
if (shouldRefresh || pageNum === 1) {
|
||||
|
|
@ -467,25 +476,31 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
setPaginating(false);
|
||||
}
|
||||
}, [addonId, type, id, genreFilter, dataSource]);
|
||||
|
||||
useEffect(() => {
|
||||
loadItems(1);
|
||||
loadItems(1, true);
|
||||
}, [loadItems]);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
setPage(1);
|
||||
setItems([]); // Clear items on refresh
|
||||
loadItems(1, true);
|
||||
}, [loadItems]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
if (!loading && hasMore) {
|
||||
if (isInitialRender.current) {
|
||||
isInitialRender.current = false;
|
||||
return;
|
||||
}
|
||||
if (!loading && !paginating && hasMore && !refreshing) {
|
||||
const nextPage = page + 1;
|
||||
setPage(nextPage);
|
||||
loadItems(nextPage);
|
||||
}
|
||||
}, [loading, hasMore, page, loadItems]);
|
||||
}, [loading, paginating, hasMore, page, loadItems, refreshing]);
|
||||
|
||||
const renderItem = useCallback(({ item, index }: { item: Meta; index: number }) => {
|
||||
// Calculate if this is the last item in a row
|
||||
|
|
@ -638,7 +653,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
onEndReached={handleLoadMore}
|
||||
onEndReachedThreshold={0.5}
|
||||
ListFooterComponent={
|
||||
loading && items.length > 0 ? (
|
||||
paginating ? (
|
||||
<View style={styles.footer}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Reference in a new issue