Group Android notifications by space and clear them on read.
All checks were successful
Build / increment-version (push) Successful in 5s
Build / build-android (push) Successful in 4m52s

Use stable per-room notification IDs with Directs/Spaces/Home channels and space group summaries so the shade nests deeper than a flat Messages list, and dismiss those tray entries when a room is marked read.
This commit is contained in:
2026-07-24 14:36:06 +10:00
parent 60edb063dd
commit 45bc475340
5 changed files with 671 additions and 59 deletions

View File

@@ -35,12 +35,17 @@ import {
isCapacitorNative,
sendNotification,
setupNotificationTapListener,
clearNotificationsForRoom,
NOTIF_GROUP_DIRECTS,
NOTIF_GROUP_HOME,
type NotificationGroupInfo,
} from '../../utils/tauri';
import { setPaarrotNavigate, initPaarrotAPI } from '../../paarrot-api';
import {
startBackgroundSync,
stopBackgroundSync,
setAppForegroundState,
syncNotificationGroupMap,
} from '../../utils/backgroundSync';
import {
TUploadItem,
@@ -226,6 +231,20 @@ function MessageNotifications() {
});
}, [navigate]);
const roomToUnread = useAtomValue(roomToUnreadAtom);
const previousUnreadRoomsRef = useRef<Set<string>>(new Set());
// Dismiss OS notifications when a room's unread is cleared (local read or receipt).
useEffect(() => {
const currentRooms = new Set(roomToUnread.keys());
for (const roomId of previousUnreadRoomsRef.current) {
if (!currentRooms.has(roomId)) {
void clearNotificationsForRoom(roomId);
}
}
previousUnreadRoomsRef.current = currentRooms;
}, [roomToUnread]);
const notify = useCallback(
({
roomName,
@@ -244,8 +263,42 @@ function MessageNotifications() {
eventId: string;
isDm: boolean;
}) => {
const notificationTitle = username;
const notificationBody = messageBody || 'New message';
let group: NotificationGroupInfo;
if (isDm) {
group = {
groupId: NOTIF_GROUP_DIRECTS,
groupName: 'Direct messages',
kind: 'direct',
roomName,
};
} else {
const orphanParents = getOrphanParents(roomToParents, roomId);
if (orphanParents.length > 0 && mx) {
const parentSpace = guessPerfectParent(mx, roomId, orphanParents) ?? orphanParents[0];
const spaceRoom = mx.getRoom(parentSpace);
group = {
groupId: parentSpace,
groupName: spaceRoom?.name || 'Space',
kind: 'space',
roomName,
};
} else {
group = {
groupId: NOTIF_GROUP_HOME,
groupName: 'Home',
kind: 'home',
roomName,
};
}
}
// DMs: sender as title. Rooms: room name as title, sender in body.
const notificationTitle = isDm ? username : roomName || username;
const notificationBody = isDm
? messageBody || 'New message'
: messageBody
? `${username}: ${messageBody}`
: `${username} sent a message`;
/** Replicates TitleBar click navigation logic */
const navigateToRoom = () => {
@@ -291,6 +344,8 @@ function MessageNotifications() {
title: notificationTitle,
body: notificationBody,
path: roomPath,
roomId,
group,
onClick: () => {
if (!window.closed) navigate(roomPath);
},
@@ -449,6 +504,9 @@ function MessageNotifications() {
*/
function BackgroundSyncSetup() {
const mx = useMatrixClient();
const mDirects = useAtomValue(mDirectAtom);
const roomToParents = useAtomValue(roomToParentsAtom);
// Try to make a simple fetch to verify component is rendering
try {
fetch('/_matrix/client/v3/sync', { method: 'HEAD' }).catch(() => {});
@@ -468,6 +526,49 @@ function BackgroundSyncSetup() {
};
}, [mx]);
// Keep native background notifier informed of space → room grouping.
useEffect(() => {
const rooms: Record<
string,
{ groupId: string; groupName: string; roomName: string; kind: 'direct' | 'space' | 'home' }
> = {};
for (const room of mx.getRooms()) {
if (room.isSpaceRoom()) continue;
const roomId = room.roomId;
const roomName = room.name || roomId;
if (mDirects.has(roomId)) {
rooms[roomId] = {
groupId: NOTIF_GROUP_DIRECTS,
groupName: 'Direct messages',
roomName,
kind: 'direct',
};
continue;
}
const orphanParents = getOrphanParents(roomToParents, roomId);
if (orphanParents.length > 0) {
const parentSpace = guessPerfectParent(mx, roomId, orphanParents) ?? orphanParents[0];
const spaceRoom = mx.getRoom(parentSpace);
rooms[roomId] = {
groupId: parentSpace,
groupName: spaceRoom?.name || 'Space',
roomName,
kind: 'space',
};
} else {
rooms[roomId] = {
groupId: NOTIF_GROUP_HOME,
groupName: 'Home',
roomName,
kind: 'home',
};
}
}
void syncNotificationGroupMap(rooms);
}, [mx, mDirects, roomToParents]);
return null;
}