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 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 { Image as ExpoImage } from 'expo-image';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { useTheme } from '../../contexts/ThemeContext'; import { useTheme } from '../../contexts/ThemeContext';
@ -90,54 +90,59 @@ const ContentItem = React.memo(({ item, onPress }: ContentItemProps) => {
return ( return (
<> <>
<TouchableOpacity <View style={styles.itemContainer}>
style={styles.contentItem} <TouchableOpacity
activeOpacity={0.7} style={styles.contentItem}
onPress={handlePress} activeOpacity={0.7}
onLongPress={handleLongPress} onPress={handlePress}
delayLongPress={300} onLongPress={handleLongPress}
> delayLongPress={300}
<View style={styles.contentItemContainer}> >
<ExpoImage <View style={styles.contentItemContainer}>
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }} <ExpoImage
style={styles.poster} source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
contentFit="cover" style={styles.poster}
cachePolicy="memory" contentFit="cover"
transition={200} cachePolicy="memory"
placeholder={{ uri: 'https://via.placeholder.com/300x450' }} transition={200}
placeholderContentFit="cover" placeholder={{ uri: 'https://via.placeholder.com/300x450' }}
recyclingKey={item.id} placeholderContentFit="cover"
onLoadStart={() => { recyclingKey={item.id}
setImageLoaded(false); onLoadStart={() => {
setImageError(false); setImageLoaded(false);
}} setImageError(false);
onLoadEnd={() => setImageLoaded(true)} }}
onError={() => { onLoadEnd={() => setImageLoaded(true)}
setImageError(true); onError={() => {
setImageLoaded(true); setImageError(true);
}} setImageLoaded(true);
/> }}
{(!imageLoaded || imageError) && ( />
<View style={[styles.loadingOverlay, { backgroundColor: currentTheme.colors.elevation2 }]}> {(!imageLoaded || imageError) && (
{!imageError ? ( <View style={[styles.loadingOverlay, { backgroundColor: currentTheme.colors.elevation2 }]}>
<ActivityIndicator color={currentTheme.colors.primary} size="small" /> {!imageError ? (
) : ( <ActivityIndicator color={currentTheme.colors.primary} size="small" />
<MaterialIcons name="broken-image" size={24} color={currentTheme.colors.lightGray} /> ) : (
)} <MaterialIcons name="broken-image" size={24} color={currentTheme.colors.lightGray} />
</View> )}
)} </View>
{isWatched && ( )}
<View style={styles.watchedIndicator}> {isWatched && (
<MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success} /> <View style={styles.watchedIndicator}>
</View> <MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success} />
)} </View>
{item.inLibrary && ( )}
<View style={styles.libraryBadge}> {item.inLibrary && (
<MaterialIcons name="bookmark" size={16} color={currentTheme.colors.white} /> <View style={styles.libraryBadge}>
</View> <MaterialIcons name="bookmark" size={16} color={currentTheme.colors.white} />
)} </View>
</View> )}
</TouchableOpacity> </View>
</TouchableOpacity>
<Text style={[styles.title, { color: currentTheme.colors.text }]} numberOfLines={2}>
{item.name}
</Text>
</View>
<DropUpMenu <DropUpMenu
visible={menuVisible} visible={menuVisible}
@ -150,6 +155,9 @@ const ContentItem = React.memo(({ item, onPress }: ContentItemProps) => {
}); });
const styles = StyleSheet.create({ const styles = StyleSheet.create({
itemContainer: {
width: POSTER_WIDTH,
},
contentItem: { contentItem: {
width: POSTER_WIDTH, width: POSTER_WIDTH,
aspectRatio: 2/3, aspectRatio: 2/3,
@ -164,6 +172,7 @@ const styles = StyleSheet.create({
shadowRadius: 6, shadowRadius: 6,
borderWidth: 0.5, borderWidth: 0.5,
borderColor: 'rgba(255,255,255,0.12)', borderColor: 'rgba(255,255,255,0.12)',
marginBottom: 8,
}, },
contentItemContainer: { contentItemContainer: {
width: '100%', width: '100%',
@ -201,6 +210,13 @@ const styles = StyleSheet.create({
borderRadius: 8, borderRadius: 8,
padding: 4, padding: 4,
}, },
title: {
fontSize: 14,
fontWeight: '500',
marginTop: 4,
textAlign: 'center',
fontFamily: 'SpaceMono-Regular',
}
}); });
export default ContentItem; 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 { addonId, type, id, name: originalName, genreFilter } = route.params;
const [items, setItems] = useState<Meta[]>([]); const [items, setItems] = useState<Meta[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [paginating, setPaginating] = useState(false);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
@ -198,6 +199,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
const colors = currentTheme.colors; const colors = currentTheme.colors;
const styles = createStyles(colors); const styles = createStyles(colors);
const isDarkMode = true; const isDarkMode = true;
const isInitialRender = React.useRef(true);
const { getCustomName, isLoadingCustomNames } = useCustomCatalogNames(); const { getCustomName, isLoadingCustomNames } = useCustomCatalogNames();
@ -262,7 +264,10 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
try { try {
if (shouldRefresh) { if (shouldRefresh) {
setRefreshing(true); setRefreshing(true);
} else if (pageNum === 1) { setHasMore(true); // Reset hasMore on refresh
} else if (pageNum > 1) {
setPaginating(true);
} else {
setLoading(true); setLoading(true);
} }
@ -360,6 +365,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
setHasMore(false); setHasMore(false);
} else { } else {
foundItems = true; foundItems = true;
setHasMore(true); // Ensure hasMore is true if we found items
} }
if (shouldRefresh || pageNum === 1) { if (shouldRefresh || pageNum === 1) {
@ -442,8 +448,11 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
index === self.findIndex((t) => t.id === item.id) index === self.findIndex((t) => t.id === item.id)
); );
if (uniqueItems.length === 0) { if (uniqueItems.length === 0 && allItems.length === 0) {
setHasMore(false); setHasMore(false);
} else {
foundItems = true;
setHasMore(true); // Ensure hasMore is true if we found items
} }
if (shouldRefresh || pageNum === 1) { if (shouldRefresh || pageNum === 1) {
@ -467,25 +476,31 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
} finally { } finally {
setLoading(false); setLoading(false);
setRefreshing(false); setRefreshing(false);
setPaginating(false);
} }
}, [addonId, type, id, genreFilter, dataSource]); }, [addonId, type, id, genreFilter, dataSource]);
useEffect(() => { useEffect(() => {
loadItems(1); loadItems(1, true);
}, [loadItems]); }, [loadItems]);
const handleRefresh = useCallback(() => { const handleRefresh = useCallback(() => {
setPage(1); setPage(1);
setItems([]); // Clear items on refresh
loadItems(1, true); loadItems(1, true);
}, [loadItems]); }, [loadItems]);
const handleLoadMore = useCallback(() => { const handleLoadMore = useCallback(() => {
if (!loading && hasMore) { if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
if (!loading && !paginating && hasMore && !refreshing) {
const nextPage = page + 1; const nextPage = page + 1;
setPage(nextPage); setPage(nextPage);
loadItems(nextPage); loadItems(nextPage);
} }
}, [loading, hasMore, page, loadItems]); }, [loading, paginating, hasMore, page, loadItems, refreshing]);
const renderItem = useCallback(({ item, index }: { item: Meta; index: number }) => { const renderItem = useCallback(({ item, index }: { item: Meta; index: number }) => {
// Calculate if this is the last item in a row // Calculate if this is the last item in a row
@ -638,7 +653,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
onEndReached={handleLoadMore} onEndReached={handleLoadMore}
onEndReachedThreshold={0.5} onEndReachedThreshold={0.5}
ListFooterComponent={ ListFooterComponent={
loading && items.length > 0 ? ( paginating ? (
<View style={styles.footer}> <View style={styles.footer}>
<ActivityIndicator size="small" color={colors.primary} /> <ActivityIndicator size="small" color={colors.primary} />
</View> </View>