Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit ab52d31

Browse files
GermainAmy Walker
authored andcommitted
Make clear notifications work with threads (#9575)
1 parent d78ff4a commit ab52d31

File tree

5 files changed

+241
-237
lines changed

5 files changed

+241
-237
lines changed

src/components/views/settings/Notifications.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import AccessibleButton from "../elements/AccessibleButton";
4242
import TagComposer from "../elements/TagComposer";
4343
import { objectClone } from "../../../utils/objects";
4444
import { arrayDiff } from "../../../utils/arrays";
45-
import { getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
45+
import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
4646

4747
// TODO: this "view" component still has far too much application logic in it,
4848
// which should be factored out to other files.
@@ -112,6 +112,8 @@ interface IState {
112112
desktopNotifications: boolean;
113113
desktopShowBody: boolean;
114114
audioNotifications: boolean;
115+
116+
clearingNotifications: boolean;
115117
}
116118

117119
export default class Notifications extends React.PureComponent<IProps, IState> {
@@ -126,6 +128,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
126128
desktopNotifications: SettingsStore.getValue("notificationsEnabled"),
127129
desktopShowBody: SettingsStore.getValue("notificationBodyEnabled"),
128130
audioNotifications: SettingsStore.getValue("audioNotificationsEnabled"),
131+
clearingNotifications: false,
129132
};
130133

131134
this.settingWatchers = [
@@ -177,8 +180,12 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
177180
])).reduce((p, c) => Object.assign(c, p), {});
178181

179182
this.setState<keyof Omit<IState,
180-
"deviceNotificationsEnabled" | "desktopNotifications" | "desktopShowBody" | "audioNotifications">
181-
>({
183+
"deviceNotificationsEnabled" |
184+
"desktopNotifications" |
185+
"desktopShowBody" |
186+
"audioNotifications" |
187+
"clearingNotifications"
188+
>>({
182189
...newState,
183190
phase: Phase.Ready,
184191
});
@@ -433,17 +440,14 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
433440
}
434441
};
435442

