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:
tapframe 2025-06-30 15:32:07 +05:30
parent d32e4d3ad0
commit 4b64dc64ee
2 changed files with 86 additions and 55 deletions

View file

@ -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;

View file

@ -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>