diff --git a/_locales/en/messages.json b/_locales/en/messages.json index cc28c5878069..14e73570bbeb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -10434,6 +10434,14 @@ "messageformat": "Group Avatar", "description": "Title for the avatar picker in the group creation flow" }, + "icu:Preferences__message-audio-title": { + "messageformat": "In-chat message sound", + "description": "Title for message audio setting" + }, + "icu:Preferences__message-audio-description": { + "messageformat": "Hear a notification sound for sent and received messages while in the chat.", + "description": "Description for message audio setting" + }, "Preferences__button--general": { "message": "General", "description": "(deleted 03/29/2023) Button to switch the settings view" diff --git a/sounds/notification.ogg b/sounds/notification.ogg index 93b3a0144e69..4a00af61094a 100755 Binary files a/sounds/notification.ogg and b/sounds/notification.ogg differ diff --git a/sounds/pop.wav b/sounds/pop.wav new file mode 100644 index 000000000000..6bca62445a59 Binary files /dev/null and b/sounds/pop.wav differ diff --git a/sounds/whoosh.wav b/sounds/whoosh.wav new file mode 100644 index 000000000000..68ea8a3ca52f Binary files /dev/null and b/sounds/whoosh.wav differ diff --git a/ts/components/Preferences.stories.tsx b/ts/components/Preferences.stories.tsx index 3dd7f5e69871..88dcb62e5c33 100644 --- a/ts/components/Preferences.stories.tsx +++ b/ts/components/Preferences.stories.tsx @@ -81,6 +81,7 @@ const getDefaultArgs = (): PropsDataType => ({ hasLinkPreviews: true, hasMediaCameraPermissions: true, hasMediaPermissions: true, + hasMessageAudio: true, hasMinimizeToAndStartInSystemTray: true, hasMinimizeToSystemTray: true, hasNotificationAttention: false, @@ -92,7 +93,6 @@ const getDefaultArgs = (): PropsDataType => ({ hasTextFormatting: true, hasTypingIndicators: true, initialSpellCheckSetting: true, - isAudioNotificationsSupported: true, isAutoDownloadUpdatesSupported: true, isAutoLaunchSupported: true, isFormattingFlagEnabled: true, @@ -152,6 +152,7 @@ export default { onLastSyncTimeChange: { action: true }, onMediaCameraPermissionsChange: { action: true }, onMediaPermissionsChange: { action: true }, + onMessageAudioChange: { action: true }, onMinimizeToAndStartInSystemTrayChange: { action: true }, onMinimizeToSystemTrayChange: { action: true }, onNotificationAttentionChange: { action: true }, diff --git a/ts/components/Preferences.tsx b/ts/components/Preferences.tsx index f5bf0e3c6836..d04ec8a13489 100644 --- a/ts/components/Preferences.tsx +++ b/ts/components/Preferences.tsx @@ -80,6 +80,7 @@ export type PropsDataType = { hasLinkPreviews: boolean; hasMediaCameraPermissions: boolean; hasMediaPermissions: boolean; + hasMessageAudio: boolean; hasMinimizeToAndStartInSystemTray: boolean; hasMinimizeToSystemTray: boolean; hasNotificationAttention: boolean; @@ -111,7 +112,6 @@ export type PropsDataType = { isFormattingFlagEnabled: boolean; // Limited support features - isAudioNotificationsSupported: boolean; isAutoDownloadUpdatesSupported: boolean; isAutoLaunchSupported: boolean; isHideMenuBarSupported: boolean; @@ -163,6 +163,7 @@ type PropsFunctionType = { onLastSyncTimeChange: (time: number) => unknown; onMediaCameraPermissionsChange: CheckboxChangeHandlerType; onMediaPermissionsChange: CheckboxChangeHandlerType; + onMessageAudioChange: CheckboxChangeHandlerType; onMinimizeToAndStartInSystemTrayChange: CheckboxChangeHandlerType; onMinimizeToSystemTrayChange: CheckboxChangeHandlerType; onNotificationAttentionChange: CheckboxChangeHandlerType; @@ -252,6 +253,7 @@ export function Preferences({ hasLinkPreviews, hasMediaCameraPermissions, hasMediaPermissions, + hasMessageAudio, hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray, hasNotificationAttention, @@ -264,7 +266,6 @@ export function Preferences({ hasTypingIndicators, i18n, initialSpellCheckSetting, - isAudioNotificationsSupported, isAutoDownloadUpdatesSupported, isAutoLaunchSupported, isFormattingFlagEnabled, @@ -290,6 +291,7 @@ export function Preferences({ onLastSyncTimeChange, onMediaCameraPermissionsChange, onMediaPermissionsChange, + onMessageAudioChange, onMinimizeToAndStartInSystemTrayChange, onMinimizeToSystemTrayChange, onNotificationAttentionChange, @@ -857,15 +859,6 @@ export function Preferences({ onChange={onNotificationAttentionChange} /> )} - {isAudioNotificationsSupported && ( - - )} + + + + ); } else if (page === Page.Privacy) { diff --git a/ts/main/settingsChannel.ts b/ts/main/settingsChannel.ts index 97d0b1eb7f41..c2212b15e1d9 100644 --- a/ts/main/settingsChannel.ts +++ b/ts/main/settingsChannel.ts @@ -81,6 +81,7 @@ export class SettingsChannel extends EventEmitter { this.installSetting('notificationSetting'); this.installSetting('notificationDrawAttention'); + this.installSetting('audioMessage'); this.installSetting('audioNotification'); this.installSetting('countMutedConversations'); diff --git a/ts/services/notifications.ts b/ts/services/notifications.ts index b498368d1812..b14bb6c5b715 100644 --- a/ts/services/notifications.ts +++ b/ts/services/notifications.ts @@ -4,32 +4,30 @@ import os from 'os'; import { debounce } from 'lodash'; import EventEmitter from 'events'; -import { Sound } from '../util/Sound'; -import { - AudioNotificationSupport, - getAudioNotificationSupport, - shouldHideExpiringMessageBody, -} from '../types/Settings'; +import { Sound, SoundType } from '../util/Sound'; +import { shouldHideExpiringMessageBody } from '../types/Settings'; import OS from '../util/os/osMain'; import * as log from '../logging/log'; import { makeEnumParser } from '../util/enum'; import { missingCaseError } from '../util/missingCaseError'; import type { StorageInterface } from '../types/Storage.d'; import type { LocalizerType } from '../types/Util'; +import { drop } from '../util/drop'; type NotificationDataType = Readonly<{ conversationId: string; - storyId?: string; + isExpiringMessage: boolean; messageId: string; - senderTitle: string; message: string; notificationIconUrl?: undefined | string; - isExpiringMessage: boolean; reaction?: { emoji: string; targetAuthorUuid: string; targetTimestamp: number; }; + senderTitle: string; + storyId?: string; + useTriToneSound?: boolean; wasShown?: boolean; }>; @@ -133,6 +131,7 @@ class NotificationService extends EventEmitter { onNotificationClick, silent, title, + useTriToneSound, }: Readonly<{ icon?: string; message: string; @@ -140,28 +139,25 @@ class NotificationService extends EventEmitter { onNotificationClick: () => void; silent: boolean; title: string; + useTriToneSound?: boolean; }>): void { log.info('NotificationService: showing a notification'); this.lastNotification?.close(); - const audioNotificationSupport = getAudioNotificationSupport(OS); - const notification = new window.Notification(title, { body: OS.isLinux() ? filterNotificationText(message) : message, icon, - silent: - silent || audioNotificationSupport !== AudioNotificationSupport.Native, + silent: true, tag: messageId, }); notification.onclick = onNotificationClick; - if ( - !silent && - audioNotificationSupport === AudioNotificationSupport.Custom - ) { + if (!silent) { + const soundType = + messageId && !useTriToneSound ? SoundType.Pop : SoundType.TriTone; // We kick off the sound to be played. No need to await it. - void new Sound({ src: 'sounds/notification.ogg' }).play(); + drop(new Sound({ soundType }).play()); } this.lastNotification = notification; @@ -273,12 +269,13 @@ class NotificationService extends EventEmitter { const { conversationId, - storyId, - messageId, - senderTitle, - message, isExpiringMessage, + message, + messageId, reaction, + senderTitle, + storyId, + useTriToneSound, wasShown, } = notificationData; @@ -346,13 +343,15 @@ class NotificationService extends EventEmitter { }; this.notify({ - title: notificationTitle, icon: notificationIconUrl, + messageId, message: notificationMessage, - silent: !shouldPlayNotificationSound, onNotificationClick: () => { this.emit('click', conversationId, messageId, storyId); }, + silent: !shouldPlayNotificationSound, + title: notificationTitle, + useTriToneSound, }); } diff --git a/ts/state/ducks/composer.ts b/ts/state/ducks/composer.ts index 000fa5859518..e65326276247 100644 --- a/ts/state/ducks/composer.ts +++ b/ts/state/ducks/composer.ts @@ -89,6 +89,7 @@ import { strictAssert } from '../../util/assert'; import { makeQuote } from '../../util/makeQuote'; import { sendEditedMessage as doSendEditedMessage } from '../../util/sendEditedMessage'; import { maybeBlockSendForFormattingModal } from '../../util/maybeBlockSendForFormattingModal'; +import { Sound, SoundType } from '../../util/Sound'; // State // eslint-disable-next-line local-rules/type-alias-readonlydeep @@ -616,6 +617,8 @@ function sendMultiMediaMessage( ); dispatch(incrementSendCounter(conversationId)); dispatch(setComposerDisabledState(conversationId, false)); + + drop(new Sound({ soundType: SoundType.Whoosh }).play()); }, } ); diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx index 790d5ec4986e..afdc466e8f13 100644 --- a/ts/state/smart/CallManager.tsx +++ b/ts/state/smart/CallManager.tsx @@ -89,7 +89,8 @@ async function notifyForCall( onNotificationClick: () => { window.IPC.showWindow(); }, - silent: false, + // The ringtone plays so we don't need sound for the notification + silent: true, }); } diff --git a/ts/state/smart/VoiceNotesPlaybackProvider.tsx b/ts/state/smart/VoiceNotesPlaybackProvider.tsx index 285f7248e52c..f2c5b346e61e 100644 --- a/ts/state/smart/VoiceNotesPlaybackProvider.tsx +++ b/ts/state/smart/VoiceNotesPlaybackProvider.tsx @@ -13,7 +13,7 @@ import { import { globalMessageAudio } from '../../services/globalMessageAudio'; import { strictAssert } from '../../util/assert'; import * as log from '../../logging/log'; -import { Sound } from '../../util/Sound'; +import { Sound, SoundType } from '../../util/Sound'; import { getConversations } from '../selectors/conversations'; import { SeenStatus } from '../../MessageSeenStatus'; import { markViewed } from '../ducks/conversations'; @@ -21,10 +21,10 @@ import * as Errors from '../../types/errors'; import { usePrevious } from '../../hooks/usePrevious'; const stateChangeConfirmUpSound = new Sound({ - src: 'sounds/state-change_confirm-up.ogg', + soundType: SoundType.VoiceNoteStart, }); const stateChangeConfirmDownSound = new Sound({ - src: 'sounds/state-change_confirm-down.ogg', + soundType: SoundType.VoiceNoteEnd, }); /** diff --git a/ts/test-node/types/Settings_test.ts b/ts/test-node/types/Settings_test.ts index b2045e59d87f..d288d5084d7c 100644 --- a/ts/test-node/types/Settings_test.ts +++ b/ts/test-node/types/Settings_test.ts @@ -19,74 +19,6 @@ describe('Settings', () => { sandbox.restore(); }); - describe('getAudioNotificationSupport', () => { - it('returns native support on macOS', () => { - sandbox.stub(process, 'platform').value('darwin'); - const OS = getOSFunctions(os.release()); - assert.strictEqual( - Settings.getAudioNotificationSupport(OS), - Settings.AudioNotificationSupport.Native - ); - }); - - it('returns no support on Windows 7', () => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('7.0.0'); - const OS = getOSFunctions(os.release()); - assert.strictEqual( - Settings.getAudioNotificationSupport(OS), - Settings.AudioNotificationSupport.None - ); - }); - - it('returns native support on Windows 8', () => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('8.0.0'); - const OS = getOSFunctions(os.release()); - assert.strictEqual( - Settings.getAudioNotificationSupport(OS), - Settings.AudioNotificationSupport.Native - ); - }); - - it('returns custom support on Linux', () => { - sandbox.stub(process, 'platform').value('linux'); - const OS = getOSFunctions(os.release()); - assert.strictEqual( - Settings.getAudioNotificationSupport(OS), - Settings.AudioNotificationSupport.Custom - ); - }); - }); - - describe('isAudioNotificationSupported', () => { - it('returns true on macOS', () => { - sandbox.stub(process, 'platform').value('darwin'); - const OS = getOSFunctions(os.release()); - assert.isTrue(Settings.isAudioNotificationSupported(OS)); - }); - - it('returns false on Windows 7', () => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('7.0.0'); - const OS = getOSFunctions(os.release()); - assert.isFalse(Settings.isAudioNotificationSupported(OS)); - }); - - it('returns true on Windows 8', () => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('8.0.0'); - const OS = getOSFunctions(os.release()); - assert.isTrue(Settings.isAudioNotificationSupported(OS)); - }); - - it('returns true on Linux', () => { - sandbox.stub(process, 'platform').value('linux'); - const OS = getOSFunctions(os.release()); - assert.isTrue(Settings.isAudioNotificationSupported(OS)); - }); - }); - describe('isNotificationGroupingSupported', () => { it('returns true on macOS', () => { sandbox.stub(process, 'platform').value('darwin'); diff --git a/ts/types/Settings.ts b/ts/types/Settings.ts index 6efe0596780d..31100d9d66fd 100644 --- a/ts/types/Settings.ts +++ b/ts/types/Settings.ts @@ -8,27 +8,6 @@ import { isProduction } from '../util/version'; const MIN_WINDOWS_VERSION = '8.0.0'; -export enum AudioNotificationSupport { - None, - Native, - Custom, -} - -export function getAudioNotificationSupport( - OS: OSType -): AudioNotificationSupport { - if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) { - return AudioNotificationSupport.Native; - } - if (OS.isLinux()) { - return AudioNotificationSupport.Custom; - } - return AudioNotificationSupport.None; -} - -export const isAudioNotificationSupported = (OS: OSType): boolean => - getAudioNotificationSupport(OS) !== AudioNotificationSupport.None; - // Using `Notification::tag` has a bug on Windows 7: // https://github.com/electron/electron/issues/11189 export const isNotificationGroupingSupported = (OS: OSType): boolean => diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index 642082dafcb8..20b3d63b1f1f 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -62,6 +62,7 @@ export type StorageAccessType = { 'spell-check': boolean; 'system-tray-setting': SystemTraySetting; 'theme-setting': ThemeSettingType; + audioMessage: boolean; attachmentMigration_isComplete: boolean; attachmentMigration_lastProcessedIndex: number; blocked: ReadonlyArray; diff --git a/ts/util/Sound.ts b/ts/util/Sound.ts index 65ada0cfd05b..020ef2056d35 100644 --- a/ts/util/Sound.ts +++ b/ts/util/Sound.ts @@ -2,14 +2,26 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { missingCaseError } from './missingCaseError'; + +export enum SoundType { + CallingHangUp, + CallingPresenting, + Pop, + Ringtone, + TriTone, + VoiceNoteEnd, + VoiceNoteStart, + Whoosh, +} export type SoundOpts = { loop?: boolean; - src: string; + soundType: SoundType; }; export class Sound { - static sounds = new Map(); + static sounds = new Map(); private static context: AudioContext | undefined; @@ -17,27 +29,29 @@ export class Sound { private node?: AudioBufferSourceNode; - private readonly src: string; + private readonly soundType: SoundType; constructor(options: SoundOpts) { this.loop = Boolean(options.loop); - this.src = options.src; + this.soundType = options.soundType; } async play(): Promise { - if (!Sound.sounds.has(this.src)) { + let soundBuffer = Sound.sounds.get(this.soundType); + + if (!soundBuffer) { try { - const buffer = await Sound.loadSoundFile(this.src); + const src = Sound.getSrc(this.soundType); + const buffer = await Sound.loadSoundFile(src); const decodedBuffer = await this.context.decodeAudioData(buffer); - Sound.sounds.set(this.src, decodedBuffer); + Sound.sounds.set(this.soundType, decodedBuffer); + soundBuffer = decodedBuffer; } catch (err) { log.error(`Sound error: ${err}`); return; } } - const soundBuffer = Sound.sounds.get(this.src); - const soundNode = this.context.createBufferSource(); soundNode.buffer = soundBuffer; @@ -87,4 +101,40 @@ export class Sound { xhr.send(); }); } + + static getSrc(soundStyle: SoundType): string { + if (soundStyle === SoundType.CallingHangUp) { + return 'sounds/navigation-cancel.ogg'; + } + + if (soundStyle === SoundType.CallingPresenting) { + return 'sounds/navigation_selection-complete-celebration.ogg'; + } + + if (soundStyle === SoundType.Pop) { + return 'sounds/pop.wav'; + } + + if (soundStyle === SoundType.TriTone) { + return 'sounds/notification.ogg'; + } + + if (soundStyle === SoundType.Ringtone) { + return 'sounds/ringtone_minimal.ogg'; + } + + if (soundStyle === SoundType.VoiceNoteEnd) { + return 'sounds/state-change_confirm-down.ogg'; + } + + if (soundStyle === SoundType.VoiceNoteStart) { + return 'sounds/state-change_confirm-up.ogg'; + } + + if (soundStyle === SoundType.Whoosh) { + return 'sounds/whoosh.wav'; + } + + throw missingCaseError(soundStyle); + } } diff --git a/ts/util/callingTones.ts b/ts/util/callingTones.ts index 5e0f3f239183..c478a51a7ff2 100644 --- a/ts/util/callingTones.ts +++ b/ts/util/callingTones.ts @@ -3,7 +3,7 @@ import PQueue from 'p-queue'; import { MINUTE } from './durations'; -import { Sound } from './Sound'; +import { Sound, SoundType } from './Sound'; const ringtoneEventQueue = new PQueue({ concurrency: 1, @@ -21,7 +21,7 @@ class CallingTones { } const tone = new Sound({ - src: 'sounds/navigation-cancel.ogg', + soundType: SoundType.CallingHangUp, }); await tone.play(); } @@ -40,7 +40,7 @@ class CallingTones { this.ringtone = new Sound({ loop: true, - src: 'sounds/ringtone_minimal.ogg', + soundType: SoundType.Ringtone, }); await this.ringtone.play(); @@ -63,7 +63,7 @@ class CallingTones { } const tone = new Sound({ - src: 'sounds/navigation_selection-complete-celebration.ogg', + soundType: SoundType.CallingPresenting, }); await tone.play(); diff --git a/ts/util/createIPCEvents.ts b/ts/util/createIPCEvents.ts index c605e3321582..58dae69e75f2 100644 --- a/ts/util/createIPCEvents.ts +++ b/ts/util/createIPCEvents.ts @@ -50,6 +50,7 @@ type NotificationSettingType = 'message' | 'name' | 'count' | 'off'; export type IPCEventsValuesType = { alwaysRelayCalls: boolean | undefined; audioNotification: boolean | undefined; + audioMessage: boolean; autoDownloadUpdate: boolean; autoLaunch: boolean; callRingtoneNotification: boolean; @@ -371,6 +372,8 @@ export function createIPCEvents( window.storage.get('notification-draw-attention', false), setNotificationDrawAttention: value => window.storage.put('notification-draw-attention', value), + getAudioMessage: () => window.storage.get('audioMessage', false), + setAudioMessage: value => window.storage.put('audioMessage', value), getAudioNotification: () => window.storage.get('audio-notification'), setAudioNotification: value => window.storage.put('audio-notification', value), diff --git a/ts/windows/preload.ts b/ts/windows/preload.ts index 363f767f6c7d..4c1e40aa4298 100644 --- a/ts/windows/preload.ts +++ b/ts/windows/preload.ts @@ -38,6 +38,7 @@ installCallback('shouldShowStoriesSettings'); installCallback('syncRequest'); installSetting('alwaysRelayCalls'); +installSetting('audioMessage'); installSetting('audioNotification'); installSetting('autoDownloadUpdate'); installSetting('autoLaunch'); diff --git a/ts/windows/settings/app.tsx b/ts/windows/settings/app.tsx index dcffb3c55109..270b90da84c7 100644 --- a/ts/windows/settings/app.tsx +++ b/ts/windows/settings/app.tsx @@ -44,6 +44,7 @@ SettingsWindowProps.onRender( hasLinkPreviews, hasMediaCameraPermissions, hasMediaPermissions, + hasMessageAudio, hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray, hasNotificationAttention, @@ -55,7 +56,6 @@ SettingsWindowProps.onRender( hasTextFormatting, hasTypingIndicators, initialSpellCheckSetting, - isAudioNotificationsSupported, isAutoDownloadUpdatesSupported, isAutoLaunchSupported, isFormattingFlagEnabled, @@ -80,6 +80,7 @@ SettingsWindowProps.onRender( onLastSyncTimeChange, onMediaCameraPermissionsChange, onMediaPermissionsChange, + onMessageAudioChange, onMinimizeToAndStartInSystemTrayChange, onMinimizeToSystemTrayChange, onNotificationAttentionChange, @@ -141,6 +142,7 @@ SettingsWindowProps.onRender( hasLinkPreviews={hasLinkPreviews} hasMediaCameraPermissions={hasMediaCameraPermissions} hasMediaPermissions={hasMediaPermissions} + hasMessageAudio={hasMessageAudio} hasMinimizeToAndStartInSystemTray={hasMinimizeToAndStartInSystemTray} hasMinimizeToSystemTray={hasMinimizeToSystemTray} hasNotificationAttention={hasNotificationAttention} @@ -153,7 +155,6 @@ SettingsWindowProps.onRender( hasTypingIndicators={hasTypingIndicators} i18n={i18n} initialSpellCheckSetting={initialSpellCheckSetting} - isAudioNotificationsSupported={isAudioNotificationsSupported} isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported} isAutoLaunchSupported={isAutoLaunchSupported} isFormattingFlagEnabled={isFormattingFlagEnabled} @@ -180,6 +181,7 @@ SettingsWindowProps.onRender( onLastSyncTimeChange={onLastSyncTimeChange} onMediaCameraPermissionsChange={onMediaCameraPermissionsChange} onMediaPermissionsChange={onMediaPermissionsChange} + onMessageAudioChange={onMessageAudioChange} onMinimizeToAndStartInSystemTrayChange={ onMinimizeToAndStartInSystemTrayChange } diff --git a/ts/windows/settings/preload.ts b/ts/windows/settings/preload.ts index fca73240bc49..4dc509981768 100644 --- a/ts/windows/settings/preload.ts +++ b/ts/windows/settings/preload.ts @@ -20,6 +20,7 @@ function doneRendering() { ipcRenderer.send('settings-done-rendering'); } +const settingMessageAudio = createSetting('audioMessage'); const settingAudioNotification = createSetting('audioNotification'); const settingAutoDownloadUpdate = createSetting('autoDownloadUpdate'); const settingAutoLaunch = createSetting('autoLaunch'); @@ -152,6 +153,7 @@ async function renderPreferences() { hasLinkPreviews, hasMediaCameraPermissions, hasMediaPermissions, + hasMessageAudio, hasNotificationAttention, hasReadReceipts, hasRelayCalls, @@ -193,6 +195,7 @@ async function renderPreferences() { hasLinkPreviews: settingLinkPreview.getValue(), hasMediaCameraPermissions: settingMediaCameraPermissions.getValue(), hasMediaPermissions: settingMediaPermissions.getValue(), + hasMessageAudio: settingMessageAudio.getValue(), hasNotificationAttention: settingNotificationDrawAttention.getValue(), hasReadReceipts: settingReadReceipts.getValue(), hasRelayCalls: settingRelayCalls.getValue(), @@ -253,6 +256,7 @@ async function renderPreferences() { hasLinkPreviews, hasMediaCameraPermissions, hasMediaPermissions, + hasMessageAudio, hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray, hasNotificationAttention, @@ -293,7 +297,6 @@ async function renderPreferences() { shouldShowStoriesSettings, // Limited support features - isAudioNotificationsSupported: Settings.isAudioNotificationSupported(OS), isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(OS), isAutoLaunchSupported: Settings.isAutoLaunchSupported(OS), isHideMenuBarSupported: Settings.isHideMenuBarSupported(OS), @@ -347,6 +350,7 @@ async function renderPreferences() { onMediaCameraPermissionsChange: attachRenderCallback( settingMediaCameraPermissions.setValue ), + onMessageAudioChange: attachRenderCallback(settingMessageAudio.setValue), onMinimizeToAndStartInSystemTrayChange: attachRenderCallback( async (value: boolean) => { await settingSystemTraySetting.setValue(