(null);
@@ -485,6 +511,22 @@ const Header: React.FC = () => {
+ {/* Téléchargements in-app (Android uniquement) */}
+ {inMovixApp && (
+
+
+ {activeDownloads > 0 && (
+
+ {activeDownloads > 9 ? '9+' : activeDownloads}
+
+ )}
+
+ )}
+
{/* Notifications */}
{isAuthenticated && !notificationsDisabled && (
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 0227390..7d3e42c 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -4253,7 +4253,56 @@
"name": "BestDebrid",
"desc": "Final link fetched directly from the browser"
}
- }
+ },
+ "appDownloadBtn": "Download in the app",
+ "appDownloadStarting": "Starting download…",
+ "appDownloadAgain": "Restart in the app",
+ "appDownloadStarted": "Download added to the app",
+ "appDownloadStartedHint": "The file is being downloaded inside Movix.",
+ "appDownloadOpenList": "View my downloads",
+ "appDownloadUnavailable": "Movix application not detected.",
+ "appDownloadError": "Could not start the download."
+ },
+ "downloads": {
+ "title": "My downloads",
+ "subtitle": "Files stored inside the Movix app",
+ "backToHome": "Back to home",
+ "appOnlyTitle": "Only available inside the app",
+ "appOnlyDesc": "This page only makes sense inside the Movix Android app, where downloads are stored.",
+ "loading": "Loading…",
+ "empty": "No downloads yet",
+ "emptyHint": "Trigger a download from the Debrider page to find it here.",
+ "activeLabel": "Active",
+ "etaPrefix": "ETA",
+ "failedHint": "A download failed. Use Resume to retry.",
+ "fileCountSuffix": "file(s)",
+ "unknownTitle": "Unknown title",
+ "unknownSeason": "Unknown season",
+ "seasonLabel": "Season {{n}}",
+ "group": {
+ "movie": "Movies",
+ "series": "Series",
+ "animes": "Animes",
+ "misc": "Other"
+ },
+ "status": {
+ "queued": "Queued",
+ "running": "Running",
+ "paused": "Paused",
+ "done": "Done",
+ "failed": "Failed",
+ "cancelled": "Cancelled"
+ },
+ "actions": {
+ "pause": "Pause",
+ "resume": "Resume",
+ "cancel": "Cancel",
+ "delete": "Delete",
+ "launch": "Play"
+ },
+ "deleteConfirmTitle": "Delete this file?",
+ "deleteConfirmCancel": "Cancel",
+ "deleteConfirmOk": "Delete"
},
"francetv": {
"title": "France TV",
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index 057cb41..db9d9e1 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -4253,7 +4253,56 @@
"name": "BestDebrid",
"desc": "Lien final obtenu directement côté navigateur"
}
- }
+ },
+ "appDownloadBtn": "Télécharger dans l'application",
+ "appDownloadStarting": "Lancement du téléchargement…",
+ "appDownloadAgain": "Relancer dans l'application",
+ "appDownloadStarted": "Téléchargement ajouté à l'application",
+ "appDownloadStartedHint": "Le fichier est en cours de téléchargement dans Movix.",
+ "appDownloadOpenList": "Voir mes téléchargements",
+ "appDownloadUnavailable": "L'application Movix n'est pas détectée.",
+ "appDownloadError": "Le téléchargement n'a pas pu démarrer."
+ },
+ "downloads": {
+ "title": "Mes téléchargements",
+ "subtitle": "Fichiers stockés dans l'application Movix",
+ "backToHome": "Retour à l'accueil",
+ "appOnlyTitle": "Disponible uniquement dans l'application",
+ "appOnlyDesc": "Cette page n'a de sens que dans l'application Android Movix, où les téléchargements sont stockés.",
+ "loading": "Chargement…",
+ "empty": "Aucun téléchargement",
+ "emptyHint": "Lance un téléchargement depuis la page Débrideur pour le retrouver ici.",
+ "activeLabel": "Actifs",
+ "etaPrefix": "ETA",
+ "failedHint": "Un téléchargement a échoué. Utilise le bouton Reprendre pour réessayer.",
+ "fileCountSuffix": "fichier(s)",
+ "unknownTitle": "Titre inconnu",
+ "unknownSeason": "Saison inconnue",
+ "seasonLabel": "Saison {{n}}",
+ "group": {
+ "movie": "Films",
+ "series": "Séries",
+ "animes": "Animés",
+ "misc": "Autres"
+ },
+ "status": {
+ "queued": "En attente",
+ "running": "En cours",
+ "paused": "En pause",
+ "done": "Terminé",
+ "failed": "Échec",
+ "cancelled": "Annulé"
+ },
+ "actions": {
+ "pause": "Mettre en pause",
+ "resume": "Reprendre",
+ "cancel": "Annuler",
+ "delete": "Supprimer",
+ "launch": "Lancer"
+ },
+ "deleteConfirmTitle": "Supprimer ce fichier ?",
+ "deleteConfirmCancel": "Annuler",
+ "deleteConfirmOk": "Supprimer"
},
"francetv": {
"title": "France TV",
diff --git a/src/pages/DebridPage.tsx b/src/pages/DebridPage.tsx
index 49c0d98..4d2ea8b 100644
--- a/src/pages/DebridPage.tsx
+++ b/src/pages/DebridPage.tsx
@@ -31,6 +31,13 @@ import AnimatedBorderCard from '../components/ui/animated-border-card';
import { Button } from '../components/ui/button';
import { BESTDEBRID_API_BASE, MAIN_API, PROXIES_EMBED_API } from '../config/runtime';
import { getVipHeaders } from '../utils/vipUtils';
+import {
+ buildDownloadSubFolder,
+ getMovixBridge,
+ isMovixApp,
+ type MovixDownloadMetadata,
+} from '../utils/appBridge';
+import { Smartphone } from 'lucide-react';
// Hébergeurs non supportés
const unsupportedHosts = [
@@ -283,6 +290,37 @@ const DebridPage: React.FC = () => {
const isVip = localStorage.getItem('is_vip') === 'true';
const hasAutoDebrided = useRef(false);
+ // Métadonnées film/série/anime propagées par DownloadPage via query string.
+ // Utilisées par le téléchargement in-app (Android) pour ranger correctement
+ // le fichier et afficher de jolies cartes dans /downloads. Stockées une seule
+ // fois au mount (avant que setSearchParams({}) ne les efface).
+ const [mediaContext] = useState(() => {
+ const out: MovixDownloadMetadata = {};
+ const kind = searchParams.get('kind');
+ if (kind === 'movie' || kind === 'series' || kind === 'animes') out.type = kind;
+ const tmdb = searchParams.get('tmdb');
+ if (tmdb) out.tmdbId = tmdb;
+ const titleParam = searchParams.get('title');
+ if (titleParam) out.title = titleParam;
+ const poster = searchParams.get('poster');
+ if (poster) out.poster = poster;
+ const s = searchParams.get('s');
+ if (s && !Number.isNaN(parseInt(s, 10))) out.season = parseInt(s, 10);
+ const e = searchParams.get('e');
+ if (e && !Number.isNaN(parseInt(e, 10))) out.episode = parseInt(e, 10);
+ const etitle = searchParams.get('etitle');
+ if (etitle) out.episodeTitle = etitle;
+ const lang = searchParams.get('lang');
+ if (lang) out.language = lang;
+ const q = searchParams.get('q');
+ if (q) out.quality = q;
+ return out;
+ });
+
+ const [appDownloadState, setAppDownloadState] = useState<'idle' | 'starting' | 'started' | 'error'>('idle');
+ const [appDownloadError, setAppDownloadError] = useState(null);
+ const inApp = isMovixApp();
+
const providerOptions: DebridProvider[] = ['deepbrid', 'realdebrid', 'bestdebrid'];
const isSubmitDisabled = isLoading || !url.trim();
@@ -459,6 +497,39 @@ const DebridPage: React.FC = () => {
});
};
+ const handleAppDownload = useCallback(async (debridResult: DebridResult) => {
+ const bridge = getMovixBridge();
+ if (!bridge) {
+ setAppDownloadError(t('debrid.appDownloadUnavailable'));
+ setAppDownloadState('error');
+ return;
+ }
+
+ setAppDownloadState('starting');
+ setAppDownloadError(null);
+ try {
+ const metadata: MovixDownloadMetadata = {
+ ...mediaContext,
+ provider: debridResult.provider,
+ host: debridResult.host,
+ originalLink: url || undefined,
+ };
+ await bridge.download.start({
+ url: debridResult.link,
+ filename: debridResult.filename,
+ subFolder: buildDownloadSubFolder(metadata),
+ metadata,
+ });
+ setAppDownloadState('started');
+ toast.success(t('debrid.appDownloadStarted'));
+ } catch (err) {
+ const message = err instanceof Error ? err.message : t('debrid.appDownloadError');
+ setAppDownloadError(message);
+ setAppDownloadState('error');
+ toast.error(message);
+ }
+ }, [mediaContext, t, url]);
+
const formatFileSize = (bytes: number): string => {
if (!bytes || bytes === 0) return '';
if (bytes > 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`;
@@ -659,6 +730,37 @@ const DebridPage: React.FC = () => {
+ {inApp && (
+
+
+ {appDownloadError && (
+
{appDownloadError}
+ )}
+ {appDownloadState === 'started' && !appDownloadError && (
+
+ {t('debrid.appDownloadStartedHint')}{' '}
+
+ {t('debrid.appDownloadOpenList')}
+
+
+ )}
+
+ )}
diff --git a/src/pages/DownloadPage.tsx b/src/pages/DownloadPage.tsx
index 89ffd8b..5bf2c28 100644
--- a/src/pages/DownloadPage.tsx
+++ b/src/pages/DownloadPage.tsx
@@ -557,7 +557,14 @@ const LinkSelector: React.FC<{
decodedLink: DecodedLink | null;
error: string | null;
queueInfo?: { size: number } | null;
-}> = ({ isOpen, onClose, title, selectedLink, isDecoding, decodedLink, error, queueInfo }) => {
+ // Métadonnées propagées au /debrid pour que le download in-app les retrouve.
+ mediaKind: 'movie' | 'series' | 'animes';
+ tmdbId: string | undefined;
+ posterPath: string | null | undefined;
+ currentSeason?: number;
+ currentEpisode?: number;
+ episodeTitle?: string;
+}> = ({ isOpen, onClose, title, selectedLink, isDecoding, decodedLink, error, queueInfo, mediaKind, tmdbId, posterPath, currentSeason, currentEpisode, episodeTitle }) => {
const { t, i18n } = useTranslation();
const [isClosing, setIsClosing] = useState(false);
const isVipUser = localStorage.getItem('is_vip') === 'true';
@@ -775,7 +782,23 @@ const LinkSelector: React.FC<{