Add option to change room notification settings ()

This commit is contained in:
Ajay Bura 2025-03-20 20:27:00 +11:00 committed by GitHub
parent 074a5e855d
commit 71bfc96b5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 409 additions and 164 deletions

View file

@ -0,0 +1,120 @@
import { Box, config, Icon, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
import React, { MouseEventHandler, ReactNode, useMemo, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import { stopPropagation } from '../utils/keyboard';
import {
getRoomNotificationModeIcon,
RoomNotificationMode,
useSetRoomNotificationPreference,
} from '../hooks/useRoomsNotificationPreferences';
import { AsyncStatus } from '../hooks/useAsyncCallback';
const useRoomNotificationModes = (): RoomNotificationMode[] =>
useMemo(
() => [
RoomNotificationMode.Unset,
RoomNotificationMode.AllMessages,
RoomNotificationMode.SpecialMessages,
RoomNotificationMode.Mute,
],
[]
);
const useRoomNotificationModeStr = (): Record<RoomNotificationMode, string> =>
useMemo(
() => ({
[RoomNotificationMode.Unset]: 'Default',
[RoomNotificationMode.AllMessages]: 'All Messages',
[RoomNotificationMode.SpecialMessages]: 'Mention & Keywords',
[RoomNotificationMode.Mute]: 'Mute',
}),
[]
);
type NotificationModeSwitcherProps = {
roomId: string;
value?: RoomNotificationMode;
children: (
handleOpen: MouseEventHandler<HTMLButtonElement>,
opened: boolean,
changing: boolean
) => ReactNode;
};
export function RoomNotificationModeSwitcher({
roomId,
value = RoomNotificationMode.Unset,
children,
}: NotificationModeSwitcherProps) {
const modes = useRoomNotificationModes();
const modeToStr = useRoomNotificationModeStr();
const { modeState, setMode } = useSetRoomNotificationPreference(roomId);
const changing = modeState.status === AsyncStatus.Loading;
const [menuCords, setMenuCords] = useState<RectCords>();
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(evt.currentTarget.getBoundingClientRect());
};
const handleClose = () => {
setMenuCords(undefined);
};
const handleSelect = (mode: RoomNotificationMode) => {
if (changing) return;
setMode(mode, value);
handleClose();
};
return (
<PopOut
anchor={menuCords}
offset={5}
position="Right"
align="Start"
content={
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: handleClose,
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) =>
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
escapeDeactivates: stopPropagation,
}}
>
<Menu>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
{modes.map((mode) => (
<MenuItem
key={mode}
size="300"
variant="Surface"
aria-pressed={mode === value}
radii="300"
disabled={changing}
onClick={() => handleSelect(mode)}
before={
<Icon
size="100"
src={getRoomNotificationModeIcon(mode)}
filled={mode === value}
/>
}
>
<Text size="T300">
{mode === value ? <b>{modeToStr[mode]}</b> : modeToStr[mode]}
</Text>
</MenuItem>
))}
</Box>
</Menu>
</FocusTrap>
}
>
{children(handleOpenMenu, !!menuCords, changing)}
</PopOut>
);
}

View file

