import { Ionicons } from "@expo/vector-icons"; import { useQueryClient } from "@tanstack/react-query"; import { Image } from "expo-image"; import { useRouter } from "expo-router"; import { t } from "i18next"; import { useMemo } from "react"; import { ActivityIndicator, Platform, TouchableOpacity, type TouchableOpacityProps, View, } from "react-native"; import { toast } from "sonner-native"; import { Text } from "@/components/common/Text"; import { useDownload } from "@/providers/DownloadProvider"; import { JobStatus } from "@/providers/Downloads/types"; import { storage } from "@/utils/mmkv"; import { formatTimeString } from "@/utils/time"; import { Button } from "../Button"; const bytesToMB = (bytes: number) => { return bytes / 1024 / 1024; }; interface DownloadCardProps extends TouchableOpacityProps { process: JobStatus; } export const DownloadCard = ({ process, ...props }: DownloadCardProps) => { const { startDownload, pauseDownload, resumeDownload, removeProcess } = useDownload(); const router = useRouter(); const queryClient = useQueryClient(); const handlePause = async (id: string) => { try { await pauseDownload(id); toast.success(t("home.downloads.toasts.download_paused")); } catch (error) { console.error("Error pausing download:", error); toast.error(t("home.downloads.toasts.could_not_pause_download")); } }; const handleResume = async (id: string) => { try { await resumeDownload(id); toast.success(t("home.downloads.toasts.download_resumed")); } catch (error) { console.error("Error resuming download:", error); toast.error(t("home.downloads.toasts.could_not_resume_download")); } }; const handleDelete = async (id: string) => { try { await removeProcess(id); toast.success(t("home.downloads.toasts.download_deleted")); queryClient.invalidateQueries({ queryKey: ["downloads"] }); } catch (error) { console.error("Error deleting download:", error); toast.error(t("home.downloads.toasts.could_not_delete_download")); } }; const eta = (p: JobStatus) => { if (!p.speed || p.speed <= 0 || !p.estimatedTotalSizeBytes) return null; const bytesRemaining = p.estimatedTotalSizeBytes - (p.bytesDownloaded || 0); if (bytesRemaining <= 0) return null; const secondsRemaining = bytesRemaining / p.speed; return formatTimeString(secondsRemaining, "s"); }; const base64Image = useMemo(() => { return storage.getString(process.item.Id!); }, []); // Sanitize progress to ensure it's within valid bounds const sanitizedProgress = useMemo(() => { if ( typeof process.progress !== "number" || Number.isNaN(process.progress) ) { return 0; } return Math.max(0, Math.min(100, process.progress)); }, [process.progress]); return ( router.push(`/(auth)/items/page?id=${process.item.Id}`)} className='relative bg-neutral-900 border border-neutral-800 rounded-2xl overflow-hidden' {...props} > {process.status === "downloading" && ( 0 ? `${Math.max(5, sanitizedProgress)}%` : "5%", }} /> )} {/* Action buttons in bottom right corner */} {process.status === "downloading" && Platform.OS !== "ios" && ( handlePause(process.id)} className='p-1' > )} {process.status === "paused" && Platform.OS !== "ios" && ( handleResume(process.id)} className='p-1' > )} handleDelete(process.id)} className='p-1' > {base64Image && ( )} {process.item.Type} {process.item.Name} {process.item.ProductionYear} {sanitizedProgress === 0 ? ( ) : ( {sanitizedProgress.toFixed(0)}% )} {process.speed && process.speed > 0 && ( {bytesToMB(process.speed).toFixed(2)} MB/s )} {eta(process) && ( {t("home.downloads.eta", { eta: eta(process) })} )} {process.status} {process.status === "completed" && ( )} ); };