diff --git a/src/components/ProgressPanel.tsx b/src/components/ProgressPanel.tsx index e5ba1e6..9a8a0e9 100644 --- a/src/components/ProgressPanel.tsx +++ b/src/components/ProgressPanel.tsx @@ -37,7 +37,10 @@ export const progressState = { // ── Progress modal content (rendered inside showModal) ───────────────────── -export const ProgressModalContent: VFC<{ closeModal?: () => void }> = ({ closeModal }) => { +export const ProgressModalContent: VFC<{ + closeModal?: () => void; + onUserDismiss?: () => void; +}> = ({ closeModal, onUserDismiss }) => { const [, rerender] = useState(0); const [cancelling, setCancelling] = useState(false); @@ -48,6 +51,11 @@ export const ProgressModalContent: VFC<{ closeModal?: () => void }> = ({ closeMo const { operation, progress } = progressState; if (!operation) return null; + const handleClose = () => { + onUserDismiss?.(); + closeModal?.(); + }; + const isInstalling = operation.type === "install"; const isComplete = operation.status === "complete"; const isError = operation.status === "error"; @@ -77,7 +85,7 @@ export const ProgressModalContent: VFC<{ closeModal?: () => void }> = ({ closeMo }; return ( - +
diff --git a/src/eventPoller.tsx b/src/eventPoller.tsx index 2330c06..4d79aa9 100644 --- a/src/eventPoller.tsx +++ b/src/eventPoller.tsx @@ -5,6 +5,7 @@ import { showModal } from "@decky/ui"; import { call, toaster } from "@decky/api"; +import type { ToastNotification } from "@decky/api"; import type { ShortcutConfig } from "./hooks/useAgent"; import { ProgressModalContent, progressState } from "./components/ProgressPanel"; @@ -199,17 +200,58 @@ async function handleUpdateArtwork(event: UpdateArtworkEvent) { let progressModalHandle: { Close: () => void } | null = null; let pairingModalHandle: { Close: () => void } | null = null; +let recoveryToastHandle: ToastNotification | null = null; +let closingModalProgrammatically = false; + +function dismissRecoveryToast() { + if (recoveryToastHandle) { + recoveryToastHandle.dismiss(); + recoveryToastHandle = null; + } +} + +function handleUserDismissProgressModal() { + // Ignore callbacks triggered by our own Close() calls. + if (closingModalProgrammatically) return; + + progressModalHandle = null; + + const op = progressState.operation; + // Only surface the recovery toast while the transfer is still running. + if (!op || op.status === "complete" || op.status === "error") return; + + dismissRecoveryToast(); + recoveryToastHandle = toaster.toast({ + title: op.type === "install" ? "Transfer in progress" : "Removal in progress", + body: `${op.gameName} — tap to show progress`, + logo: toastLogo, + duration: 10_000, + expiration: 24 * 60 * 60 * 1000, + showNewIndicator: true, + onClick: () => { + dismissRecoveryToast(); + showProgressModal(); + }, + }); +} function showProgressModal() { if (!progressModalHandle) { - progressModalHandle = showModal(); + progressModalHandle = showModal( + + ); } + // Modal is visible again — no need for the recovery toast. + dismissRecoveryToast(); } function closeProgressModal(delay = 3000) { setTimeout(() => { + closingModalProgrammatically = true; progressModalHandle?.Close(); progressModalHandle = null; + closingModalProgrammatically = false; + dismissRecoveryToast(); }, delay); }