Add decline button to call notification toast (use new notification event) (#30729)

* Add decline button to call notification toast (use new notification event)

 - This make EW incompatible with the old style notify events.

Signed-off-by: Timo K <toger5@hotmail.de>

* update styling for call toast

Signed-off-by: Timo K <toger5@hotmail.de>

* skip lobby on join button click / dont skip lobby on toast click

Signed-off-by: Timo K <toger5@hotmail.de>

* dismiss toast on remote decline

Signed-off-by: Timo K <toger5@hotmail.de>

* fixup docstring and event_id

Signed-off-by: Timo K <toger5@hotmail.de>

* Add tests
Signed-off-by: Timo K <toger5@hotmail.de>

* remove unused var

Signed-off-by: Timo K <toger5@hotmail.de>

* test that decline event gets sent

Signed-off-by: Timo K <toger5@hotmail.de>

* make "go to lobby" accessible via keyboard (fix sonar cloud)

Signed-off-by: Timo K <toger5@hotmail.de>

* remove keyboard input

Signed-off-by: Timo K <toger5@hotmail.de>

* fix lint

Signed-off-by: Timo K <toger5@hotmail.de>

* use actual button

Signed-off-by: Timo K <toger5@hotmail.de>

* review style + toggle for join immediately

Signed-off-by: Timo K <toger5@hotmail.de>

* fix `getNotificationEventSendTs`

Signed-off-by: Timo K <toger5@hotmail.de>

* use story component

Signed-off-by: Timo K <toger5@hotmail.de>

* english text

Signed-off-by: Timo K <toger5@hotmail.de>

* dont use legacy toggle

Signed-off-by: Timo K <toger5@hotmail.de>

* fix lint

Signed-off-by: Timo K <toger5@hotmail.de>

* review

Signed-off-by: Timo K <toger5@hotmail.de>

* review (mostly docs)

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2025-09-16 10:41:44 +00:00
committed by GitHub
parent 3c13f55b74
commit 0783f27f33
14 changed files with 638 additions and 193 deletions
+33 -57
View File
@@ -385,33 +385,49 @@ describe("Notifier", () => {
jest.resetAllMocks();
});
const emitCallNotifyEvent = (type?: string, roomMention = true) => {
const callEvent = mkEvent({
type: type ?? EventType.CallNotify,
const emitCallNotificationEvent = (
params: {
type?: string;
roomMention?: boolean;
lifetime?: number;
ts?: number;
} = {},
) => {
const { type, roomMention, lifetime, ts } = {
type: EventType.RTCNotification,
roomMention: true,
lifetime: 30000,
ts: Date.now(),
...params,
};
const notificationEvent = mkEvent({
type: type,
user: "@alice:foo",
room: roomId,
ts,
content: {
"application": "m.call",
"notification_type": "ring",
"m.relation": { rel_type: "m.reference", event_id: "$memberEventId" },
"m.mentions": { user_ids: [], room: roomMention },
"notify_type": "ring",
"call_id": "abc123",
lifetime,
"sender_ts": ts,
},
event: true,
});
emitLiveEvent(callEvent);
return callEvent;
emitLiveEvent(notificationEvent);
return notificationEvent;
};
it("shows group call toast", () => {
const notifyEvent = emitCallNotifyEvent();
const notificationEvent = emitCallNotificationEvent();
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(
expect.objectContaining({
key: getIncomingCallToastKey(notifyEvent.getContent().call_id ?? "", roomId),
key: getIncomingCallToastKey(notificationEvent.getId() ?? "", roomId),
priority: 100,
component: IncomingCallToast,
bodyClassName: "mx_IncomingCallToast",
props: { notifyEvent },
props: { notificationEvent },
}),
);
});
@@ -439,59 +455,19 @@ describe("Notifier", () => {
const roomSession = MatrixRTCSession.roomSessionForRoom(mockClient, testRoom);
mockClient.matrixRTC.getRoomSession.mockReturnValue(roomSession);
emitCallNotifyEvent();
emitCallNotificationEvent();
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
spyCallMemberships.mockRestore();
});
it("dismisses call notification when another device answers the call", () => {
const notifyEvent = emitCallNotifyEvent();
const spyCallMemberships = jest.spyOn(MatrixRTCSession, "callMembershipsForRoom");
it("should not show toast when calling with a different event type to org.matrix.msc4075.rtc.notification", () => {
emitCallNotificationEvent({ type: "event_type" });
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(
expect.objectContaining({
key: getIncomingCallToastKey(notifyEvent.getContent().call_id ?? "", roomId),
priority: 100,
component: IncomingCallToast,
bodyClassName: "mx_IncomingCallToast",
props: { notifyEvent },
}),
);
// Mock ourselves joining the call.
spyCallMemberships.mockReturnValue([
new CallMembership(
mkEvent({
event: true,
room: testRoom.roomId,
user: userId,
type: EventType.GroupCallMemberPrefix,
content: {},
}),
{
call_id: "123",
application: "m.call",
focus_active: { type: "livekit" },
foci_preferred: [],
device_id: "DEVICE",
},
),
]);
const callEvent = mkEvent({
type: EventType.GroupCallMemberPrefix,
user: "@alice:foo",
room: roomId,
content: {
call_id: "abc123",
},
event: true,
});
emitLiveEvent(callEvent);
expect(ToastStore.sharedInstance().dismissToast).toHaveBeenCalled();
spyCallMemberships.mockRestore();
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
});
it("should not show toast when calling with non-group call event", () => {
emitCallNotifyEvent("event_type");
it("should not show notification event is expired", () => {
emitCallNotificationEvent({ ts: Date.now() - 40000 });
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
});