436-
private onClearNotificationsClicked = () => {
437-
const client = MatrixClientPeg.get();
438-
client.getRooms().forEach(r => {
439-
if (r.getUnreadNotificationCount() > 0) {
440-
const events = r.getLiveTimeline().getEvents();
441-
if (events.length) {
442-
// noinspection JSIgnoredPromiseFromCall
443-
client.sendReadReceipt(events[events.length - 1]);
444-
}
445-
}
446-
});
443+
private onClearNotificationsClicked = async (): Promise<void> => {
444+
try {
445+
this.setState({ clearingNotifications: true });
446+
const client = MatrixClientPeg.get();
447+
await clearAllNotifications(client);
448+
} finally {
449+
this.setState({ clearingNotifications: false });
450+
}
447451
};
448452

449453
private async setKeywords(keywords: string[], originalRules: IAnnotatedPushRule[]) {
@@ -531,7 +535,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
531535

532536
private renderTopSection() {
533537
const masterSwitch = <LabelledToggleSwitch
534-
data-test-id='notif-master-switch'
538+
data-testid='notif-master-switch'
535539
value={!this.isInhibited}
536540
label={_t("Enable notifications for this account")}
537541
caption={_t("Turn off to disable notifications on all your devices and sessions")}
@@ -546,7 +550,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
546550

547551
const emailSwitches = (this.state.threepids || []).filter(t => t.medium === ThreepidMedium.Email)
548552
.map(e => <LabelledToggleSwitch
549-
data-test-id='notif-email-switch'
553+
data-testid='notif-email-switch'
550554
key={e.address}
551555
value={this.state.pushers.some(p => p.kind === "email" && p.pushkey === e.address)}
552556
label={_t("Enable email notifications for %(email)s", { email: e.address })}
@@ -558,7 +562,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
558562
{ masterSwitch }
559563

560564
<LabelledToggleSwitch
561-
data-test-id='notif-device-switch'
565+
data-testid='notif-device-switch'
562566
value={this.state.deviceNotificationsEnabled}
563567
label={_t("Enable notifications for this device")}
564568
onChange={checked => this.updateDeviceNotifications(checked)}
@@ -567,21 +571,21 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
567571

568572
{ this.state.deviceNotificationsEnabled && (<>
569573
<LabelledToggleSwitch
570-
data-test-id='notif-setting-notificationsEnabled'
574+
data-testid='notif-setting-notificationsEnabled'
571575
value={this.state.desktopNotifications}
572576
onChange={this.onDesktopNotificationsChanged}
573577
label={_t('Enable desktop notifications for this session')}
574578
disabled={this.state.phase === Phase.Persisting}
575579
/>
576580
<LabelledToggleSwitch
577-
data-test-id='notif-setting-notificationBodyEnabled'
581+
data-testid='notif-setting-notificationBodyEnabled'
578582
value={this.state.desktopShowBody}
579583
onChange={this.onDesktopShowBodyChanged}
580584
label={_t('Show message in desktop notification')}
581585
disabled={this.state.phase === Phase.Persisting}
582586
/>
583587
<LabelledToggleSwitch
584-
data-test-id='notif-setting-audioNotificationsEnabled'
588+
data-testid='notif-setting-audioNotificationsEnabled'
585589
value={this.state.audioNotifications}
586590
onChange={this.onAudioNotificationsChanged}
587591
label={_t('Enable audible notifications for this session')}
@@ -605,8 +609,10 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
605609
) {
606610
clearNotifsButton = <AccessibleButton
607611
onClick={this.onClearNotificationsClicked}
612+
disabled={this.state.clearingNotifications}
608613
kind='danger'
609614
className='mx_UserNotifSettings_clearNotifsButton'
615+
data-testid="clear-notifications"
610616
>{ _t("Clear notifications") }</AccessibleButton>;
611617
}
612618

@@ -653,7 +659,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
653659
const fieldsetRows = this.state.vectorPushRules[category].map(r =>
654660
<fieldset
655661
key={category + r.ruleId}
656-
data-test-id={category + r.ruleId}
662+
data-testid={category + r.ruleId}
657663
className='mx_UserNotifSettings_gridRowContainer'
658664
>
659665
<legend className='mx_UserNotifSettings_gridRowLabel'>{ r.description }</legend>
@@ -678,7 +684,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
678684
}
679685

680686
return <>
681-
<div data-test-id={`notif-section-${category}`} className='mx_UserNotifSettings_grid'>
687+
<div data-testid={`notif-section-${category}`} className='mx_UserNotifSettings_grid'>
682688
<span className='mx_UserNotifSettings_gridRowLabel mx_UserNotifSettings_gridRowHeading'>{ sectionName }</span>
683689
<span className='mx_UserNotifSettings_gridColumnLabel'>{ VectorStateToLabel[VectorState.Off] }</span>
684690
<span className='mx_UserNotifSettings_gridColumnLabel'>{ VectorStateToLabel[VectorState.On] }</span>
@@ -715,7 +721,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
715721
// Ends up default centered
716722
return <Spinner />;
717723
} else if (this.state.phase === Phase.Error) {
718-
return <p data-test-id='error-message'>{ _t("There was an error loading your notification settings.") }</p>;
724+
return <p data-testid='error-message'>{ _t("There was an error loading your notification settings.") }</p>;
719725
}
720726

721727
return <div className='mx_UserNotifSettings'>

src/utils/notifications.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
import { MatrixClient } from "matrix-js-sdk/src/client";
1818
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX } from "matrix-js-sdk/src/@types/event";
1919
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
20+
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
21+
import { Room } from "matrix-js-sdk/src/models/room";
2022

2123
import SettingsStore from "../settings/SettingsStore";
2224

@@ -56,3 +58,31 @@ export function localNotificationsAreSilenced(cli: MatrixClient): boolean {
5658
const event = cli.getAccountData(eventType);
5759
return event?.getContent<LocalNotificationSettings>()?.is_silenced ?? false;
5860
}
61+
62+
export function clearAllNotifications(client: MatrixClient): Promise<Array<{}>> {
63+
const receiptPromises = client.getRooms().reduce((promises, room: Room) => {
64+
if (room.getUnreadNotificationCount() > 0) {
65+
const roomEvents = room.getLiveTimeline().getEvents();
66+
const lastThreadEvents = room.lastThread?.events;
67+
68+
const lastRoomEvent = roomEvents?.[roomEvents?.length - 1];
69+
const lastThreadLastEvent = lastThreadEvents?.[lastThreadEvents?.length - 1];
70+
71+
const lastEvent = (lastRoomEvent?.getTs() ?? 0) > (lastThreadLastEvent?.getTs() ?? 0)
72+
? lastRoomEvent
73+
: lastThreadLastEvent;
74+
75+
if (lastEvent) {
76+
const receiptType = SettingsStore.getValue("sendReadReceipts", room.roomId)
77+
? ReceiptType.Read
78+
: ReceiptType.ReadPrivate;
79+
const promise = client.sendReadReceipt(lastEvent, receiptType, true);
80+
promises.push(promise);
81+
}
82+
}
83+
84+
return promises;
85+
}, []);
86+
87+
return Promise.all(receiptPromises);
88+
}

0 commit comments

Comments
 (0)