import { Text, useTheme } from "@streamplace/components";
import { AlertCircle, ArrowUp, CheckCircle2, X } from "lucide-react-native";
import { useSyncExternalStore } from "react";
import { Pressable, View } from "react-native";
import {
cancelUpload,
dismissUpload,
getUploads,
subscribeUploads,
UploadJob,
} from "utils/upload-manager";
// Floating upload status card, pinned to the lower-right corner of the app
// shell. Lives outside the navigators so it keeps rendering progress while
// the user moves between screens; the uploads themselves live in
// utils/upload-manager.
function humanBytes(n: number): string {
if (n < 1024) return `${n} B`;
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
}
function UploadRow({ job }: { job: UploadJob }) {
const { theme } = useTheme();
const pct = job.bytesTotal > 0 ? (job.bytesSent / job.bytesTotal) * 100 : 0;
return (
{job.status === "uploading" && (
)}
{job.status === "done" && (
)}
{job.status === "error" && (
)}
{job.filename}
job.status === "uploading"
? cancelUpload(job.id)
: dismissUpload(job.id)
}
hitSlop={8}
>
{job.status === "uploading" && (
<>
{pct.toFixed(1)}% — {humanBytes(job.bytesSent)} /{" "}
{humanBytes(job.bytesTotal)}
>
)}
{job.status === "done" && (
Upload complete — processing continues on the server
)}
{job.status === "error" && (
{job.error || "Upload failed"}
)}
);
}
export default function UploadProgressIndicator() {
const { theme } = useTheme();
const jobs = useSyncExternalStore(subscribeUploads, getUploads, getUploads);
if (jobs.length === 0) return null;
return (
{jobs.map((job) => (
))}
);
}