import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { playNotificationTone } from '../lib/notificationSound'; import { clearCustomNotificationSound, getCustomNotificationSoundMeta, MAX_NOTIFICATION_SOUND_BYTES, type NotificationSoundEntry, setCustomNotificationSound, subscribeNotificationSoundChanges, } from '../lib/notificationSoundStorage'; import { SpinnerIcon, TrashIcon } from './icons'; interface Props { disabled?: boolean; } const BYTES_PER_MB = 1024 * 1024; // UI for the custom new-message notification chime. Single file slot. // Preview button calls playNotificationTone() directly so the user // hears exactly what a real incoming message will sound like (same // AudioContext, same volume curve). export function NotificationSoundSettings({ disabled = false }: Props) { const { t } = useTranslation(['app']); const inputRef = useRef(null); const [current, setCurrent] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const refresh = useCallback(async () => { try { const meta = await getCustomNotificationSoundMeta(); setCurrent(meta); } catch (err: unknown) { console.error('getCustomNotificationSoundMeta failed', err); } }, []); useEffect(() => { void refresh(); return subscribeNotificationSoundChanges(() => { void refresh(); }); }, [refresh]); async function handleFile(file: File): Promise { setError(null); setBusy(true); try { await setCustomNotificationSound(file); // refresh fires via subscribeNotificationSoundChanges } catch (err: unknown) { const code = err instanceof Error ? err.message : 'upload_failed'; if (code === 'sound_too_large') { setError( t('app:settings.notif_sound_error_too_large', { defaultValue: 'Datei zu groß (max {{max}} MB).', max: MAX_NOTIFICATION_SOUND_BYTES / BYTES_PER_MB, }), ); } else if (code === 'sound_not_audio') { setError( t('app:settings.notif_sound_error_not_audio', { defaultValue: 'Nur Audio-Dateien werden unterstützt.', }), ); } else if (code === 'empty_file') { setError( t('app:settings.notif_sound_error_empty', { defaultValue: 'Leere Datei.', }), ); } else { setError( t('app:settings.notif_sound_error_generic', { defaultValue: 'Ton konnte nicht gespeichert werden.', }), ); } } finally { setBusy(false); if (inputRef.current) inputRef.current.value = ''; } } async function handleReset(): Promise { setError(null); setBusy(true); try { await clearCustomNotificationSound(); // refresh fires via subscribeNotificationSoundChanges } catch (err: unknown) { console.error('clearCustomNotificationSound failed', err); } finally { setBusy(false); } } function handlePreview(): void { // Same code path as a real notification — user hears exactly what // peers triggering a message will cause. playNotificationTone(); } const hasCustom = current !== null; const sizeMb = current ? (current.size / BYTES_PER_MB).toFixed(2) : null; const interactionsDisabled = disabled || busy; return (
{t('app:settings.notif_sound_title', { defaultValue: 'Benachrichtigungston' })}
{hasCustom && current ? t('app:settings.notif_sound_custom_active', { defaultValue: '{{name}} · {{size}} MB', name: current.filename, size: sizeMb, }) : t('app:settings.notif_sound_default_active', { defaultValue: 'Standard (Zwei-Ton-Chime)', })}
{ const f = e.target.files?.[0]; if (f) void handleFile(f); }} /> {hasCustom && ( )}
{error && (

{error}

)}

{t('app:settings.notif_sound_hint', { defaultValue: 'MP3, WAV, OGG oder M4A bis 1 MB. Spielt bei neuen Nachrichten in Chats die du nicht aktiv ansiehst.', })}

); }