mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
converted from sectionlist to flatflist (streamscreen)
This commit is contained in:
parent
43cd14a025
commit
7fdd4c4383
2 changed files with 135 additions and 138 deletions
|
|
@ -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<TabletStreamsLayoutProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
// 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 }) => (
|
||||
<StreamCard
|
||||
stream={item}
|
||||
onPress={() => 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 (
|
||||
<StreamCard
|
||||
stream={stream}
|
||||
onPress={() => 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<TabletStreamsLayoutProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const getItemLayout = (data: any, index: number) => ({
|
||||
length: 78,
|
||||
offset: 78 * index,
|
||||
index,
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
sections={sectionListData}
|
||||
<LegendList
|
||||
data={flatListData}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
renderSectionHeader={({ section }) => 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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={styles.sectionHeaderContainer}>
|
||||
<View style={styles.sectionHeaderContent}>
|
||||
<Text style={styles.streamGroupTitle}>{section.title}</Text>
|
||||
{isProviderLoading && (
|
||||
<View style={styles.sectionLoadingIndicator}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
<Text style={[styles.sectionLoadingText, { color: colors.primary }]}>
|
||||
Loading...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[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 }) => (
|
||||
<StreamCard
|
||||
stream={item}
|
||||
onPress={() => 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 (
|
||||
<View style={styles.sectionHeaderContainer}>
|
||||
<View style={styles.sectionHeaderContent}>
|
||||
<Text style={styles.streamGroupTitle}>{item.title}</Text>
|
||||
{isProviderLoading && (
|
||||
<View style={styles.sectionLoadingIndicator}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
<Text style={[styles.sectionLoadingText, { color: colors.primary }]}>
|
||||
Loading...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const stream = item.stream;
|
||||
return (
|
||||
<StreamCard
|
||||
stream={stream}
|
||||
onPress={() => 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 (
|
||||
<View collapsable={false} style={{ flex: 1 }}>
|
||||
<SectionList
|
||||
sections={sectionListData}
|
||||
{ListHeaderComponent}
|
||||
<LegendList
|
||||
data={flatListData}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
renderSectionHeader={renderSectionHeader}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
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}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue