From 276446a4dd031d1aa358e396d00d8af093019fd4 Mon Sep 17 00:00:00 2001 From: NimBold Date: Thu, 2 Jul 2026 01:58:43 +0330 Subject: [PATCH] fix(app): decouple completion chime Play the completion sound through the app with Web Audio so it is not tied to whether desktop notifications are enabled or whether a platform-specific notification sound name is supported. Keep the notification body focused on completion status and leave the settings toggle available even when notifications are disabled. --- src/App.tsx | 49 +++++++++++++++++++++++---------- src/components/SettingsView.tsx | 16 +---------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 39af64c..55d7a41 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,9 +42,34 @@ const getScheduledQueueIds = () => { return selectedQueueIds; }; +type AudioContextConstructor = typeof AudioContext; + +const playCompletionChime = () => { + const AudioCtor = + window.AudioContext || + (window as Window & { webkitAudioContext?: AudioContextConstructor }).webkitAudioContext; + if (!AudioCtor) return; + + const context = new AudioCtor(); + const oscillator = context.createOscillator(); + const gain = context.createGain(); + oscillator.type = 'sine'; + oscillator.frequency.setValueAtTime(880, context.currentTime); + oscillator.frequency.exponentialRampToValueAtTime(1320, context.currentTime + 0.12); + gain.gain.setValueAtTime(0.0001, context.currentTime); + gain.gain.exponentialRampToValueAtTime(0.18, context.currentTime + 0.02); + gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.24); + oscillator.connect(gain); + gain.connect(context.destination); + oscillator.start(); + oscillator.stop(context.currentTime + 0.24); + oscillator.onended = () => { + void context.close(); + }; +}; + function App() { const platform = usePlatformInfo(); - const platformOsRef = useRef(platform.os); const [filter, setFilter] = useState('all'); const [coreReady, setCoreReady] = useState(false); @@ -115,10 +140,6 @@ function App() { const { addToast } = useToast(); - useEffect(() => { - platformOsRef.current = platform.os; - }, [platform.os]); - useEffect(() => { let active = true; const initialize = async () => { @@ -520,24 +541,22 @@ function App() { const unlistenTerminalState = listen('download-state', (event) => { if (event.payload.status !== 'completed' && event.payload.status !== 'failed') return; const settings = useSettingsStore.getState(); + if (event.payload.status === 'completed' && settings.playCompletionSound) { + try { + playCompletionChime(); + } catch (error) { + console.error('Completion sound failed:', error); + } + } if (!settings.showNotifications) return; const item = useDownloadStore.getState().downloads.find(d => d.id === event.payload.id); const fileName = item?.fileName || 'A file'; - const platformOs = platformOsRef.current; - const sound = settings.playCompletionSound - ? platformOs === 'macos' - ? 'Ping' - : platformOs === 'linux' - ? 'message-new-instant' - : undefined - : undefined; if (event.payload.status === 'completed') { try { sendNotification({ title: 'Download Complete', - body: `${fileName} has finished downloading.`, - sound + body: `${fileName} has finished downloading.` }); } catch (error) { console.error('Completion notification failed:', error); diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx index 1289fc2..dd23694 100644 --- a/src/components/SettingsView.tsx +++ b/src/components/SettingsView.tsx @@ -526,19 +526,6 @@ runEngineChecks(false); className="app-control w-24 text-center" /> -
-
- Global speed limit: - {settings.globalSpeedLimit || 'Unlimited'} -
- -
Automatic retries: @@ -571,12 +558,11 @@ runEngineChecks(false); className="mac-switch" /> -