Make clear notifications work with threads (#9575)
This commit is contained in:
parent
e66027cd0c
commit
c10339ad68
5 changed files with 241 additions and 237 deletions
|
@ -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.
|
||||
|
@ -112,6 +112,8 @@ interface IState {
|
|||
desktopNotifications: boolean;
|
||||
desktopShowBody: boolean;
|
||||
audioNotifications: boolean;
|
||||
|
||||
clearingNotifications: boolean;
|
||||
}
|
||||
|
||||
export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
|
@ -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 = [
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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[]) {
|
||||
|
@ -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")}
|
||||
|
@ -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 })}
|
||||
|
@ -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)}
|
||||
|
@ -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')}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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'>
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
@ -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…
Add table
Add a link
Reference in a new issue