diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 045ff0e0de..419d5d83c7 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -6938,6 +6938,22 @@ "messageformat": "Calls, mentions, replies", "description": "Summary shown on the 'While muted' row when calls, mentions, and replies all notify while the chat is muted" }, + "icu:Preferences__WhileMuted__description": { + "messageformat": "Choose notifications to show for muted chats.", + "description": "Sub-label of the row in notification settings that opens the global 'While muted' page" + }, + "icu:Preferences__WhileMuted__calls__description": { + "messageformat": "Ring or notify when a call is started in muted chats.", + "description": "On the global 'While muted' settings page, sub-label for the switch that allows call notifications in muted chats" + }, + "icu:Preferences__WhileMuted__mentions__description": { + "messageformat": "Notify when you are mentioned in muted chats.", + "description": "On the global 'While muted' settings page, sub-label for the switch that allows @mention notifications in muted chats" + }, + "icu:Preferences__WhileMuted__replies__description": { + "messageformat": "Notify when someone replies to your message in muted chats.", + "description": "On the global 'While muted' settings page, sub-label for the switch that allows reply notifications in muted chats" + }, "icu:ConversationNotificationsSettings__mentions__label": { "messageformat": "Mentions", "description": "In the conversation notifications settings, this is the label for the mentions option" diff --git a/protos/Backups.proto b/protos/Backups.proto index 79c155549a..8377cfdc07 100644 --- a/protos/Backups.proto +++ b/protos/Backups.proto @@ -138,6 +138,9 @@ message AccountData { bool allowSealedSenderFromAnyone = 30; bool allowAutomaticKeyVerification = 31; bool hasSeenAdminDeleteEducationDialog = 32; + optional bool notifyForCallsIfMuted = 36; // If unset, consider this disabled + optional bool notifyForMentionsIfMuted = 37; // If unset, consider this enabled + optional bool notifyForRepliesIfMuted = 38; // If unset, consider this enabled } message SubscriberData { @@ -1465,4 +1468,4 @@ message ChatFolder { repeated uint64 includedRecipientIds = 7; // generated recipient id of groups, contacts, and/or note to self repeated uint64 excludedRecipientIds = 8; // generated recipient id of groups, contacts, and/or note to self bytes id = 9; // should be 16 bytes -} +} \ No newline at end of file diff --git a/protos/SignalStorage.proto b/protos/SignalStorage.proto index 98b6e2af9c..6c9617472a 100644 --- a/protos/SignalStorage.proto +++ b/protos/SignalStorage.proto @@ -189,7 +189,6 @@ message Payments { } message AccountRecord { - enum PhoneNumberSharingMode { UNKNOWN = 0; EVERYBODY = 1; @@ -313,6 +312,9 @@ message AccountRecord { optional bool releaseNotesChatBlocked = 50; optional bool releaseNotesChatMarkedUnread = 51; optional uint64 releaseNotesChatBlockedAt = 52; // only set if known (>0) + OptionalBool notifyForCallsIfMuted = 56; // If unset, consider this off + OptionalBool notifyForMentionsIfMuted = 57; // If unset, consider this on + OptionalBool notifyForRepliesIfMuted = 58; // If unset, consider this on } message StoryDistributionListRecord { diff --git a/ts/background.preload.ts b/ts/background.preload.ts index b1555f526c..14d7e54eba 100644 --- a/ts/background.preload.ts +++ b/ts/background.preload.ts @@ -1451,6 +1451,21 @@ async function startApp(): Promise { window.ConversationController.repairPinnedConversations(); } + // Existing accounts keep notifying for calls in muted chats; new installs + // leave this unset, which means calls will not ring in muted chats. + if ( + window.isBeforeVersion(lastVersion, '8.29.0-alpha') && + itemStorage.get('notifyForCallsIfMuted') == null + ) { + log.info( + 'Defaulting notifyForCallsIfMuted to true for existing account' + ); + await itemStorage.put('notifyForCallsIfMuted', true); + window.ConversationController.getOurConversation()?.captureChange( + 'notifyForCallsIfMuted' + ); + } + if (!itemStorage.get('avatarsHaveBeenMigrated', false)) { window.ConversationController.migrateAvatarsForNonAcceptedConversations(); } diff --git a/ts/components/Preferences.dom.stories.tsx b/ts/components/Preferences.dom.stories.tsx index 9465325759..aa1689b5b0 100644 --- a/ts/components/Preferences.dom.stories.tsx +++ b/ts/components/Preferences.dom.stories.tsx @@ -515,6 +515,7 @@ export default { me, navTabsCollapsed: false, notificationContent: 'name', + notifyWhileMuted: { calls: false, mentions: true, replies: true }, osName: 'windows', otherTabsUnreadStats: { unreadCount: 0, @@ -626,6 +627,7 @@ export default { onNotificationAttentionChange: action('onNotificationAttentionChange'), onNotificationContentChange: action('onNotificationContentChange'), onNotificationsChange: action('onNotificationsChange'), + onNotifyWhileMutedChange: action('onNotifyWhileMutedChange'), onPreferContactAvatarsChange: action('onPreferContactAvatarsChange'), onReactionNotificationsChange: action('onReactionNotificationsChange'), onReadReceiptsChange: action('onReadReceiptsChange'), @@ -775,6 +777,10 @@ export const Notifications = Template.bind({}); Notifications.args = { settingsLocation: { page: SettingsPage.Notifications }, }; +export const NotificationsWhileMuted = Template.bind({}); +NotificationsWhileMuted.args = { + settingsLocation: { page: SettingsPage.WhileMuted }, +}; export const Privacy = Template.bind({}); Privacy.args = { settingsLocation: { page: SettingsPage.Privacy }, diff --git a/ts/components/Preferences.dom.tsx b/ts/components/Preferences.dom.tsx index 8944149158..0351af71dc 100644 --- a/ts/components/Preferences.dom.tsx +++ b/ts/components/Preferences.dom.tsx @@ -54,6 +54,11 @@ import type { ZoomFactorType, StorageAccessType, } from '../types/StorageKeys.std.ts'; +import type { + NotifyWhileMuted, + NotifyWhileMutedKey, +} from '../util/notifyWhileMuted.std.ts'; +import { getNotifyWhileMutedSummary } from '../util/notifyWhileMuted.std.ts'; import type { ThemeSettingType } from '../util/theme.std.ts'; import type { AnyToast } from '../types/Toast.dom.tsx'; import { ToastType } from '../types/Toast.dom.tsx'; @@ -171,6 +176,7 @@ export type PropsDataType = { settingsLocation: SettingsLocation; lastSyncTime?: number; notificationContent: NotificationSettingType; + notifyWhileMuted: NotifyWhileMuted; osName: 'linux' | 'macos' | 'windows' | undefined; phoneNumber: string | undefined; selectedCamera?: string; @@ -349,6 +355,10 @@ type PropsFunctionType = { onNotificationAttentionChange: CheckboxChangeHandlerType; onNotificationContentChange: SelectChangeHandlerType; onNotificationsChange: CheckboxChangeHandlerType; + onNotifyWhileMutedChange: ( + key: NotifyWhileMutedKey, + value: boolean + ) => unknown; onPinRemindersChange: CheckboxChangeHandlerType; onPreferContactAvatarsChange: CheckboxChangeHandlerType; onReactionNotificationsChange: CheckboxChangeHandlerType; @@ -517,6 +527,7 @@ export function Preferences({ me, navTabsCollapsed, notificationContent, + notifyWhileMuted, onAudioNotificationsChange, onAutoConvertEmojiChange, onAutoDownloadAttachmentChange, @@ -544,6 +555,7 @@ export function Preferences({ onNotificationAttentionChange, onNotificationContentChange, onNotificationsChange, + onNotifyWhileMutedChange, onPinRemindersChange, onPreferContactAvatarsChange, onReactionNotificationsChange, @@ -1546,6 +1558,16 @@ export function Preferences({ }, ]} /> + + setSettingsLocation({ page: SettingsPage.WhileMuted }) + } + /> ); + } else if (settingsLocation.page === SettingsPage.WhileMuted) { + const backButton = ( +