improved downloads

This commit is contained in:
tapframe 2025-10-14 01:15:37 +05:30
parent 615172d29c
commit 9e7b9c5fe4
3 changed files with 54 additions and 19 deletions

View file

@ -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]);

View file

@ -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,

View file

@ -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>
)}