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 <ggurdin@gmail.com>
This commit is contained in:
Copilot 2025-10-14 10:59:25 -04:00 committed by GitHub
parent f59f72c53d
commit 5609632641
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 118 additions and 43 deletions

View file

@ -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"
}

View file

@ -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<void> _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<Room?> _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<Room?> 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<void> _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<void> _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<void> 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<String, dynamic>;
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

View file

@ -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(),
),
);

View file

@ -26,6 +26,9 @@ Future<void> pushHelper(
L10n? l10n,
String? activeRoomId,
required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin,
// #Pangea
Map<String, dynamic>? additionalData,
// Pangea#
}) async {
try {
await _tryPushHelper(
@ -34,6 +37,9 @@ Future<void> 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<void> 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<void> _tryPushHelper(
L10n? l10n,
String? activeRoomId,
required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin,
// #Pangea
Map<String, dynamic>? additionalData,
// Pangea#
}) async {
final isBackgroundMessage = client == null;
Logs().v(
@ -144,7 +156,10 @@ Future<void> _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<void> _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!');
}