From 5609632641f60a90fce27d88440dfd830781c23e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:59:25 -0400 Subject: [PATCH] Fix foreground notification navigation to activity sessions for course pings (#4369) * Initial plan * Fix foreground notification navigation to activity sessions Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com> * Add clarifying comments to notification handling code Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com> * Refactor to reduce duplicate code between _onOpenNotification and goToRoom Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com> * chore: fix foreground notif small icon --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com> Co-authored-by: ggurdin --- lib/l10n/intl_en.arb | 3 +- lib/utils/background_push.dart | 112 ++++++++++++++++++++++----------- lib/utils/client_manager.dart | 5 +- lib/utils/push_helper.dart | 41 +++++++++++- 4 files changed, 118 insertions(+), 43 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c45857708..ff2c15c08 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5314,5 +5314,6 @@ "unsubscribedResponseError": "This feature requires a subscription", "leaveDesc": "Leave this space and all chats within it", "selectAll": "Select all", - "deselectAll": "Deselect all" + "deselectAll": "Deselect all", + "newMessageInPangeaChat": "💬 New message in Pangea Chat" } diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 57e333a2b..5b803ebc7 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -76,18 +76,24 @@ class BackgroundPush { void _init() async { try { // #Pangea + // Handle notifications when app is opened from terminated/background state FirebaseMessaging.instance.getInitialMessage().then(_onOpenNotification); FirebaseMessaging.onMessageOpenedApp.listen(_onOpenNotification); // Pangea# await _flutterLocalNotificationsPlugin.initialize( const InitializationSettings( - android: AndroidInitializationSettings('notifications_icon'), + // #Pangea + // android: AndroidInitializationSettings('notifications_icon'), + android: AndroidInitializationSettings('@mipmap/ic_launcher'), + // Pangea# iOS: DarwinInitializationSettings(), ), onDidReceiveNotificationResponse: goToRoom, ); // #Pangea + // Handle notifications when app is in foreground + // Pass additionalData to preserve activity session info for local notifications FirebaseMessaging.onMessage.listen((RemoteMessage message) { pushHelper( PushNotification.fromJson(message.data), @@ -95,6 +101,7 @@ class BackgroundPush { l10n: l10n, activeRoomId: matrix?.activeRoomId, flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin, + additionalData: message.data, ); }); // Pangea# @@ -137,36 +144,32 @@ class BackgroundPush { } // #Pangea - Future _onOpenNotification(RemoteMessage? message) async { - const sessionIdKey = "content_pangea.activity.session_room_id"; - const activityIdKey = "content_pangea.activity.id"; + // Helper to ensure a room is loaded or synced. + Future _ensureRoomLoaded(String id) async { + await client.roomsLoading; + await client.accountDataLoading; - // Early return if no room_id. - final roomId = message?.data['room_id']; - if (roomId is! String || roomId.isEmpty) return; - - // Helper to ensure a room is loaded or synced. - Future ensureRoomLoaded(String id) async { - await client.roomsLoading; - await client.accountDataLoading; - - var room = client.getRoomById(id); - if (room == null) { - await client.waitForRoomInSync(id).timeout(const Duration(seconds: 30)); - room = client.getRoomById(id); - } - return room; + var room = client.getRoomById(id); + if (room == null) { + await client.waitForRoomInSync(id).timeout(const Duration(seconds: 30)); + room = client.getRoomById(id); } + return room; + } + // Navigate to activity session or fallback to room + Future _navigateToActivityOrRoom({ + required String roomId, + String? sessionRoomId, + String? activityId, + }) async { // Handle session room if provided. - final sessionRoomId = message?.data[sessionIdKey]; - final activityId = message?.data[activityIdKey]; - if (sessionRoomId is String && + if (sessionRoomId != null && sessionRoomId.isNotEmpty && - activityId is String && + activityId != null && activityId.isNotEmpty) { try { - final course = await ensureRoomLoaded(roomId); + final course = await _ensureRoomLoaded(roomId); if (course == null) return; final session = client.getRoomById(sessionRoomId); @@ -186,7 +189,7 @@ class BackgroundPush { // Fallback: just open the original room. try { - final room = await ensureRoomLoaded(roomId); + final room = await _ensureRoomLoaded(roomId); FluffyChatApp.router.go( room?.membership == Membership.invite ? '/rooms' : '/rooms/$roomId', ); @@ -194,6 +197,24 @@ class BackgroundPush { ErrorHandler.logError(e: err, s: s, data: {"roomId": roomId}); } } + + Future _onOpenNotification(RemoteMessage? message) async { + const sessionIdKey = "content_pangea.activity.session_room_id"; + const activityIdKey = "content_pangea.activity.id"; + + // Early return if no room_id. + final roomId = message?.data['room_id']; + if (roomId is! String || roomId.isEmpty) return; + + final sessionRoomId = message?.data[sessionIdKey] as String?; + final activityId = message?.data[activityIdKey] as String?; + + await _navigateToActivityOrRoom( + roomId: roomId, + sessionRoomId: sessionRoomId, + activityId: activityId, + ); + } // Pangea# factory BackgroundPush.clientOnly(Client client) { @@ -461,23 +482,38 @@ class BackgroundPush { Future goToRoom(NotificationResponse? response) async { try { - final roomId = response?.payload; - Logs().v('[Push] Attempting to go to room $roomId...'); - if (roomId == null) { + final payload = response?.payload; + Logs().v('[Push] Attempting to go to room with payload: $payload'); + if (payload == null) { return; } - await client.roomsLoading; - await client.accountDataLoading; - if (client.getRoomById(roomId) == null) { - await client - .waitForRoomInSync(roomId) - .timeout(const Duration(seconds: 30)); + + // #Pangea - Handle activity session data if present + String? roomId; + String? sessionRoomId; + String? activityId; + + try { + final payloadData = jsonDecode(payload) as Map; + roomId = payloadData['room_id'] as String?; + sessionRoomId = + payloadData['content_pangea.activity.session_room_id'] as String?; + activityId = payloadData['content_pangea.activity.id'] as String?; + } catch (_) { + // If payload is not JSON, treat it as a simple room ID + roomId = payload; } - FluffyChatApp.router.go( - client.getRoomById(roomId)?.membership == Membership.invite - ? '/rooms' - : '/rooms/$roomId', + + if (roomId == null || roomId.isEmpty) { + return; + } + + await _navigateToActivityOrRoom( + roomId: roomId, + sessionRoomId: sessionRoomId, + activityId: activityId, ); + // Pangea# } catch (e, s) { Logs().e('[Push] Failed to open room', e, s); // #Pangea diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 896e5e61d..a0e4d78f3 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -216,7 +216,10 @@ abstract class ClientManager { await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( - android: AndroidInitializationSettings('notifications_icon'), + // #Pangea + // android: AndroidInitializationSettings('notifications_icon'), + android: AndroidInitializationSettings('@mipmap/ic_launcher'), + // Pangea# iOS: DarwinInitializationSettings(), ), ); diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 57b24a25e..03a88e085 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -26,6 +26,9 @@ Future pushHelper( L10n? l10n, String? activeRoomId, required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, + // #Pangea + Map? additionalData, + // Pangea# }) async { try { await _tryPushHelper( @@ -34,6 +37,9 @@ Future pushHelper( l10n: l10n, activeRoomId: activeRoomId, flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, + // #Pangea + additionalData: additionalData, + // Pangea# ); } catch (e, s) { Logs().v('Push Helper has crashed!', e, s); @@ -41,7 +47,10 @@ Future pushHelper( l10n ??= await lookupL10n(const Locale('en')); flutterLocalNotificationsPlugin.show( notification.roomId?.hashCode ?? 0, - l10n.newMessageInFluffyChat, + // #Pangea + // l10n.newMessageInFluffyChat, + l10n.newMessageInPangeaChat, + // Pangea# l10n.openAppToReadMessages, NotificationDetails( iOS: const DarwinNotificationDetails(), @@ -69,6 +78,9 @@ Future _tryPushHelper( L10n? l10n, String? activeRoomId, required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, + // #Pangea + Map? additionalData, + // Pangea# }) async { final isBackgroundMessage = client == null; Logs().v( @@ -144,7 +156,10 @@ Future _tryPushHelper( // Calculate the body final body = event.type == EventTypes.Encrypted - ? l10n.newMessageInFluffyChat + // #Pangea + // ? l10n.newMessageInFluffyChat + ? l10n.newMessageInPangeaChat + // Pangea# : await event.calcLocalizedBody( matrixLocals, plaintextBody: true, @@ -297,12 +312,32 @@ Future _tryPushHelper( await _setShortcut(event, l10n, title, roomAvatarFile); } + // #Pangea - Include activity session data in payload + String payload = event.roomId!; + if (additionalData != null) { + const sessionIdKey = "content_pangea.activity.session_room_id"; + const activityIdKey = "content_pangea.activity.id"; + final sessionRoomId = additionalData[sessionIdKey]; + final activityId = additionalData[activityIdKey]; + if (sessionRoomId is String && activityId is String) { + payload = jsonEncode({ + 'room_id': event.roomId, + sessionIdKey: sessionRoomId, + activityIdKey: activityId, + }); + } + } + // Pangea# + await flutterLocalNotificationsPlugin.show( id, title, body, platformChannelSpecifics, - payload: event.roomId, + // #Pangea + // payload: event.roomId, + payload: payload, + // Pangea# ); Logs().v('Push helper has been completed!'); }