mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
improved downloads
This commit is contained in:
parent
615172d29c
commit
9e7b9c5fe4
3 changed files with 54 additions and 19 deletions
|
|
@ -8,7 +8,7 @@ export type DownloadStatus = 'downloading' | 'completed' | 'paused' | 'error' |
|
|||
|
||||
export interface DownloadItem {
|
||||
id: string; // unique id for this download (content id + episode if any)
|
||||
contentId: string; // base id
|
||||
contentId: string; // base id (e.g., tt0903747 for series, tt0499549 for movies)
|
||||
type: 'movie' | 'series';
|
||||
title: string; // movie title or show name
|
||||
providerName?: string;
|
||||
|
|
@ -29,10 +29,13 @@ export interface DownloadItem {
|
|||
fileUri?: string; // local file uri once downloading/finished
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
// Additional metadata for progress tracking
|
||||
imdbId?: string; // IMDb ID for better tracking
|
||||
tmdbId?: number; // TMDB ID if available
|
||||
}
|
||||
|
||||
type StartDownloadInput = {
|
||||
id: string;
|
||||
id: string; // Base content ID (e.g., tt0903747)
|
||||
type: 'movie' | 'series';
|
||||
title: string;
|
||||
providerName?: string;
|
||||
|
|
@ -43,6 +46,9 @@ type StartDownloadInput = {
|
|||
posterUrl?: string | null;
|
||||
url: string;
|
||||
headers?: Record<string, string>;
|
||||
// Additional metadata for progress tracking
|
||||
imdbId?: string;
|
||||
tmdbId?: number;
|
||||
};
|
||||
|
||||
type DownloadsContextValue = {
|
||||
|
|
@ -153,6 +159,9 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|||
fileUri: d.fileUri ? String(d.fileUri) : undefined,
|
||||
createdAt: typeof d.createdAt === 'number' ? d.createdAt : Date.now(),
|
||||
updatedAt: typeof d.updatedAt === 'number' ? d.updatedAt : Date.now(),
|
||||
// Restore metadata for progress tracking
|
||||
imdbId: (d as any).imdbId ? String((d as any).imdbId) : undefined,
|
||||
tmdbId: typeof (d as any).tmdbId === 'number' ? (d as any).tmdbId : undefined,
|
||||
};
|
||||
return safe;
|
||||
});
|
||||
|
|
@ -394,6 +403,9 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|||
fileUri,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
// Store metadata for progress tracking
|
||||
imdbId: input.imdbId,
|
||||
tmdbId: input.tmdbId,
|
||||
};
|
||||
|
||||
setDownloads(prev => [newItem, ...prev]);
|
||||
|
|
|
|||
|
|
@ -105,11 +105,11 @@ const DownloadItemComponent: React.FC<{
|
|||
case 'downloading':
|
||||
return currentTheme.colors.primary;
|
||||
case 'completed':
|
||||
return currentTheme.colors.success;
|
||||
return currentTheme.colors.success || '#4CAF50';
|
||||
case 'paused':
|
||||
return currentTheme.colors.warning;
|
||||
return currentTheme.colors.warning || '#FF9500';
|
||||
case 'error':
|
||||
return currentTheme.colors.error;
|
||||
return currentTheme.colors.error || '#FF3B30';
|
||||
case 'queued':
|
||||
return currentTheme.colors.mediumEmphasis;
|
||||
default:
|
||||
|
|
@ -166,7 +166,7 @@ const DownloadItemComponent: React.FC<{
|
|||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.downloadItem, { backgroundColor: currentTheme.colors.card }]}
|
||||
style={[styles.downloadItem, { backgroundColor: currentTheme.colors.elevation2 }]}
|
||||
onPress={() => onPress(item)}
|
||||
onLongPress={handleLongPress}
|
||||
activeOpacity={0.8}
|
||||
|
|
@ -192,7 +192,7 @@ const DownloadItemComponent: React.FC<{
|
|||
{/* Provider + quality row */}
|
||||
<View style={styles.providerRow}>
|
||||
<Text style={[styles.providerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
{(item.providerName || 'Provider') + (item.quality ? ` ${item.quality}` : '')}
|
||||
{item.providerName || 'Provider'}
|
||||
</Text>
|
||||
</View>
|
||||
{/* Status row */}
|
||||
|
|
@ -344,6 +344,11 @@ const DownloadsScreen: React.FC = () => {
|
|||
const isMp4 = /\.mp4(\?|$)/i.test(lower);
|
||||
const videoType = isM3u8 ? 'm3u8' : isMpd ? 'mpd' : isMp4 ? 'mp4' : undefined;
|
||||
|
||||
// Build episodeId for series progress tracking (format: contentId:season:episode)
|
||||
const episodeId = item.type === 'series' && item.season && item.episode
|
||||
? `${item.contentId}:${item.season}:${item.episode}`
|
||||
: undefined;
|
||||
|
||||
const playerRoute = Platform.OS === 'ios' ? 'PlayerIOS' : 'PlayerAndroid';
|
||||
navigation.navigate(playerRoute as any, {
|
||||
uri,
|
||||
|
|
@ -357,10 +362,10 @@ const DownloadsScreen: React.FC = () => {
|
|||
streamName: item.providerName || 'Offline',
|
||||
headers: undefined,
|
||||
forceVlc: Platform.OS === 'android' ? isMkv : false,
|
||||
id: item.id,
|
||||
id: item.contentId, // Use contentId (base ID) instead of compound id for progress tracking
|
||||
type: item.type,
|
||||
episodeId: undefined,
|
||||
imdbId: undefined,
|
||||
episodeId: episodeId, // Pass episodeId for series progress tracking
|
||||
imdbId: (item as any).imdbId || item.contentId, // Use imdbId if available, fallback to contentId
|
||||
availableStreams: {},
|
||||
backdrop: undefined,
|
||||
videoType,
|
||||
|
|
@ -409,7 +414,7 @@ const DownloadsScreen: React.FC = () => {
|
|||
styles.filterButtonText,
|
||||
{
|
||||
color: selectedFilter === filter
|
||||
? currentTheme.colors.background
|
||||
? currentTheme.colors.white
|
||||
: currentTheme.colors.text,
|
||||
}
|
||||
]}>
|
||||
|
|
@ -420,7 +425,7 @@ const DownloadsScreen: React.FC = () => {
|
|||
styles.filterBadge,
|
||||
{
|
||||
backgroundColor: selectedFilter === filter
|
||||
? currentTheme.colors.background
|
||||
? currentTheme.colors.white
|
||||
: currentTheme.colors.primary,
|
||||
}
|
||||
]}>
|
||||
|
|
@ -429,7 +434,7 @@ const DownloadsScreen: React.FC = () => {
|
|||
{
|
||||
color: selectedFilter === filter
|
||||
? currentTheme.colors.primary
|
||||
: currentTheme.colors.background,
|
||||
: currentTheme.colors.white,
|
||||
}
|
||||
]}>
|
||||
{count}
|
||||
|
|
@ -440,7 +445,7 @@ const DownloadsScreen: React.FC = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.background }]}>
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar
|
||||
translucent
|
||||
barStyle="light-content"
|
||||
|
|
@ -451,8 +456,9 @@ const DownloadsScreen: React.FC = () => {
|
|||
<Animated.View style={[
|
||||
styles.header,
|
||||
{
|
||||
backgroundColor: currentTheme.colors.background,
|
||||
backgroundColor: currentTheme.colors.darkBackground,
|
||||
paddingTop: safeAreaTop + 16,
|
||||
borderBottomColor: currentTheme.colors.border,
|
||||
},
|
||||
headerStyle,
|
||||
]}>
|
||||
|
|
@ -484,6 +490,7 @@ const DownloadsScreen: React.FC = () => {
|
|||
onAction={handleDownloadAction}
|
||||
/>
|
||||
)}
|
||||
style={{ backgroundColor: currentTheme.colors.darkBackground }}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
|
|
@ -523,7 +530,6 @@ const styles = StyleSheet.create({
|
|||
paddingHorizontal: 20,
|
||||
paddingBottom: 16,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 32,
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ const AnimatedView = memo(({
|
|||
});
|
||||
|
||||
// Extracted Components
|
||||
const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme, showLogos, scraperLogo, showAlert, parentTitle, parentType, parentSeason, parentEpisode, parentEpisodeTitle, parentPosterUrl, providerName }: {
|
||||
const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme, showLogos, scraperLogo, showAlert, parentTitle, parentType, parentSeason, parentEpisode, parentEpisodeTitle, parentPosterUrl, providerName, parentId, parentImdbId }: {
|
||||
stream: Stream;
|
||||
onPress: () => void;
|
||||
index: number;
|
||||
|
|
@ -220,6 +220,8 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
|||
parentEpisodeTitle?: string;
|
||||
parentPosterUrl?: string | null;
|
||||
providerName?: string;
|
||||
parentId?: string; // Content ID (e.g., tt0903747 or tmdb:1396)
|
||||
parentImdbId?: string; // IMDb ID if available
|
||||
}) => {
|
||||
const { useSettings } = require('../hooks/useSettings');
|
||||
const { settings } = useSettings();
|
||||
|
|
@ -301,7 +303,17 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
|||
const episodeTitle = parentEpisodeTitle || parent.episodeTitle || parent.episode_name;
|
||||
// Prefer the stream's display name (often includes provider + resolution)
|
||||
const provider = (stream.name as any) || (stream.title as any) || providerName || parent.addonName || parent.addonId || (stream.addonName as any) || (stream.addonId as any) || 'Provider';
|
||||
const idForContent = parent.imdbId || parent.tmdbId || parent.addonId || inferredTitle;
|
||||
|
||||
// Use parentId first (from route params), fallback to stream metadata
|
||||
const idForContent = parentId || parent.imdbId || parent.tmdbId || parent.addonId || inferredTitle;
|
||||
|
||||
// Extract tmdbId if available (from parentId or parent metadata)
|
||||
let tmdbId: number | undefined = undefined;
|
||||
if (parentId && parentId.startsWith('tmdb:')) {
|
||||
tmdbId = parseInt(parentId.split(':')[1], 10);
|
||||
} else if (typeof parent.tmdbId === 'number') {
|
||||
tmdbId = parent.tmdbId;
|
||||
}
|
||||
|
||||
await startDownload({
|
||||
id: String(idForContent),
|
||||
|
|
@ -315,10 +327,13 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
|||
posterUrl: parentPosterUrl || parent.poster || parent.backdrop || null,
|
||||
url,
|
||||
headers: (stream.headers as any) || undefined,
|
||||
// Pass metadata for progress tracking
|
||||
imdbId: parentImdbId || parent.imdbId || undefined,
|
||||
tmdbId: tmdbId,
|
||||
});
|
||||
Toast.success('Download started', 'bottom');
|
||||
} catch {}
|
||||
}, [startDownload, stream.url, stream.headers, streamInfo.quality, showAlert, stream.name, stream.title]);
|
||||
}, [startDownload, stream.url, stream.headers, streamInfo.quality, showAlert, stream.name, stream.title, parentId, parentImdbId, parentTitle, parentType, parentSeason, parentEpisode, parentEpisodeTitle, parentPosterUrl, providerName]);
|
||||
|
||||
const isDebrid = streamInfo.isDebrid;
|
||||
return (
|
||||
|
|
@ -2188,6 +2203,8 @@ export const StreamsScreen = () => {
|
|||
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}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue