Fix muted room show unread indicator (#386)
* Move getNotifType function Signed-off-by: Ajay Bura <ajbura@gmail.com> * Fix bug in getNotiType Signed-off-by: Ajay Bura <ajbura@gmail.com> * Add isMuted prop in room selector Signed-off-by: Ajay Bura <ajbura@gmail.com> * Fix muted room show unread indicator Signed-off-by: Ajay Bura <ajbura@gmail.com> * Fix muted room notification visible in space Signed-off-by: Ajay Bura <ajbura@gmail.com> * Fix space shows muted room notification on load Signed-off-by: Ajay Bura <ajbura@gmail.com> * Toggle room mute when changed from other client Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
parent
92a3a8d6fa
commit
70ffd7ded8
9 changed files with 106 additions and 30 deletions
|
@ -32,28 +32,9 @@ const items = [{
|
||||||
type: cons.notifs.MUTE,
|
type: cons.notifs.MUTE,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
function getNotifType(roomId) {
|
|
||||||
const mx = initMatrix.matrixClient;
|
|
||||||
const pushRule = mx.getRoomPushRule('global', roomId);
|
|
||||||
|
|
||||||
if (typeof pushRule === 'undefined') {
|
|
||||||
const overridePushRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
|
|
||||||
if (typeof overridePushRules === 'undefined') return 0;
|
|
||||||
|
|
||||||
const isMuteOverride = overridePushRules.find((rule) => (
|
|
||||||
rule.rule_id === roomId
|
|
||||||
&& rule.actions[0] === 'dont_notify'
|
|
||||||
&& rule.conditions[0].kind === 'event_match'
|
|
||||||
));
|
|
||||||
|
|
||||||
return isMuteOverride ? cons.notifs.MUTE : cons.notifs.DEFAULT;
|
|
||||||
}
|
|
||||||
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
|
|
||||||
return cons.notifs.MENTIONS_AND_KEYWORDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRoomNotifType(roomId, newType) {
|
function setRoomNotifType(roomId, newType) {
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
|
const { notifications } = initMatrix;
|
||||||
const roomPushRule = mx.getRoomPushRule('global', roomId);
|
const roomPushRule = mx.getRoomPushRule('global', roomId);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
|
@ -76,7 +57,7 @@ function setRoomNotifType(roomId, newType) {
|
||||||
return promises;
|
return promises;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldState = getNotifType(roomId);
|
const oldState = notifications.getNotiType(roomId);
|
||||||
if (oldState === cons.notifs.MUTE) {
|
if (oldState === cons.notifs.MUTE) {
|
||||||
promises.push(mx.deletePushRule('global', 'override', roomId));
|
promises.push(mx.deletePushRule('global', 'override', roomId));
|
||||||
}
|
}
|
||||||
|
@ -115,8 +96,9 @@ function setRoomNotifType(roomId, newType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function useNotifications(roomId) {
|
function useNotifications(roomId) {
|
||||||
const [activeType, setActiveType] = useState(getNotifType(roomId));
|
const { notifications } = initMatrix;
|
||||||
useEffect(() => setActiveType(getNotifType(roomId)), [roomId]);
|
const [activeType, setActiveType] = useState(notifications.getNotiType(roomId));
|
||||||
|
useEffect(() => setActiveType(notifications.getNotiType(roomId)), [roomId]);
|
||||||
|
|
||||||
const setNotification = useCallback((item) => {
|
const setNotification = useCallback((item) => {
|
||||||
if (item.type === activeType.type) return;
|
if (item.type === activeType.type) return;
|
||||||
|
|
|
@ -11,13 +11,16 @@ import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
||||||
import { blurOnBubbling } from '../../atoms/button/script';
|
import { blurOnBubbling } from '../../atoms/button/script';
|
||||||
|
|
||||||
function RoomSelectorWrapper({
|
function RoomSelectorWrapper({
|
||||||
isSelected, isUnread, onClick,
|
isSelected, isMuted, isUnread, onClick,
|
||||||
content, options, onContextMenu,
|
content, options, onContextMenu,
|
||||||
}) {
|
}) {
|
||||||
let myClass = isUnread ? ' room-selector--unread' : '';
|
const classes = ['room-selector'];
|
||||||
myClass += isSelected ? ' room-selector--selected' : '';
|
if (isMuted) classes.push('room-selector--muted');
|
||||||
|
if (isUnread) classes.push('room-selector--unread');
|
||||||
|
if (isSelected) classes.push('room-selector--selected');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`room-selector${myClass}`}>
|
<div className={classes.join(' ')}>
|
||||||
<button
|
<button
|
||||||
className="room-selector__content"
|
className="room-selector__content"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -32,11 +35,13 @@ function RoomSelectorWrapper({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
RoomSelectorWrapper.defaultProps = {
|
RoomSelectorWrapper.defaultProps = {
|
||||||
|
isMuted: false,
|
||||||
options: null,
|
options: null,
|
||||||
onContextMenu: null,
|
onContextMenu: null,
|
||||||
};
|
};
|
||||||
RoomSelectorWrapper.propTypes = {
|
RoomSelectorWrapper.propTypes = {
|
||||||
isSelected: PropTypes.bool.isRequired,
|
isSelected: PropTypes.bool.isRequired,
|
||||||
|
isMuted: PropTypes.bool,
|
||||||
isUnread: PropTypes.bool.isRequired,
|
isUnread: PropTypes.bool.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
content: PropTypes.node.isRequired,
|
content: PropTypes.node.isRequired,
|
||||||
|
@ -46,12 +51,13 @@ RoomSelectorWrapper.propTypes = {
|
||||||
|
|
||||||
function RoomSelector({
|
function RoomSelector({
|
||||||
name, parentName, roomId, imageSrc, iconSrc,
|
name, parentName, roomId, imageSrc, iconSrc,
|
||||||
isSelected, isUnread, notificationCount, isAlert,
|
isSelected, isMuted, isUnread, notificationCount, isAlert,
|
||||||
options, onClick, onContextMenu,
|
options, onClick, onContextMenu,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<RoomSelectorWrapper
|
<RoomSelectorWrapper
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
isMuted={isMuted}
|
||||||
isUnread={isUnread}
|
isUnread={isUnread}
|
||||||
content={(
|
content={(
|
||||||
<>
|
<>
|
||||||
|
@ -91,6 +97,7 @@ RoomSelector.defaultProps = {
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
imageSrc: null,
|
imageSrc: null,
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
|
isMuted: false,
|
||||||
options: null,
|
options: null,
|
||||||
onContextMenu: null,
|
onContextMenu: null,
|
||||||
};
|
};
|
||||||
|
@ -101,6 +108,7 @@ RoomSelector.propTypes = {
|
||||||
imageSrc: PropTypes.string,
|
imageSrc: PropTypes.string,
|
||||||
iconSrc: PropTypes.string,
|
iconSrc: PropTypes.string,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
|
isMuted: PropTypes.bool,
|
||||||
isUnread: PropTypes.bool.isRequired,
|
isUnread: PropTypes.bool.isRequired,
|
||||||
notificationCount: PropTypes.oneOfType([
|
notificationCount: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
border-radius: var(--bo-radius);
|
border-radius: var(--bo-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&--muted {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
&--unread {
|
&--unread {
|
||||||
.room-selector__content > .text {
|
.room-selector__content > .text {
|
||||||
color: var(--tc-surface-high);
|
color: var(--tc-surface-high);
|
||||||
|
|
|
@ -33,9 +33,11 @@ function Directs() {
|
||||||
|
|
||||||
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
|
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||||
return () => {
|
return () => {
|
||||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
|
notifications.removeListener(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -62,9 +62,11 @@ function Home({ spaceId }) {
|
||||||
|
|
||||||
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
|
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||||
return () => {
|
return () => {
|
||||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||||
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||||
|
notifications.removeListener(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
|
import cons from '../../../client/state/cons';
|
||||||
import navigation from '../../../client/state/navigation';
|
import navigation from '../../../client/state/navigation';
|
||||||
import { openReusableContextMenu } from '../../../client/action/navigation';
|
import { openReusableContextMenu } from '../../../client/action/navigation';
|
||||||
import { getEventCords, abbreviateNumber } from '../../../util/common';
|
import { getEventCords, abbreviateNumber } from '../../../util/common';
|
||||||
|
@ -23,9 +24,12 @@ function Selector({
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
const noti = initMatrix.notifications;
|
const noti = initMatrix.notifications;
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
|
|
||||||
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||||
|
|
||||||
|
const isMuted = noti.getNotiType(roomId) === cons.notifs.MUTE;
|
||||||
|
|
||||||
const [, forceUpdate] = useForceUpdate();
|
const [, forceUpdate] = useForceUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -56,7 +60,8 @@ function Selector({
|
||||||
imageSrc={isDM ? imageSrc : null}
|
imageSrc={isDM ? imageSrc : null}
|
||||||
iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())}
|
iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())}
|
||||||
isSelected={navigation.selectedRoomId === roomId}
|
isSelected={navigation.selectedRoomId === roomId}
|
||||||
isUnread={noti.hasNoti(roomId)}
|
isMuted={isMuted}
|
||||||
|
isUnread={!isMuted && noti.hasNoti(roomId)}
|
||||||
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
|
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
|
||||||
isAlert={noti.getHighlightNoti(roomId) !== 0}
|
isAlert={noti.getHighlightNoti(roomId) !== 0}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|
|
@ -17,6 +17,17 @@ function isNotifEvent(mEvent) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMutedRule(rule) {
|
||||||
|
return rule.actions[0] === 'dont_notify' && rule.conditions[0].kind === 'event_match';
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMutedRule(overrideRules, roomId) {
|
||||||
|
return overrideRules.find((rule) => (
|
||||||
|
rule.rule_id === roomId
|
||||||
|
&& isMutedRule(rule)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
class Notifications extends EventEmitter {
|
class Notifications extends EventEmitter {
|
||||||
constructor(roomList) {
|
constructor(roomList) {
|
||||||
super();
|
super();
|
||||||
|
@ -39,7 +50,9 @@ class Notifications extends EventEmitter {
|
||||||
_initNoti() {
|
_initNoti() {
|
||||||
const addNoti = (roomId) => {
|
const addNoti = (roomId) => {
|
||||||
const room = this.matrixClient.getRoom(roomId);
|
const room = this.matrixClient.getRoom(roomId);
|
||||||
|
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return;
|
||||||
if (this.doesRoomHaveUnread(room) === false) return;
|
if (this.doesRoomHaveUnread(room) === false) return;
|
||||||
|
|
||||||
const total = room.getUnreadNotificationCount('total');
|
const total = room.getUnreadNotificationCount('total');
|
||||||
const highlight = room.getUnreadNotificationCount('highlight');
|
const highlight = room.getUnreadNotificationCount('highlight');
|
||||||
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||||
|
@ -65,6 +78,22 @@ class Notifications extends EventEmitter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNotiType(roomId) {
|
||||||
|
const mx = this.matrixClient;
|
||||||
|
const pushRule = mx.getRoomPushRule('global', roomId);
|
||||||
|
|
||||||
|
if (pushRule === undefined) {
|
||||||
|
const overrideRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
|
||||||
|
if (overrideRules === undefined) return cons.notifs.DEFAULT;
|
||||||
|
|
||||||
|
const isMuted = findMutedRule(overrideRules, roomId);
|
||||||
|
|
||||||
|
return isMuted ? cons.notifs.MUTE : cons.notifs.DEFAULT;
|
||||||
|
}
|
||||||
|
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
|
||||||
|
return cons.notifs.MENTIONS_AND_KEYWORDS;
|
||||||
|
}
|
||||||
|
|
||||||
getNoti(roomId) {
|
getNoti(roomId) {
|
||||||
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
|
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
|
||||||
}
|
}
|
||||||
|
@ -195,6 +224,7 @@ class Notifications extends EventEmitter {
|
||||||
this.matrixClient.on('Room.timeline', (mEvent, room) => {
|
this.matrixClient.on('Room.timeline', (mEvent, room) => {
|
||||||
if (room.isSpaceRoom()) return;
|
if (room.isSpaceRoom()) return;
|
||||||
if (!isNotifEvent(mEvent)) return;
|
if (!isNotifEvent(mEvent)) return;
|
||||||
|
|
||||||
const liveEvents = room.getLiveTimeline().getEvents();
|
const liveEvents = room.getLiveTimeline().getEvents();
|
||||||
|
|
||||||
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
|
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
|
||||||
|
@ -204,6 +234,11 @@ class Notifications extends EventEmitter {
|
||||||
const total = room.getUnreadNotificationCount('total');
|
const total = room.getUnreadNotificationCount('total');
|
||||||
const highlight = room.getUnreadNotificationCount('highlight');
|
const highlight = room.getUnreadNotificationCount('highlight');
|
||||||
|
|
||||||
|
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) {
|
||||||
|
this.deleteNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||||
|
|
||||||
if (this.matrixClient.getSyncState() === 'SYNCING') {
|
if (this.matrixClient.getSyncState() === 'SYNCING') {
|
||||||
|
@ -211,6 +246,43 @@ class Notifications extends EventEmitter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.matrixClient.on('accountData', (mEvent, oldMEvent) => {
|
||||||
|
if (mEvent.getType() === 'm.push_rules') {
|
||||||
|
const override = mEvent?.getContent()?.global?.override;
|
||||||
|
const oldOverride = oldMEvent?.getContent()?.global?.override;
|
||||||
|
if (!override || !oldOverride) return;
|
||||||
|
|
||||||
|
const isMuteToggled = (rule, otherOverride) => {
|
||||||
|
const roomId = rule.rule_id;
|
||||||
|
const room = this.matrixClient.getRoom(roomId);
|
||||||
|
if (room === null) return false;
|
||||||
|
if (room.isSpaceRoom()) return false;
|
||||||
|
|
||||||
|
const isMuted = isMutedRule(rule);
|
||||||
|
if (!isMuted) return false;
|
||||||
|
const isOtherMuted = findMutedRule(otherOverride, roomId);
|
||||||
|
if (isOtherMuted) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride));
|
||||||
|
const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override));
|
||||||
|
|
||||||
|
mutedRules.forEach((rule) => {
|
||||||
|
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, true);
|
||||||
|
this.deleteNoti(rule.rule_id);
|
||||||
|
});
|
||||||
|
unMutedRules.forEach((rule) => {
|
||||||
|
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, false);
|
||||||
|
const room = this.matrixClient.getRoom(rule.rule_id);
|
||||||
|
if (!this.doesRoomHaveUnread(room)) return;
|
||||||
|
const total = room.getUnreadNotificationCount('total');
|
||||||
|
const highlight = room.getUnreadNotificationCount('highlight');
|
||||||
|
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
||||||
if (mEvent.getType() === 'm.receipt') {
|
if (mEvent.getType() === 'm.receipt') {
|
||||||
if (room.isSpaceRoom()) return;
|
if (room.isSpaceRoom()) return;
|
||||||
|
|
|
@ -107,6 +107,7 @@ const cons = {
|
||||||
notifications: {
|
notifications: {
|
||||||
NOTI_CHANGED: 'NOTI_CHANGED',
|
NOTI_CHANGED: 'NOTI_CHANGED',
|
||||||
FULL_READ: 'FULL_READ',
|
FULL_READ: 'FULL_READ',
|
||||||
|
MUTE_TOGGLED: 'MUTE_TOGGLED',
|
||||||
},
|
},
|
||||||
roomTimeline: {
|
roomTimeline: {
|
||||||
READY: 'READY',
|
READY: 'READY',
|
||||||
|
|
Loading…
Reference in a new issue