@ -15,6 +15,7 @@ import {
Line,
RectCords,
Badge,
Spinner,
} from 'folds';
import { useFocusWithin, useHover } from 'react-aria';
import FocusTrap from 'focus-trap-react';
@ -43,13 +44,19 @@ import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { useOpenRoomSettings } from '../../state/hooks/roomSettings';
import { useSpaceOptionally } from '../../hooks/useSpace';
import {
getRoomNotificationModeIcon,
RoomNotificationMode,
} from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
type RoomNavItemMenuProps = {
room: Room;
requestClose: () => void;
notificationMode?: RoomNotificationMode;
};
const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
({ room, requestClose }, ref) => {
({ room, requestClose, notificationMode }, ref) => {
const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
@ -95,6 +102,27 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
Mark as Read
</Text>
</MenuItem>
<RoomNotificationModeSwitcher roomId={room.roomId} value={notificationMode}>
{(handleOpen, opened, changing) => (
<MenuItem
size="300"
after={
changing ? (
<Spinner size="100" variant="Secondary" />
) : (
<Icon size="100" src={getRoomNotificationModeIcon(notificationMode)} />
)
}
radii="300"
aria-pressed={opened}
onClick={handleOpen}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Notifications
</Text>
</MenuItem>
)}
</RoomNotificationModeSwitcher>
</Box>
<Line variant="Surface" size="300" />
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
@ -170,7 +198,7 @@ type RoomNavItemProps = {
room: Room;
selected: boolean;
linkPath: string;
muted?: boolean;
notificationMode?: RoomNotificationMode;
showAvatar?: boolean;
direct?: boolean;
};
@ -179,7 +207,7 @@ export function RoomNavItem({
selected,
showAvatar,
direct,
muted,
notificationMode,
linkPath,
}: RoomNavItemProps) {
const mx = useMatrixClient();
@ -263,7 +291,9 @@ export function RoomNavItem({
<UnreadBadge highlight={unread.highlight > 0} count={unread.total} />
</UnreadBadgeCenter>
)}
{muted && !optionsVisible && <Icon size="50" src={Icons.BellMute} />}
{!optionsVisible && notificationMode !== RoomNotificationMode.Unset && (
<Icon size="50" src={getRoomNotificationModeIcon(notificationMode)} />
)}
</Box>
</NavItemContent>
</NavLink>
@ -287,7 +317,11 @@ export function RoomNavItem({
escapeDeactivates: stopPropagation,
}}
>
<RoomNavItemMenu room={room} requestClose={() => setMenuAnchor(undefined)} />
<RoomNavItemMenu
room={room}
requestClose={() => setMenuAnchor(undefined)}
notificationMode={notificationMode}
/>
</FocusTrap>
}
>

View file

@ -7,6 +7,19 @@ export enum NotificationMode {
NotifyLoud = 'NotifyLoud',
}
export const getNotificationMode = (actions: PushRuleAction[]): NotificationMode => {
const soundTweak = actions.find(
(action) => typeof action === 'object' && action.set_tweak === TweakName.Sound
);
const notify = actions.find(
(action) => typeof action === 'string' && action === PushRuleActionName.Notify
);
if (notify && soundTweak) return NotificationMode.NotifyLoud;
if (notify) return NotificationMode.Notify;
return NotificationMode.OFF;
};
export type NotificationModeOptions = {
soundValue?: string;
highlight?: boolean;
@ -49,18 +62,7 @@ export const useNotificationModeActions = (
};
export const useNotificationActionsMode = (actions: PushRuleAction[]): NotificationMode => {
const mode: NotificationMode = useMemo(() => {
const soundTweak = actions.find(
(action) => typeof action === 'object' && action.set_tweak === TweakName.Sound
);
const notify = actions.find(
(action) => typeof action === 'string' && action === PushRuleActionName.Notify
);
if (notify && soundTweak) return NotificationMode.NotifyLoud;
if (notify) return NotificationMode.Notify;
return NotificationMode.OFF;
}, [actions]);
const mode: NotificationMode = useMemo(() => getNotificationMode(actions), [actions]);
return mode;
};

View file

@ -0,0 +1,169 @@
import { createContext, useCallback, useContext, useMemo } from 'react';
import { ConditionKind, IPushRules, MatrixClient, PushRuleKind } from 'matrix-js-sdk';
import { Icons, IconSrc } from 'folds';
import { AccountDataEvent } from '../../types/matrix/accountData';
import { useAccountData } from './useAccountData';
import { isRoomId } from '../utils/matrix';
import {
getNotificationMode,
getNotificationModeActions,
NotificationMode,
} from './useNotificationMode';
import { useAsyncCallback } from './useAsyncCallback';
import { useMatrixClient } from './useMatrixClient';
export type RoomsNotificationPreferences = {
mute: Set<string>;
specialMessages: Set<string>;
allMessages: Set<string>;
};
const RoomsNotificationPreferencesContext = createContext<RoomsNotificationPreferences | null>(
null
);
export const RoomsNotificationPreferencesProvider = RoomsNotificationPreferencesContext.Provider;
export const useRoomsNotificationPreferencesContext = (): RoomsNotificationPreferences => {
const preferences = useContext(RoomsNotificationPreferencesContext);
if (!preferences) {
throw new Error('No RoomsNotificationPreferences provided!');
}
return preferences;
};
export const useRoomsNotificationPreferences = (): RoomsNotificationPreferences => {
const pushRules = useAccountData(AccountDataEvent.PushRules)?.getContent<IPushRules>();
const preferences: RoomsNotificationPreferences = useMemo(() => {
const global = pushRules?.global;
const room = global?.room ?? [];
const override = global?.override ?? [];
const pref: RoomsNotificationPreferences = {
mute: new Set(),
specialMessages: new Set(),
allMessages: new Set(),
};
override.forEach((rule) => {
if (isRoomId(rule.rule_id) && getNotificationMode(rule.actions) === NotificationMode.OFF) {
pref.mute.add(rule.rule_id);
}
});
room.forEach((rule) => {
if (getNotificationMode(rule.actions) === NotificationMode.OFF) {
pref.specialMessages.add(rule.rule_id);
}
});
room.forEach((rule) => {
if (getNotificationMode(rule.actions) !== NotificationMode.OFF) {
pref.allMessages.add(rule.rule_id);
}
});
return pref;
}, [pushRules]);
return preferences;
};
export enum RoomNotificationMode {
Unset = 'Unset',
Mute = 'Mute',
SpecialMessages = 'SpecialMessages',
AllMessages = 'AllMessages',
}
export const getRoomNotificationMode = (
preferences: RoomsNotificationPreferences,
roomId: string
): RoomNotificationMode => {
if (preferences.mute.has(roomId)) {
return RoomNotificationMode.Mute;
}
if (preferences.specialMessages.has(roomId)) {
return RoomNotificationMode.SpecialMessages;
}
if (preferences.allMessages.has(roomId)) {
return RoomNotificationMode.AllMessages;
}
return RoomNotificationMode.Unset;
};
export const useRoomNotificationPreference = (
preferences: RoomsNotificationPreferences,
roomId: string
): RoomNotificationMode =>
useMemo(() => getRoomNotificationMode(preferences, roomId), [preferences, roomId]);
export const getRoomNotificationModeIcon = (mode?: RoomNotificationMode): IconSrc => {
if (mode === RoomNotificationMode.Mute) return Icons.BellMute;
if (mode === RoomNotificationMode.SpecialMessages) return Icons.BellPing;
if (mode === RoomNotificationMode.AllMessages) return Icons.BellRing;
return Icons.Bell;
};
export const setRoomNotificationPreference = async (
mx: MatrixClient,
roomId: string,
mode: RoomNotificationMode,
previousMode: RoomNotificationMode
): Promise<void> => {
// remove the old preference
if (
previousMode === RoomNotificationMode.AllMessages ||
previousMode === RoomNotificationMode.SpecialMessages
) {
await mx.deletePushRule('global', PushRuleKind.RoomSpecific, roomId);
}
if (previousMode === RoomNotificationMode.Mute) {
await mx.deletePushRule('global', PushRuleKind.Override, roomId);
}
// set new preference
if (mode === RoomNotificationMode.Unset) {
return;
}
if (mode === RoomNotificationMode.Mute) {
await mx.addPushRule('global', PushRuleKind.Override, roomId, {
conditions: [
{
kind: ConditionKind.EventMatch,
key: 'room_id',
pattern: roomId,
},
],
actions: getNotificationModeActions(NotificationMode.OFF),
});
return;
}
await mx.addPushRule('global', PushRuleKind.RoomSpecific, roomId, {
actions:
mode === RoomNotificationMode.AllMessages
? getNotificationModeActions(NotificationMode.NotifyLoud)
: getNotificationModeActions(NotificationMode.OFF),
});
};
export const useSetRoomNotificationPreference = (roomId: string) => {
const mx = useMatrixClient();
const [modeState, setMode] = useAsyncCallback(
useCallback(
(mode: RoomNotificationMode, previousMode: RoomNotificationMode) =>
setRoomNotificationPreference(mx, roomId, mode, previousMode),
[mx, roomId]
)
);
return {
modeState,
setMode,
};
};

View file

@ -59,6 +59,7 @@ import { AuthRouteThemeManager, UnAuthRouteThemeManager } from './ThemeManager';
import { ReceiveSelfDeviceVerification } from '../components/DeviceVerification';
import { AutoRestoreBackupOnVerification } from '../components/BackupRestore';
import { RoomSettingsRenderer } from '../features/room-settings';
import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences';
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
const { hashRouter } = clientConfig;
@ -111,22 +112,24 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
<>
<ClientRoot>
<ClientInitStorageAtom>
<ClientBindAtoms>
<ClientNonUIFeatures>
<ClientLayout
nav={
<MobileFriendlyClientNav>
<SidebarNav />
</MobileFriendlyClientNav>
}
>
<Outlet />
</ClientLayout>
<RoomSettingsRenderer />
<ReceiveSelfDeviceVerification />
<AutoRestoreBackupOnVerification />
</ClientNonUIFeatures>
</ClientBindAtoms>
<ClientRoomsNotificationPreferences>
<ClientBindAtoms>
<ClientNonUIFeatures>
<ClientLayout
nav={
<MobileFriendlyClientNav>
<SidebarNav />
</MobileFriendlyClientNav>
}
>
<Outlet />
</ClientLayout>
<RoomSettingsRenderer />
<ReceiveSelfDeviceVerification />
<AutoRestoreBackupOnVerification />
</ClientNonUIFeatures>
</ClientBindAtoms>
</ClientRoomsNotificationPreferences>
</ClientInitStorageAtom>
</ClientRoot>
<AuthRouteThemeManager />

View file

@ -0,0 +1,15 @@
import React, { ReactNode } from 'react';
import {
RoomsNotificationPreferencesProvider,
useRoomsNotificationPreferences,
} from '../../hooks/useRoomsNotificationPreferences';
export function ClientRoomsNotificationPreferences({ children }: { children: ReactNode }) {
const preferences = useRoomsNotificationPreferences();
return (
<RoomsNotificationPreferencesProvider value={preferences}>
{children}
</RoomsNotificationPreferencesProvider>
);
}

View file

@ -33,7 +33,6 @@ import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
@ -47,6 +46,10 @@ import { markAsRead } from '../../../../client/action/notifications';
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import {
getRoomNotificationMode,
useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
type DirectMenuProps = {
requestClose: () => void;
@ -167,8 +170,7 @@ export function Direct() {
useNavToActivePathMapper('direct');
const scrollRef = useRef<HTMLDivElement>(null);
const directs = useDirectRooms();
const muteChanges = useAtomValue(muteChangesAtom);
const mutedRooms = muteChanges.added;
const notificationPreferences = useRoomsNotificationPreferencesContext();
const roomToUnread = useAtomValue(roomToUnreadAtom);
const selectedRoomId = useSelectedRoom();
@ -254,7 +256,10 @@ export function Direct() {
showAvatar
direct
linkPath={getDirectRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
muted={mutedRooms.includes(roomId)}
notificationMode={getRoomNotificationMode(
notificationPreferences,
room.roomId
)}
/>
</VirtualTile>
);

View file

@ -37,7 +37,6 @@ import { useHomeRooms } from './useHomeRooms';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
@ -50,6 +49,10 @@ import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCatego
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import {
getRoomNotificationMode,
useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
type HomeMenuProps = {
requestClose: () => void;
@ -199,8 +202,7 @@ export function Home() {
useNavToActivePathMapper('home');
const scrollRef = useRef<HTMLDivElement>(null);
const rooms = useHomeRooms();
const muteChanges = useAtomValue(muteChangesAtom);
const mutedRooms = muteChanges.added;
const notificationPreferences = useRoomsNotificationPreferencesContext();
const roomToUnread = useAtomValue(roomToUnreadAtom);
const selectedRoomId = useSelectedRoom();
@ -321,7 +323,10 @@ export function Home() {
room={room}
selected={selected}
linkPath={getHomeRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
muted={mutedRooms.includes(roomId)}
notificationMode={getRoomNotificationMode(
notificationPreferences,
room.roomId
)}
/>
</VirtualTile>
);

View file

@ -45,7 +45,6 @@ import {
import { useSpace } from '../../../hooks/useSpace';
import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
@ -71,6 +70,10 @@ import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import {
getRoomNotificationMode,
useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
type SpaceMenuProps = {
room: Room;
@ -269,8 +272,7 @@ export function Space() {
const roomToUnread = useAtomValue(roomToUnreadAtom);
const allRooms = useAtomValue(allRoomsAtom);
const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]);
const muteChanges = useAtomValue(muteChangesAtom);
const mutedRooms = muteChanges.added;
const notificationPreferences = useRoomsNotificationPreferencesContext();
const selectedRoomId = useSelectedRoom();
const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias);
@ -404,7 +406,7 @@ export function Space() {
showAvatar={mDirects.has(roomId)}
direct={mDirects.has(roomId)}
linkPath={getToLink(roomId)}
muted={mutedRooms.includes(roomId)}
notificationMode={getRoomNotificationMode(notificationPreferences, room.roomId)}
/>
</VirtualTile>
);

View file

@ -2,7 +2,6 @@ import { MatrixClient } from 'matrix-js-sdk';
import { allInvitesAtom, useBindAllInvitesAtom } from '../room-list/inviteList';
import { allRoomsAtom, useBindAllRoomsAtom } from '../room-list/roomList';
import { mDirectAtom, useBindMDirectAtom } from '../mDirectList';
import { muteChangesAtom, mutedRoomsAtom, useBindMutedRoomsAtom } from '../room-list/mutedRoomList';
import { roomToUnreadAtom, useBindRoomToUnreadAtom } from '../room/roomToUnread';
import { roomToParentsAtom, useBindRoomToParentsAtom } from '../room/roomToParents';
import { roomIdToTypingMembersAtom, useBindRoomIdToTypingMembersAtom } from '../typingMembers';
@ -12,8 +11,7 @@ export const useBindAtoms = (mx: MatrixClient) => {
useBindAllInvitesAtom(mx, allInvitesAtom);
useBindAllRoomsAtom(mx, allRoomsAtom);
useBindRoomToParentsAtom(mx, roomToParentsAtom);
useBindMutedRoomsAtom(mx, mutedRoomsAtom);
useBindRoomToUnreadAtom(mx, roomToUnreadAtom, muteChangesAtom);
useBindRoomToUnreadAtom(mx, roomToUnreadAtom);
useBindRoomIdToTypingMembersAtom(mx, roomIdToTypingMembersAtom);
};

View file

@ -1,98 +0,0 @@
import { atom, useSetAtom } from 'jotai';
import { ClientEvent, IPushRule, IPushRules, MatrixClient, MatrixEvent } from 'matrix-js-sdk';
import { useEffect } from 'react';
import { MuteChanges } from '../../../types/matrix/room';
import { findMutedRule, isMutedRule } from '../../utils/room';
export type MutedRoomsUpdate =
| {
type: 'INITIALIZE';
addRooms: string[];
}
| {
type: 'UPDATE';
addRooms: string[];
removeRooms: string[];
};
export const muteChangesAtom = atom<MuteChanges>({
added: [],
removed: [],
});
const baseMutedRoomsAtom = atom(new Set<string>());
export const mutedRoomsAtom = atom<Set<string>, [MutedRoomsUpdate], undefined>(
(get) => get(baseMutedRoomsAtom),
(get, set, action) => {
const mutedRooms = new Set([...get(mutedRoomsAtom)]);
if (action.type === 'INITIALIZE') {
set(baseMutedRoomsAtom, new Set([...action.addRooms]));
set(muteChangesAtom, {
added: [...action.addRooms],
removed: [],
});
return;
}
if (action.type === 'UPDATE') {
action.removeRooms.forEach((roomId) => mutedRooms.delete(roomId));
action.addRooms.forEach((roomId) => mutedRooms.add(roomId));
set(baseMutedRoomsAtom, mutedRooms);
set(muteChangesAtom, {
added: [...action.addRooms],
removed: [...action.removeRooms],
});
}
}
);
export const useBindMutedRoomsAtom = (mx: MatrixClient, mutedAtom: typeof mutedRoomsAtom) => {
const setMuted = useSetAtom(mutedAtom);
useEffect(() => {
const overrideRules = mx.getAccountData('m.push_rules')?.getContent<IPushRules>()
?.global?.override;
if (overrideRules) {
const mutedRooms = overrideRules.reduce<string[]>((rooms, rule) => {
if (isMutedRule(rule)) rooms.push(rule.rule_id);
return rooms;
}, []);
setMuted({
type: 'INITIALIZE',
addRooms: mutedRooms,
});
}
}, [mx, setMuted]);
useEffect(() => {
const handlePushRules = (mEvent: MatrixEvent, oldMEvent?: MatrixEvent) => {
if (mEvent.getType() === 'm.push_rules') {
const override = mEvent?.getContent()?.global?.override as IPushRule[] | undefined;
const oldOverride = oldMEvent?.getContent()?.global?.override as IPushRule[] | undefined;
if (!override || !oldOverride) return;
const isMuteToggled = (rule: IPushRule, otherOverride: IPushRule[]) => {
const roomId = rule.rule_id;
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));
setMuted({
type: 'UPDATE',
addRooms: mutedRules.map((rule) => rule.rule_id),
removeRooms: unMutedRules.map((rule) => rule.rule_id),
});
}
};
mx.on(ClientEvent.AccountData, handlePushRules);
return () => {
mx.removeListener(ClientEvent.AccountData, handlePushRules);
};
}, [mx, setMuted]);
};

View file

@ -1,5 +1,5 @@
import produce from 'immer';
import { atom, useSetAtom, PrimitiveAtom, useAtomValue } from 'jotai';
import { atom, useSetAtom } from 'jotai';
import {
IRoomTimelineData,
MatrixClient,
@ -11,7 +11,6 @@ import {
import { ReceiptContent, ReceiptType } from 'matrix-js-sdk/lib/@types/read_receipts';
import { useCallback, useEffect } from 'react';
import {
MuteChanges,
Membership,
NotificationType,
RoomToUnread,
@ -25,11 +24,11 @@ import {
getUnreadInfo,
getUnreadInfos,
isNotificationEvent,
roomHaveUnread,
} from '../../utils/room';
import { roomToParentsAtom } from './roomToParents';
import { useStateEventCallback } from '../../hooks/useStateEventCallback';
import { useSyncState } from '../../hooks/useSyncState';
import { useRoomsNotificationPreferencesContext } from '../../hooks/useRoomsNotificationPreferences';
export type RoomToUnreadAction =
| {
@ -167,13 +166,9 @@ export const roomToUnreadAtom = atom<RoomToUnread, [RoomToUnreadAction], undefin
}
);
export const useBindRoomToUnreadAtom = (
mx: MatrixClient,
unreadAtom: typeof roomToUnreadAtom,
muteChangesAtom: PrimitiveAtom<MuteChanges>
) => {
export const useBindRoomToUnreadAtom = (mx: MatrixClient, unreadAtom: typeof roomToUnreadAtom) => {
const setUnreadAtom = useSetAtom(unreadAtom);
const muteChanges = useAtomValue(muteChangesAtom);
const roomsNotificationPreferences = useRoomsNotificationPreferencesContext();
useEffect(() => {
setUnreadAtom({
@ -249,16 +244,11 @@ export const useBindRoomToUnreadAtom = (
}, [mx, setUnreadAtom]);
useEffect(() => {
muteChanges.removed.forEach((roomId) => {
const room = mx.getRoom(roomId);
if (!room) return;
if (!roomHaveUnread(mx, room)) return;
setUnreadAtom({ type: 'PUT', unreadInfo: getUnreadInfo(room) });
setUnreadAtom({
type: 'RESET',
unreadInfos: getUnreadInfos(mx),
});
muteChanges.added.forEach((roomId) => {
setUnreadAtom({ type: 'DELETE', roomId });
});
}, [mx, setUnreadAtom, muteChanges]);
}, [mx, setUnreadAtom, roomsNotificationPreferences]);
useEffect(() => {
const handleMembershipChange = (room: Room, membership: string) => {