From 7fdd4c438363f873feec5f781b82eaeb2a10ffdc Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 28 Dec 2025 03:41:33 +0530 Subject: [PATCH] converted from sectionlist to flatflist (streamscreen) --- src/components/TabletStreamsLayout.tsx | 100 +++++----- .../streams/components/StreamsList.tsx | 173 +++++++++--------- 2 files changed, 135 insertions(+), 138 deletions(-) diff --git a/src/components/TabletStreamsLayout.tsx b/src/components/TabletStreamsLayout.tsx index d05c58a..6a05b27 100644 --- a/src/components/TabletStreamsLayout.tsx +++ b/src/components/TabletStreamsLayout.tsx @@ -4,10 +4,10 @@ import { Text, StyleSheet, ActivityIndicator, - SectionList, Platform, TouchableOpacity, } from 'react-native'; +import { LegendList } from '@legendapp/list'; import { LinearGradient } from 'expo-linear-gradient'; import FastImage from '@d11/react-native-fast-image'; import { MaterialIcons } from '@expo/vector-icons'; @@ -309,42 +309,56 @@ const TabletStreamsLayout: React.FC = ({ } } - // Convert sections to SectionList format - const sectionListData = sections + // Flatten sections into a single list with header items + type ListItem = { type: 'header'; title: string; addonId: string } | { type: 'stream'; stream: Stream; index: number }; + + const flatListData: ListItem[] = []; + sections .filter(Boolean) .filter(section => section!.data && section!.data.length > 0) - .map(section => ({ - title: section!.title, - addonId: section!.addonId, - data: section!.data, - })); + .forEach(section => { + flatListData.push({ type: 'header', title: section!.title, addonId: section!.addonId }); + section!.data.forEach((stream, index) => { + flatListData.push({ type: 'stream', stream, index }); + }); + }); - const renderItem = ({ item, index }: { item: Stream; index: number }) => ( - handleStreamPress(item)} - index={index} - isLoading={false} - statusMessage={undefined} - theme={currentTheme} - showLogos={settings.showScraperLogos} - scraperLogo={(item.addonId && scraperLogos[item.addonId]) || (item as any).addon ? scraperLogos[(item.addonId || (item as any).addon) as string] || null : null} - showAlert={(t: string, m: string) => openAlert(t, m)} - parentTitle={metadata?.name} - parentType={type as 'movie' | 'series'} - parentSeason={(type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined} - parentEpisode={(type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined} - parentEpisodeTitle={(type === 'series' || type === 'other') ? currentEpisode?.name : undefined} - parentPosterUrl={episodeImage || metadata?.poster || undefined} - providerName={streams && Object.keys(streams).find(pid => (streams as any)[pid]?.streams?.includes?.(item))} - parentId={id} - parentImdbId={imdbId || undefined} - /> - ); + const renderItem = ({ item }: { item: ListItem }) => { + if (item.type === 'header') { + return renderSectionHeader({ section: { title: item.title, addonId: item.addonId } }); + } + + const stream = item.stream; + return ( + handleStreamPress(stream)} + index={item.index} + isLoading={false} + statusMessage={undefined} + theme={currentTheme} + showLogos={settings.showScraperLogos} + scraperLogo={(stream.addonId && scraperLogos[stream.addonId]) || (stream as any).addon ? scraperLogos[(stream.addonId || (stream as any).addon) as string] || null : null} + showAlert={(t: string, m: string) => openAlert(t, m)} + parentTitle={metadata?.name} + parentType={type as 'movie' | 'series'} + parentSeason={(type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined} + parentEpisode={(type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined} + parentEpisodeTitle={(type === 'series' || type === 'other') ? currentEpisode?.name : undefined} + parentPosterUrl={episodeImage || metadata?.poster || undefined} + providerName={streams && Object.keys(streams).find(pid => (streams as any)[pid]?.streams?.includes?.(stream))} + parentId={id} + parentImdbId={imdbId || undefined} + /> + ); + }; - const keyExtractor = (item: Stream, index: number) => { - if (item && item.url) { - return `${item.url}-${index}`; + const keyExtractor = (item: ListItem, index: number) => { + if (item.type === 'header') { + return `header-${item.addonId}-${index}`; + } + if (item.stream && item.stream.url) { + return `stream-${item.stream.url}-${index}`; } return `empty-${index}`; }; @@ -359,32 +373,20 @@ const TabletStreamsLayout: React.FC = ({ ); }; - const getItemLayout = (data: any, index: number) => ({ - length: 78, - offset: 78 * index, - index, - }); - return ( - renderSectionHeader({ section })} ListFooterComponent={ListFooterComponent} - stickySectionHeadersEnabled={false} contentContainerStyle={[ styles.streamsContainer, { paddingBottom: insets.bottom + 100 } ]} style={styles.streamsContent} showsVerticalScrollIndicator={false} - initialNumToRender={5} - maxToRenderPerBatch={3} - updateCellsBatchingPeriod={100} - windowSize={3} - removeClippedSubviews={true} - getItemLayout={getItemLayout} + recycleItems={true} + estimatedItemSize={78} /> ); }; diff --git a/src/screens/streams/components/StreamsList.tsx b/src/screens/streams/components/StreamsList.tsx index 392255e..aea6b6c 100644 --- a/src/screens/streams/components/StreamsList.tsx +++ b/src/screens/streams/components/StreamsList.tsx @@ -3,10 +3,10 @@ import { View, Text, StyleSheet, - SectionList, ActivityIndicator, Platform, } from 'react-native'; +import { LegendList } from '@legendapp/list'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import StreamCard from '../../../components/StreamCard'; @@ -62,84 +62,91 @@ const StreamsList = memo( const insets = useSafeAreaInsets(); const styles = React.useMemo(() => createStyles(colors), [colors]); - const renderSectionHeader = useCallback( - ({ section }: { section: StreamSection }) => { - const isProviderLoading = loadingProviders[section.addonId]; - - return ( - - - {section.title} - {isProviderLoading && ( - - - - Loading... - - - )} - - - ); - }, - [loadingProviders, styles, colors.primary] - ); - - // Convert sections to SectionList format - const sectionListData = useMemo(() => { - return sections + // Flatten sections into a single list with header items + type ListItem = { type: 'header'; title: string; addonId: string } | { type: 'stream'; stream: Stream; index: number }; + + const flatListData = useMemo(() => { + const items: ListItem[] = []; + sections .filter(Boolean) .filter(section => section!.data && section!.data.length > 0) - .map(section => ({ - title: section!.title, - addonId: section!.addonId, - data: section!.data, - })); + .forEach(section => { + items.push({ type: 'header', title: section!.title, addonId: section!.addonId }); + section!.data.forEach((stream, index) => { + items.push({ type: 'stream', stream, index }); + }); + }); + return items; }, [sections]); const renderItem = useCallback( - ({ item, index }: { item: Stream; index: number }) => ( - handleStreamPress(item)} - index={index} - isLoading={false} - statusMessage={undefined} - theme={currentTheme} - showLogos={settings.showScraperLogos} - scraperLogo={ - (item.addonId && scraperLogos[item.addonId]) || - ((item as any).addon ? scraperLogos[(item.addonId || (item as any).addon) as string] || null : null) - } - showAlert={(t: string, m: string) => openAlert(t, m)} - parentTitle={metadata?.name} - parentType={type as 'movie' | 'series'} - parentSeason={ - (type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined - } - parentEpisode={ - (type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined - } - parentEpisodeTitle={ - (type === 'series' || type === 'other') ? currentEpisode?.name : undefined - } - parentPosterUrl={episodeImage || metadata?.poster || undefined} - providerName={ - streams && - Object.keys(streams).find(pid => - (streams as any)[pid]?.streams?.includes?.(item) - ) - } - parentId={id} - parentImdbId={imdbId} - /> - ), - [handleStreamPress, currentTheme, settings.showScraperLogos, scraperLogos, openAlert, metadata, type, currentEpisode, episodeImage, streams, id, imdbId] + ({ item }: { item: ListItem }) => { + if (item.type === 'header') { + const isProviderLoading = loadingProviders[item.addonId]; + return ( + + + {item.title} + {isProviderLoading && ( + + + + Loading... + + + )} + + + ); + } + + const stream = item.stream; + return ( + handleStreamPress(stream)} + index={item.index} + isLoading={false} + statusMessage={undefined} + theme={currentTheme} + showLogos={settings.showScraperLogos} + scraperLogo={ + (stream.addonId && scraperLogos[stream.addonId]) || + ((stream as any).addon ? scraperLogos[(stream.addonId || (stream as any).addon) as string] || null : null) + } + showAlert={(t: string, m: string) => openAlert(t, m)} + parentTitle={metadata?.name} + parentType={type as 'movie' | 'series'} + parentSeason={ + (type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined + } + parentEpisode={ + (type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined + } + parentEpisodeTitle={ + (type === 'series' || type === 'other') ? currentEpisode?.name : undefined + } + parentPosterUrl={episodeImage || metadata?.poster || undefined} + providerName={ + streams && + Object.keys(streams).find(pid => + (streams as any)[pid]?.streams?.includes?.(item.stream) + ) + } + parentId={id} + parentImdbId={imdbId} + /> + ); + }, + [handleStreamPress, currentTheme, settings.showScraperLogos, scraperLogos, openAlert, metadata, type, currentEpisode, episodeImage, streams, id, imdbId, loadingProviders, styles, colors.primary] ); - const keyExtractor = useCallback((item: Stream, index: number) => { - if (item && item.url) { - return `${item.url}-${index}`; + const keyExtractor = useCallback((item: ListItem, index: number) => { + if (item.type === 'header') { + return `header-${item.addonId}-${index}`; + } + if (item.stream && item.stream.url) { + return `stream-${item.stream.url}-${index}`; } return `empty-${index}`; }, []); @@ -166,34 +173,22 @@ const StreamsList = memo( ); }, [loadingStreams, loadingEpisodeStreams, hasStremioStreamProviders, styles, colors.primary]); - const getItemLayout = useCallback((data: any, index: number) => ({ - length: 78, - offset: 78 * index, - index, - }), []); - return ( - );