From b8ddbaacf3cf162ad0fa51b82f6f359d9886a096 Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:08:01 +0200 Subject: [PATCH] Mobile calls: keep call memberships in memory; answering keeps the call screen Found testing calls between two phones on the Compose call stack: - The famedly SDK keeps only "important" state in memory for a room that hasn't been opened, so call memberships of other rooms were invisible: no ring (it was cleared at once, the call looking empty), no room-list marker. org.matrix.msc3401.call.member is now important state. - The incoming-call card closed itself with Navigator.pop when the ring cleared, which popped the call screen pushed above it on Answer. The buttons now close the card first, and it closes itself only while it is the top route. Tested: a voice call in a room of two (ring, answer, both connected through LiveKit), and a video call in an encrypted room (to-device media keys, frame cryptors OK on both sides, each phone showing the other's video). The web client sees a phone's membership and rings for its notification. Co-Authored-By: Claude Opus 5.5 --- apps/mobile/lib/matrix/client.dart | 6 +++++- apps/mobile/lib/screens/call_screen.dart | 12 ++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/mobile/lib/matrix/client.dart b/apps/mobile/lib/matrix/client.dart index 9b5f902..59f1e01 100644 --- a/apps/mobile/lib/matrix/client.dart +++ b/apps/mobile/lib/matrix/client.dart @@ -9,6 +9,7 @@ import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart' as sqflite; +import 'calls.dart'; import 'sliding_sync.dart'; /// The name the session's device gets on the homeserver. @@ -60,7 +61,10 @@ Future createClient({bool startSync = true}) async { vodozemacInit: () => vod.init(), ), logLevel: kReleaseMode ? Level.warning : Level.info, - importantStateEvents: {'im.ponies.room_emotes'}, + // Kept in memory for every room, not just opened ones: the room list + // marks rooms with a call going on, and a ring needs to know who is in + // the call. + importantStateEvents: {'im.ponies.room_emotes', Calls.memberType}, // Refresh tokens: a soft logout (an expired access token) is answered by // refreshing it rather than by signing the user out. onSoftLogout: (client) => client.refreshAccessToken(), diff --git a/apps/mobile/lib/screens/call_screen.dart b/apps/mobile/lib/screens/call_screen.dart index c10ea61..6383c5f 100644 --- a/apps/mobile/lib/screens/call_screen.dart +++ b/apps/mobile/lib/screens/call_screen.dart @@ -427,8 +427,11 @@ Future showIncomingCall( final call = calls.incoming; if (call == null) { // Answered elsewhere, declined, or it stopped ringing. + // Only while the sheet is on top: after Answer, the call screen + // is, and popping would close it instead. WidgetsBinding.instance.addPostFrameCallback((_) { - if (Navigator.of(sheet).canPop()) Navigator.of(sheet).pop(); + final route = ModalRoute.of(sheet); + if (route != null && route.isCurrent) Navigator.of(sheet).pop(); }); return const SizedBox(height: 200); } @@ -462,7 +465,10 @@ Future showIncomingCall( icon: Icons.call_end, label: 'Decline', colour: c.danger, - onTap: () => calls.decline(), + onTap: () { + Navigator.of(sheet).pop(); + calls.decline(); + }, ), if (call.video) _RoundButton( @@ -470,6 +476,7 @@ Future showIncomingCall( label: 'Voice only', colour: c.tealStrong, onTap: () { + Navigator.of(sheet).pop(); calls.answer(withVideo: false); navigator.push(CallScreen.route()); }, @@ -479,6 +486,7 @@ Future showIncomingCall( label: 'Answer', colour: c.tealStrong, onTap: () { + Navigator.of(sheet).pop(); calls.answer(); navigator.push(CallScreen.route()); },