mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Refactor HomeScreen to use FlatList for improved performance and structure
This update replaces the ScrollView with a FlatList in the HomeScreen component, enhancing the rendering of catalog items and improving performance. The list data structure is also updated to include placeholders for loading states, ensuring a smoother user experience. Additionally, the logic for rendering list items and the footer component is optimized for better clarity and maintainability.
This commit is contained in:
parent
5f8bb8948d
commit
152ec88e04
1 changed files with 125 additions and 380 deletions
|
|
@ -73,299 +73,16 @@ interface Category {
|
|||
name: string;
|
||||
}
|
||||
|
||||
interface ContentItemProps {
|
||||
item: StreamingContent;
|
||||
onPress: (id: string, type: string) => void;
|
||||
}
|
||||
|
||||
interface DropUpMenuProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
item: StreamingContent;
|
||||
onOptionSelect: (option: string) => void;
|
||||
}
|
||||
|
||||
interface ContinueWatchingRef {
|
||||
refresh: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
const DropUpMenu = React.memo(({ visible, onClose, item, onOptionSelect }: DropUpMenuProps) => {
|
||||
const translateY = useSharedValue(300);
|
||||
const opacity = useSharedValue(0);
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const { currentTheme } = useTheme();
|
||||
const SNAP_THRESHOLD = 100;
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
opacity.value = withTiming(1, { duration: 200 });
|
||||
translateY.value = withTiming(0, { duration: 300 });
|
||||
} else {
|
||||
opacity.value = withTiming(0, { duration: 200 });
|
||||
translateY.value = withTiming(300, { duration: 300 });
|
||||
}
|
||||
|
||||
// Cleanup animations when component unmounts
|
||||
return () => {
|
||||
opacity.value = 0;
|
||||
translateY.value = 300;
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
const gesture = useMemo(() => Gesture.Pan()
|
||||
.onStart(() => {
|
||||
// Store initial position if needed
|
||||
})
|
||||
.onUpdate((event) => {
|
||||
if (event.translationY > 0) { // Only allow dragging downwards
|
||||
translateY.value = event.translationY;
|
||||
opacity.value = interpolate(
|
||||
event.translationY,
|
||||
[0, 300],
|
||||
[1, 0],
|
||||
Extrapolate.CLAMP
|
||||
);
|
||||
}
|
||||
})
|
||||
.onEnd((event) => {
|
||||
if (event.translationY > SNAP_THRESHOLD || event.velocityY > 500) {
|
||||
translateY.value = withTiming(300, { duration: 300 });
|
||||
opacity.value = withTiming(0, { duration: 200 });
|
||||
runOnJS(onClose)();
|
||||
} else {
|
||||
translateY.value = withTiming(0, { duration: 300 });
|
||||
opacity.value = withTiming(1, { duration: 200 });
|
||||
}
|
||||
}), [onClose]);
|
||||
|
||||
const overlayStyle = useAnimatedStyle(() => ({
|
||||
opacity: opacity.value,
|
||||
backgroundColor: currentTheme.colors.transparentDark,
|
||||
}));
|
||||
|
||||
const menuStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ translateY: translateY.value }],
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
backgroundColor: isDarkMode ? currentTheme.colors.elevation2 : currentTheme.colors.white,
|
||||
}));
|
||||
|
||||
const menuOptions = useMemo(() => [
|
||||
{
|
||||
icon: item.inLibrary ? 'bookmark' : 'bookmark-border',
|
||||
label: item.inLibrary ? 'Remove from Library' : 'Add to Library',
|
||||
action: 'library'
|
||||
},
|
||||
{
|
||||
icon: 'check-circle',
|
||||
label: 'Mark as Watched',
|
||||
action: 'watched'
|
||||
},
|
||||
{
|
||||
icon: 'playlist-add',
|
||||
label: 'Add to Playlist',
|
||||
action: 'playlist'
|
||||
},
|
||||
{
|
||||
icon: 'share',
|
||||
label: 'Share',
|
||||
action: 'share'
|
||||
}
|
||||
], [item.inLibrary]);
|
||||
|
||||
const handleOptionSelect = useCallback((action: string) => {
|
||||
onOptionSelect(action);
|
||||
onClose();
|
||||
}, [onOptionSelect, onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent
|
||||
animationType="none"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<Animated.View style={[styles.modalOverlay, overlayStyle]}>
|
||||
<Pressable style={styles.modalOverlayPressable} onPress={onClose} />
|
||||
<GestureDetector gesture={gesture}>
|
||||
<Animated.View style={[styles.menuContainer, menuStyle]}>
|
||||
<View style={[styles.dragHandle, { backgroundColor: currentTheme.colors.transparentLight }]} />
|
||||
<View style={[styles.menuHeader, { borderBottomColor: currentTheme.colors.border }]}>
|
||||
<ExpoImage
|
||||
source={{ uri: item.poster }}
|
||||
style={styles.menuPoster}
|
||||
contentFit="cover"
|
||||
/>
|
||||
<View style={styles.menuTitleContainer}>
|
||||
<Text style={[styles.menuTitle, { color: isDarkMode ? currentTheme.colors.white : currentTheme.colors.black }]}>
|
||||
{item.name}
|
||||
</Text>
|
||||
{item.year && (
|
||||
<Text style={[styles.menuYear, { color: isDarkMode ? currentTheme.colors.mediumEmphasis : currentTheme.colors.textMutedDark }]}>
|
||||
{item.year}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.menuOptions}>
|
||||
{menuOptions.map((option, index) => (
|
||||
<TouchableOpacity
|
||||
key={option.action}
|
||||
style={[
|
||||
styles.menuOption,
|
||||
{ borderBottomColor: isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)' },
|
||||
index === menuOptions.length - 1 && styles.lastMenuOption
|
||||
]}
|
||||
onPress={() => handleOptionSelect(option.action)}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={option.icon as "bookmark" | "check-circle" | "playlist-add" | "share" | "bookmark-border"}
|
||||
size={24}
|
||||
color={currentTheme.colors.primary}
|
||||
/>
|
||||
<Text style={[
|
||||
styles.menuOptionText,
|
||||
{ color: isDarkMode ? currentTheme.colors.white : currentTheme.colors.black }
|
||||
]}>
|
||||
{option.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</GestureDetector>
|
||||
</Animated.View>
|
||||
</GestureHandlerRootView>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
const ContentItem = React.memo(({ item: initialItem, onPress }: ContentItemProps) => {
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const [localItem, setLocalItem] = useState(initialItem);
|
||||
const [isWatched, setIsWatched] = useState(false);
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
setMenuVisible(true);
|
||||
}, []);
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
onPress(localItem.id, localItem.type);
|
||||
}, [localItem.id, localItem.type, onPress]);
|
||||
|
||||
const handleOptionSelect = useCallback((option: string) => {
|
||||
switch (option) {
|
||||
case 'library':
|
||||
if (localItem.inLibrary) {
|
||||
catalogService.removeFromLibrary(localItem.type, localItem.id);
|
||||
} else {
|
||||
catalogService.addToLibrary(localItem);
|
||||
}
|
||||
break;
|
||||
case 'watched':
|
||||
setIsWatched(prev => !prev);
|
||||
break;
|
||||
case 'playlist':
|
||||
case 'share':
|
||||
// These options don't have implementations yet
|
||||
break;
|
||||
}
|
||||
}, [localItem]);
|
||||
|
||||
const handleMenuClose = useCallback(() => {
|
||||
setMenuVisible(false);
|
||||
}, []);
|
||||
|
||||
// Only update localItem when initialItem changes
|
||||
useEffect(() => {
|
||||
setLocalItem(initialItem);
|
||||
}, [initialItem]);
|
||||
|
||||
// Subscribe to library updates
|
||||
useEffect(() => {
|
||||
const unsubscribe = catalogService.subscribeToLibraryUpdates((libraryItems) => {
|
||||
const isInLibrary = libraryItems.some(
|
||||
libraryItem => libraryItem.id === localItem.id && libraryItem.type === localItem.type
|
||||
);
|
||||
if (isInLibrary !== localItem.inLibrary) {
|
||||
setLocalItem(prev => ({ ...prev, inLibrary: isInLibrary }));
|
||||
}
|
||||
});
|
||||
|
||||
return () => unsubscribe();
|
||||
}, [localItem.id, localItem.type]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={styles.contentItem}
|
||||
activeOpacity={0.7}
|
||||
onPress={handlePress}
|
||||
onLongPress={handleLongPress}
|
||||
delayLongPress={300}
|
||||
>
|
||||
<View style={styles.contentItemContainer}>
|
||||
<ExpoImage
|
||||
source={{ uri: localItem.poster }}
|
||||
style={styles.poster}
|
||||
contentFit="cover"
|
||||
transition={300}
|
||||
cachePolicy="memory-disk"
|
||||
recyclingKey={`poster-${localItem.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>
|
||||
)}
|
||||
{localItem.inLibrary && (
|
||||
<View style={styles.libraryBadge}>
|
||||
<MaterialIcons name="bookmark" size={16} color={currentTheme.colors.white} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{menuVisible && (
|
||||
<DropUpMenu
|
||||
visible={menuVisible}
|
||||
onClose={handleMenuClose}
|
||||
item={localItem}
|
||||
onOptionSelect={handleOptionSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, (prevProps, nextProps) => {
|
||||
// Custom comparison function to prevent unnecessary re-renders
|
||||
return (
|
||||
prevProps.item.id === nextProps.item.id &&
|
||||
prevProps.item.inLibrary === nextProps.item.inLibrary &&
|
||||
prevProps.onPress === nextProps.onPress
|
||||
);
|
||||
});
|
||||
type HomeScreenListItem =
|
||||
| { type: 'featured'; key: string }
|
||||
| { type: 'thisWeek'; key: string }
|
||||
| { type: 'continueWatching'; key: string }
|
||||
| { type: 'catalog'; catalog: CatalogContent; key: string }
|
||||
| { type: 'placeholder'; key: string };
|
||||
|
||||
// Sample categories (real app would get these from API)
|
||||
const SAMPLE_CATEGORIES: Category[] = [
|
||||
|
|
@ -397,7 +114,7 @@ const HomeScreen = () => {
|
|||
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [hasContinueWatching, setHasContinueWatching] = useState(false);
|
||||
|
||||
const [catalogs, setCatalogs] = useState<CatalogContent[]>([]);
|
||||
const [catalogs, setCatalogs] = useState<(CatalogContent | null)[]>([]);
|
||||
const [catalogsLoading, setCatalogsLoading] = useState(true);
|
||||
const [loadedCatalogCount, setLoadedCatalogCount] = useState(0);
|
||||
const totalCatalogsRef = useRef(0);
|
||||
|
|
@ -423,6 +140,9 @@ const HomeScreen = () => {
|
|||
const catalogSettingsJson = await AsyncStorage.getItem(CATALOG_SETTINGS_KEY);
|
||||
const catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {};
|
||||
|
||||
// Hoist addon manifest loading out of the loop
|
||||
const addonManifests = await stremioService.getInstalledAddonsAsync();
|
||||
|
||||
// Create placeholder array with proper order and track indices
|
||||
const catalogPlaceholders: (CatalogContent | null)[] = [];
|
||||
const catalogPromises: Promise<void>[] = [];
|
||||
|
|
@ -442,8 +162,7 @@ const HomeScreen = () => {
|
|||
|
||||
const catalogPromise = (async () => {
|
||||
try {
|
||||
const addonManifest = await stremioService.getInstalledAddonsAsync();
|
||||
const manifest = addonManifest.find((a: any) => a.id === addon.id);
|
||||
const manifest = addonManifests.find((a: any) => a.id === addon.id);
|
||||
if (!manifest) return;
|
||||
|
||||
const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1);
|
||||
|
|
@ -737,6 +456,106 @@ const HomeScreen = () => {
|
|||
return null;
|
||||
}, [isLoading, currentTheme.colors]);
|
||||
|
||||
const listData: HomeScreenListItem[] = useMemo(() => {
|
||||
const data: HomeScreenListItem[] = [];
|
||||
|
||||
if (showHeroSection) {
|
||||
data.push({ type: 'featured', key: 'featured' });
|
||||
}
|
||||
|
||||
data.push({ type: 'thisWeek', key: 'thisWeek' });
|
||||
data.push({ type: 'continueWatching', key: 'continueWatching' });
|
||||
|
||||
catalogs.forEach((catalog, index) => {
|
||||
if (catalog) {
|
||||
data.push({ type: 'catalog', catalog, key: `${catalog.addon}-${catalog.id}-${index}` });
|
||||
} else {
|
||||
// Add a key for placeholders
|
||||
data.push({ type: 'placeholder', key: `placeholder-${index}` });
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}, [showHeroSection, catalogs]);
|
||||
|
||||
const renderListItem = useCallback(({ item }: { item: HomeScreenListItem }) => {
|
||||
switch (item.type) {
|
||||
case 'featured':
|
||||
return (
|
||||
<FeaturedContent
|
||||
key={`featured-${showHeroSection}-${featuredContentSource}`}
|
||||
featuredContent={featuredContent}
|
||||
isSaved={isSaved}
|
||||
handleSaveToLibrary={handleSaveToLibrary}
|
||||
/>
|
||||
);
|
||||
case 'thisWeek':
|
||||
return <Animated.View entering={FadeIn.duration(400).delay(150)}><ThisWeekSection /></Animated.View>;
|
||||
case 'continueWatching':
|
||||
return <ContinueWatchingSection ref={continueWatchingRef} />;
|
||||
case 'catalog':
|
||||
return (
|
||||
<Animated.View entering={FadeIn.duration(300)}>
|
||||
<CatalogSection catalog={item.catalog} />
|
||||
</Animated.View>
|
||||
);
|
||||
case 'placeholder':
|
||||
return (
|
||||
<View style={styles.catalogPlaceholder}>
|
||||
<View style={styles.placeholderHeader}>
|
||||
<View style={[styles.placeholderTitle, { backgroundColor: currentTheme.colors.elevation1 }]} />
|
||||
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||
</View>
|
||||
<View style={styles.placeholderPosters}>
|
||||
{[...Array(4)].map((_, posterIndex) => (
|
||||
<View
|
||||
key={posterIndex}
|
||||
style={[styles.placeholderPoster, { backgroundColor: currentTheme.colors.elevation1 }]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [
|
||||
showHeroSection,
|
||||
featuredContentSource,
|
||||
featuredContent,
|
||||
isSaved,
|
||||
handleSaveToLibrary,
|
||||
currentTheme.colors
|
||||
]);
|
||||
|
||||
const ListFooterComponent = useMemo(() => (
|
||||
<>
|
||||
{catalogsLoading && catalogs.length < totalCatalogsRef.current && (
|
||||
<View style={styles.loadingMoreCatalogs}>
|
||||
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.loadingMoreText, { color: currentTheme.colors.textMuted }]}>
|
||||
Loading more content... ({loadedCatalogCount}/{totalCatalogsRef.current})
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{!catalogsLoading && catalogs.filter(c => c).length === 0 && (
|
||||
<View style={[styles.emptyCatalog, { backgroundColor: currentTheme.colors.elevation1 }]}>
|
||||
<MaterialIcons name="movie-filter" size={40} color={currentTheme.colors.textDark} />
|
||||
<Text style={{ color: currentTheme.colors.textDark, marginTop: 8, fontSize: 16, textAlign: 'center' }}>
|
||||
No content available
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.addCatalogButton, { backgroundColor: currentTheme.colors.primary }]}
|
||||
onPress={() => navigation.navigate('Settings')}
|
||||
>
|
||||
<MaterialIcons name="add-circle" size={20} color={currentTheme.colors.white} />
|
||||
<Text style={[styles.addCatalogButtonText, { color: currentTheme.colors.white }]}>Add Catalogs</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
), [catalogsLoading, catalogs, loadedCatalogCount, totalCatalogsRef.current, navigation, currentTheme.colors]);
|
||||
|
||||
// Memoize the main content section
|
||||
const renderMainContent = useMemo(() => {
|
||||
if (isLoading) return null;
|
||||
|
|
@ -748,102 +567,28 @@ const HomeScreen = () => {
|
|||
backgroundColor="transparent"
|
||||
translucent
|
||||
/>
|
||||
<ScrollView
|
||||
<FlatList
|
||||
data={listData}
|
||||
renderItem={renderListItem}
|
||||
keyExtractor={item => item.key}
|
||||
contentContainerStyle={[
|
||||
styles.scrollContent,
|
||||
{ paddingTop: Platform.OS === 'ios' ? 100 : 90 }
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
removeClippedSubviews={true}
|
||||
>
|
||||
{showHeroSection && (
|
||||
<FeaturedContent
|
||||
key={`featured-${showHeroSection}-${featuredContentSource}`}
|
||||
featuredContent={featuredContent}
|
||||
isSaved={isSaved}
|
||||
handleSaveToLibrary={handleSaveToLibrary}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Animated.View entering={FadeIn.duration(400).delay(150)}>
|
||||
<ThisWeekSection />
|
||||
</Animated.View>
|
||||
|
||||
<ContinueWatchingSection ref={continueWatchingRef} />
|
||||
|
||||
{/* Show catalogs as they load */}
|
||||
{catalogs.map((catalog, index) => {
|
||||
if (!catalog) {
|
||||
// Show placeholder for loading catalog
|
||||
return (
|
||||
<View key={`placeholder-${index}`} style={styles.catalogPlaceholder}>
|
||||
<View style={styles.placeholderHeader}>
|
||||
<View style={[styles.placeholderTitle, { backgroundColor: currentTheme.colors.elevation1 }]} />
|
||||
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||
</View>
|
||||
<View style={styles.placeholderPosters}>
|
||||
{[...Array(4)].map((_, posterIndex) => (
|
||||
<View
|
||||
key={posterIndex}
|
||||
style={[styles.placeholderPoster, { backgroundColor: currentTheme.colors.elevation1 }]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={`${catalog.addon}-${catalog.id}-${index}`}
|
||||
entering={FadeIn.duration(300)}
|
||||
>
|
||||
<CatalogSection catalog={catalog} />
|
||||
</Animated.View>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Show loading indicator for remaining catalogs */}
|
||||
{catalogsLoading && catalogs.length < totalCatalogsRef.current && (
|
||||
<View style={styles.loadingMoreCatalogs}>
|
||||
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.loadingMoreText, { color: currentTheme.colors.textMuted }]}>
|
||||
Loading more content... ({loadedCatalogCount}/{totalCatalogsRef.current})
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Show empty state only if all catalogs are loaded and none are available */}
|
||||
{!catalogsLoading && catalogs.length === 0 && (
|
||||
<View style={[styles.emptyCatalog, { backgroundColor: currentTheme.colors.elevation1 }]}>
|
||||
<MaterialIcons name="movie-filter" size={40} color={currentTheme.colors.textDark} />
|
||||
<Text style={{ color: currentTheme.colors.textDark, marginTop: 8, fontSize: 16, textAlign: 'center' }}>
|
||||
No content available
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.addCatalogButton, { backgroundColor: currentTheme.colors.primary }]}
|
||||
onPress={() => navigation.navigate('Settings')}
|
||||
>
|
||||
<MaterialIcons name="add-circle" size={20} color={currentTheme.colors.white} />
|
||||
<Text style={[styles.addCatalogButtonText, { color: currentTheme.colors.white }]}>Add Catalogs</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
initialNumToRender={5}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={10}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}, [
|
||||
isLoading,
|
||||
currentTheme.colors,
|
||||
showHeroSection,
|
||||
featuredContent,
|
||||
isSaved,
|
||||
handleSaveToLibrary,
|
||||
hasContinueWatching,
|
||||
catalogs,
|
||||
catalogsLoading,
|
||||
navigation,
|
||||
featuredContentSource
|
||||
isLoading,
|
||||
currentTheme.colors,
|
||||
listData,
|
||||
renderListItem,
|
||||
ListFooterComponent
|
||||
]);
|
||||
|
||||
return isLoading ? renderLoadingScreen : renderMainContent;
|
||||
|
|
|
|||
Loading…
Reference in a new issue