Add reset notification settings action

Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-08-28 19:42:53 +00:00
committed by GitHub
co-authored by trevor-signal
parent 9861e36837
commit 55e9cba5fd
6 changed files with 165 additions and 12 deletions
+16
View File
@@ -8232,6 +8232,22 @@
"messageformat": "App badge",
"description": "Preferences > Notifications > App Badge Section > Title"
},
"icu:Preferences__Notifications__Reset__title": {
"messageformat": "Reset notification settings to default, including custom settings for your chats.",
"description": "Preferences > Notifications > Reset Section > Text of the row that restores the default notification settings"
},
"icu:Preferences__Notifications__Reset__button": {
"messageformat": "Reset",
"description": "Preferences > Notifications > Reset Section > Label of the button that restores the default notification settings"
},
"icu:Preferences__Notifications__Reset__modal--body": {
"messageformat": "Reset all notification settings to default for all chats?",
"description": "Preferences > Notifications > Reset Section > Confirmation Modal > Body. The modal has no visible title, so this is the only text in it."
},
"icu:Preferences__Notifications__Reset__modal--confirm": {
"messageformat": "Reset",
"description": "Preferences > Notifications > Reset Section > Confirmation Modal > Label of the button that confirms restoring the default notification settings"
},
"icu:PlaintextExport--PreferencesRow--Header": {
"messageformat": "Export chat history",
"description": "Shown in the Preferences/Chats page, header for a row"
@@ -632,6 +632,7 @@ export default {
onReactionNotificationsChange: action('onReactionNotificationsChange'),
onReadReceiptsChange: action('onReadReceiptsChange'),
onRelayCallsChange: action('onRelayCallsChange'),
onResetNotificationSettings: action('onResetNotificationSettings'),
onSealedSenderIndicatorsChange: action('onSealedSenderIndicatorsChange'),
onSelectedCameraChange: action('onSelectedCameraChange'),
onSelectedMicrophoneChange: action('onSelectedMicrophoneChange'),
+45
View File
@@ -100,6 +100,7 @@ import type { VisibleRemoteMegaphoneType } from '../types/Megaphone.std.ts';
import { TitlebarDragArea } from './TitlebarDragArea.dom.tsx';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges.preload.ts';
import { Emoji } from '../axo/emoji.std.ts';
import { AxoAlertDialog } from '../axo/AxoAlertDialog.dom.tsx';
import { AxoConfirmDialog } from '../axo/AxoConfirmDialog.dom.tsx';
import moment from 'moment';
import { AxoItem } from '../axo/items/AxoItem.dom.tsx';
@@ -364,6 +365,7 @@ type PropsFunctionType = {
onReactionNotificationsChange: CheckboxChangeHandlerType;
onReadReceiptsChange: CheckboxChangeHandlerType;
onRelayCallsChange: CheckboxChangeHandlerType;
onResetNotificationSettings: () => unknown;
onSealedSenderIndicatorsChange: CheckboxChangeHandlerType;
onSelectedCameraChange: SelectChangeHandlerType<string | undefined>;
onSelectedMicrophoneChange: SelectChangeHandlerType<AudioDevice | undefined>;
@@ -561,6 +563,7 @@ export function Preferences({
onReactionNotificationsChange,
onReadReceiptsChange,
onRelayCallsChange,
onResetNotificationSettings,
onSealedSenderIndicatorsChange,
onSelectedCameraChange,
onSelectedMicrophoneChange,
@@ -658,6 +661,8 @@ export function Preferences({
const [confirmStoriesOff, setConfirmStoriesOff] = useState(false);
const [confirmContentProtection, setConfirmContentProtection] =
useState(false);
const [confirmResetNotifications, setConfirmResetNotifications] =
useState(false);
const [showSyncFailed, setShowSyncFailed] = useState(false);
const [nowSyncing, setNowSyncing] = useState(false);
const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] =
@@ -1608,6 +1613,46 @@ export function Preferences({
}
/>
</List>
<List>
<ItemWithAction
title={i18n('icu:Preferences__Notifications__Reset__title')}
action={
<AxoItem.Action
variant="subtle-destructive"
onClick={() => setConfirmResetNotifications(true)}
>
{i18n('icu:Preferences__Notifications__Reset__button')}
</AxoItem.Action>
}
/>
<AxoAlertDialog.Root
open={confirmResetNotifications}
onOpenChange={setConfirmResetNotifications}
>
<AxoAlertDialog.Content escape="cancel-is-noop">
<AxoAlertDialog.Title screenReaderOnly>
{i18n('icu:Preferences__Notifications__Reset__title')}
</AxoAlertDialog.Title>
<AxoAlertDialog.Body>
<AxoAlertDialog.Description>
{i18n('icu:Preferences__Notifications__Reset__modal--body')}
</AxoAlertDialog.Description>
</AxoAlertDialog.Body>
<AxoAlertDialog.Footer>
<AxoAlertDialog.Cancel />
<AxoAlertDialog.Action
variant="strong-destructive"
onClick={onResetNotificationSettings}
>
{i18n(
'icu:Preferences__Notifications__Reset__modal--confirm'
)}
</AxoAlertDialog.Action>
</AxoAlertDialog.Footer>
</AxoAlertDialog.Content>
</AxoAlertDialog.Root>
</List>
</ListGroup>
);
content = (
+20 -1
View File
@@ -32,7 +32,10 @@ import {
} from '../util/migrations.preload.ts';
import { drop } from '../util/drop.std.ts';
import { isShallowEqual } from '../util/isShallowEqual.std.ts';
import type { NotifyWhileMutedKey } from '../util/notifyWhileMuted.std.ts';
import type {
NotifyWhileMutedFields,
NotifyWhileMutedKey,
} from '../util/notifyWhileMuted.std.ts';
import { NOTIFY_WHILE_MUTED_FIELDS } from '../util/notifyWhileMuted.std.ts';
import { getInitials } from '../util/getInitials.std.ts';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary.std.ts';
@@ -6125,6 +6128,22 @@ export class ConversationModel {
this.captureChange(attributeName);
}
resetNotifyWhileMuted(): void {
const attributeNames = Object.values(NOTIFY_WHILE_MUTED_FIELDS);
if (attributeNames.every(name => this.get(name) === undefined)) {
return;
}
this.set({
notifyForCallsIfMuted: undefined,
notifyForMentionsIfMuted: undefined,
notifyForRepliesIfMuted: undefined,
} satisfies Record<keyof NotifyWhileMutedFields, undefined>);
drop(DataWriter.updateConversation(this.attributes));
this.captureChange('resetNotifyWhileMuted');
}
acknowledgeGroupMemberNameCollisions(
groupNameCollisions: ReadonlyDeep<GroupNameCollisionsWithIdsByTitle>
): void {
+9
View File
@@ -1234,6 +1234,7 @@ export const actions = {
repairOldestMessage,
replaceAvatar,
resetAllChatColors,
resetAllNotifyWhileMuted,
copyMessageText,
retryDeleteForEveryone,
retryMessageSend,
@@ -2403,6 +2404,14 @@ function resetAllChatColors(): ThunkAction<
};
}
function resetAllNotifyWhileMuted(): NoopActionType {
for (const conversation of window.ConversationController.getAll()) {
conversation.resetNotifyWhileMuted();
}
return noopAction('resetAllNotifyWhileMuted');
}
function kickOffAttachmentDownload(
options: Readonly<{ messageId: string }>
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
+74 -11
View File
@@ -129,6 +129,20 @@ import { pinReminderService } from '../../services/pinReminder.preload.ts';
const DEFAULT_NOTIFICATION_SETTING = 'message';
// Defaults for the settings that "Reset notification settings" restores
const NOTIFICATION_SETTING_DEFAULTS = {
'notification-setting': DEFAULT_NOTIFICATION_SETTING,
'call-system-notification': true,
'reaction-notification': true,
'notification-draw-attention': false,
'audio-notification': false,
audioMessage: false,
'badge-count-muted-conversations': false,
notifyForCallsIfMuted: undefined,
notifyForMentionsIfMuted: undefined,
notifyForRepliesIfMuted: undefined,
} as const satisfies Partial<StorageAccessType>;
function renderUpdateDialog(
props: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
): JSX.Element {
@@ -228,8 +242,11 @@ export function SmartPreferences(): JSX.Element | null {
setGlobalDefaultConversationColor,
toggleNavTabsCollapse,
} = useItemsActions();
const { removeCustomColorOnConversations, resetAllChatColors } =
useConversationsActions();
const {
removeCustomColorOnConversations,
resetAllChatColors,
resetAllNotifyWhileMuted: resetAllPerChatNotifyWhileMutedSettings,
} = useConversationsActions();
const { startUpdate } = useUpdatesActions();
const { changeLocation } = useNavActions();
const { showToast, openFileInFolder } = useToastActions();
@@ -720,7 +737,7 @@ export function SmartPreferences(): JSX.Element | null {
const [hasAudioNotifications, onAudioNotificationsChange] = createItemsAccess(
'audio-notification',
false
NOTIFICATION_SETTING_DEFAULTS['audio-notification']
);
const [hasAutoConvertEmoji, onAutoConvertEmojiChange] = createItemsAccess(
'autoConvertEmoji',
@@ -738,16 +755,20 @@ export function SmartPreferences(): JSX.Element | null {
);
const [hasCallNotifications, onCallNotificationsChange] = createItemsAccess(
'call-system-notification',
true
NOTIFICATION_SETTING_DEFAULTS['call-system-notification']
);
const [hasIncomingCallNotifications, onIncomingCallNotificationsChange] =
createItemsAccess('incoming-call-notification', true);
const [hasCallRingtoneNotification, onCallRingtoneNotificationChange] =
createItemsAccess('call-ringtone-notification', true);
const [hasCountMutedConversations, onCountMutedConversationsChange] =
createItemsAccess('badge-count-muted-conversations', false, () => {
window.Whisper.events.emit('updateUnreadCount');
});
createItemsAccess(
'badge-count-muted-conversations',
NOTIFICATION_SETTING_DEFAULTS['badge-count-muted-conversations'],
() => {
window.Whisper.events.emit('updateUnreadCount');
}
);
const [hasHideMenuBar, onHideMenuBarChange] = createItemsAccess(
'hide-menu-bar',
false,
@@ -758,16 +779,22 @@ export function SmartPreferences(): JSX.Element | null {
);
const [hasMessageAudio, onMessageAudioChange] = createItemsAccess(
'audioMessage',
false
NOTIFICATION_SETTING_DEFAULTS.audioMessage
);
const [hasNotificationAttention, onNotificationAttentionChange] =
createItemsAccess('notification-draw-attention', false);
createItemsAccess(
'notification-draw-attention',
NOTIFICATION_SETTING_DEFAULTS['notification-draw-attention']
);
const [hasReactionNotifications, onReactionNotificationsChange] =
createItemsAccess('reaction-notification', true);
createItemsAccess(
'reaction-notification',
NOTIFICATION_SETTING_DEFAULTS['reaction-notification']
);
const [notificationContent, onNotificationContentChange] = createItemsAccess(
'notification-setting',
'message'
NOTIFICATION_SETTING_DEFAULTS['notification-setting']
);
const hasNotifications = notificationContent !== 'off';
const onNotificationsChange = (value: boolean) => {
@@ -788,6 +815,41 @@ export function SmartPreferences(): JSX.Element | null {
account.captureChange(itemKey);
};
const onResetNotificationSettings = () => {
// Reset global settings
onNotificationContentChange(
NOTIFICATION_SETTING_DEFAULTS['notification-setting']
);
onCallNotificationsChange(
NOTIFICATION_SETTING_DEFAULTS['call-system-notification']
);
onReactionNotificationsChange(
NOTIFICATION_SETTING_DEFAULTS['reaction-notification']
);
onNotificationAttentionChange(
NOTIFICATION_SETTING_DEFAULTS['notification-draw-attention']
);
onAudioNotificationsChange(
NOTIFICATION_SETTING_DEFAULTS['audio-notification']
);
onMessageAudioChange(NOTIFICATION_SETTING_DEFAULTS.audioMessage);
onCountMutedConversationsChange(
NOTIFICATION_SETTING_DEFAULTS['badge-count-muted-conversations']
);
const account = window.ConversationController.getOurConversationOrThrow();
for (const itemKey of Object.values(NOTIFY_WHILE_MUTED_FIELDS)) {
if (itemStorage.get(itemKey) === NOTIFICATION_SETTING_DEFAULTS[itemKey]) {
continue;
}
drop(itemStorage.put(itemKey, NOTIFICATION_SETTING_DEFAULTS[itemKey]));
account.captureChange(itemKey);
}
// Reset per-chat settings
resetAllPerChatNotifyWhileMutedSettings();
};
const [hasPinReminders, onPinRemindersChange] = createItemsAccess(
'pinReminders',
true,
@@ -1082,6 +1144,7 @@ export function SmartPreferences(): JSX.Element | null {
onReactionNotificationsChange={onReactionNotificationsChange}
onReadReceiptsChange={onReadReceiptsChange}
onRelayCallsChange={onRelayCallsChange}
onResetNotificationSettings={onResetNotificationSettings}
onSealedSenderIndicatorsChange={onSealedSenderIndicatorsChange}
onSelectedCameraChange={onSelectedCameraChange}
onSelectedMicrophoneChange={onSelectedMicrophoneChange}