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

Make clear notifications work with threads #9575

Merged
merged 4 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 29 additions & 23 deletions src/components/views/settings/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import AccessibleButton from "../elements/AccessibleButton";
import TagComposer from "../elements/TagComposer";
import { objectClone } from "../../../utils/objects";
import { arrayDiff } from "../../../utils/arrays";
import { getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications";

// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
Expand Down Expand Up @@ -112,6 +112,8 @@ interface IState {
desktopNotifications: boolean;
desktopShowBody: boolean;
audioNotifications: boolean;

clearingNotifications: boolean;
}

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

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

this.setState<keyof Omit<IState,
"deviceNotificationsEnabled" | "desktopNotifications" | "desktopShowBody" | "audioNotifications">
>({
"deviceNotificationsEnabled" |
"desktopNotifications" |
"desktopShowBody" |
"audioNotifications" |
"clearingNotifications"
>>({
...newState,
phase: Phase.Ready,
});
Expand Down Expand Up @@ -433,17 +440,14 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
}
};

private onClearNotificationsClicked = () => {
const client = MatrixClientPeg.get();
client.getRooms().forEach(r => {
if (r.getUnreadNotificationCount() > 0) {
const events = r.getLiveTimeline().getEvents();
if (events.length) {
// noinspection JSIgnoredPromiseFromCall
client.sendReadReceipt(events[events.length - 1]);
}
}
});
private onClearNotificationsClicked = async (): Promise<void> => {
try {
this.setState({ clearingNotifications: true });
const client = MatrixClientPeg.get();
await clearAllNotifications(client);
} finally {
this.setState({ clearingNotifications: false });
}
};

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

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

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

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

{ this.state.deviceNotificationsEnabled && (<>
<LabelledToggleSwitch
data-test-id='notif-setting-notificationsEnabled'
data-testid='notif-setting-notificationsEnabled'
value={this.state.desktopNotifications}
onChange={this.onDesktopNotificationsChanged}
label={_t('Enable desktop notifications for this session')}
disabled={this.state.phase === Phase.Persisting}
/>
<LabelledToggleSwitch
data-test-id='notif-setting-notificationBodyEnabled'
data-testid='notif-setting-notificationBodyEnabled'
value={this.state.desktopShowBody}
onChange={this.onDesktopShowBodyChanged}
label={_t('Show message in desktop notification')}
disabled={this.state.phase === Phase.Persisting}
/>
<LabelledToggleSwitch
data-test-id='notif-setting-audioNotificationsEnabled'
data-testid='notif-setting-audioNotificationsEnabled'
value={this.state.audioNotifications}
onChange={this.onAudioNotificationsChanged}
label={_t('Enable audible notifications for this session')}
Expand All @@ -605,8 +609,10 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
) {
clearNotifsButton = <AccessibleButton
onClick={this.onClearNotificationsClicked}
disabled={this.state.clearingNotifications}
kind='danger'
className='mx_UserNotifSettings_clearNotifsButton'
data-testid="clear-notifications"
>{ _t("Clear notifications") }</AccessibleButton>;
}

Expand Down Expand Up @@ -653,7 +659,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
const fieldsetRows = this.state.vectorPushRules[category].map(r =>
<fieldset
key={category + r.ruleId}
data-test-id={category + r.ruleId}
data-testid={category + r.ruleId}
className='mx_UserNotifSettings_gridRowContainer'
>
<legend className='mx_UserNotifSettings_gridRowLabel'>{ r.description }</legend>
Expand All @@ -678,7 +684,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
}

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

return <div className='mx_UserNotifSettings'>
Expand Down
30 changes: 30 additions & 0 deletions src/utils/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX } from "matrix-js-sdk/src/@types/event";
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
import { Room } from "matrix-js-sdk/src/models/room";

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

Expand Down Expand Up @@ -56,3 +58,31 @@ export function localNotificationsAreSilenced(cli: MatrixClient): boolean {
const event = cli.getAccountData(eventType);
return event?.getContent<LocalNotificationSettings>()?.is_silenced ?? false;
}

export function clearAllNotifications(client: MatrixClient): Promise<Array<{}>> {
const receiptPromises = client.getRooms().reduce((promises, room: Room) => {
if (room.getUnreadNotificationCount() > 0) {
const roomEvents = room.getLiveTimeline().getEvents();
const lastThreadEvents = room.lastThread?.events;

const lastRoomEvent = roomEvents?.[roomEvents?.length - 1];
const lastThreadLastEvent = lastThreadEvents?.[lastThreadEvents?.length - 1];

const lastEvent = (lastRoomEvent?.getTs() ?? 0) > (lastThreadLastEvent?.getTs() ?? 0)
? lastRoomEvent
: lastThreadLastEvent;

if (lastEvent) {
const receiptType = SettingsStore.getValue("sendReadReceipts", room.roomId)
? ReceiptType.Read
: ReceiptType.ReadPrivate;
const promise = client.sendReadReceipt(lastEvent, receiptType, true);
promises.push(promise);
}
}

return promises;
}, []);

return Promise.all(receiptPromises);
}
Loading