From b283a8a7b8f7318900e0c02a460d8970991bc6a4 Mon Sep 17 00:00:00 2001 From: baltevl Date: Thu, 31 Oct 2024 19:25:23 +0100 Subject: [PATCH 1/6] [UnifiedPush] WIP: notifications for multiple accounts --- lib/main.dart | 2 +- lib/pages/chat/chat.dart | 4 +- lib/pages/chat_list/chat_list.dart | 4 +- lib/utils/background_push.dart | 83 +++++++++++++++++++++--------- lib/utils/push_helper.dart | 27 ++++++---- 5 files changed, 85 insertions(+), 35 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d9ac10b4c3..c049187fd6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,7 +38,7 @@ void main() async { // In the background fetch mode we do not want to waste ressources with // starting the Flutter engine but process incoming push notifications. - BackgroundPush.clientOnly(clients.first); + BackgroundPush.clientsOnly(clients); // To start the flutter engine afterwards we add an custom observer. WidgetsBinding.instance.addObserver(AppStarter(clients, store)); Logs().i( diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index fce1d38a4a..2bb1072f93 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -392,7 +392,9 @@ class ChatController extends State _setReadMarkerFuture = null; }); if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { - Matrix.of(context).backgroundPush?.cancelNotification(roomId); + Matrix.of(context) + .backgroundPush + ?.cancelNotification(Matrix.of(context).client, roomId); } } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 22868fa54d..b8f0b85e09 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -515,7 +515,9 @@ class ChatListController extends State if (mounted) { searchServer = Matrix.of(context).store.getString(_serverStoreNamespace); - Matrix.of(context).backgroundPush?.setupPush(); + Matrix.of(context) + .backgroundPush + ?.setupPush(Matrix.of(context).widget.clients); UpdateNotifier.showUpdateSnackBar(context); } diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 1ba2659a6b..23f326b618 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -49,7 +49,7 @@ class BackgroundPush { static BackgroundPush? _instance; final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - Client client; + List clients; MatrixState? matrix; String? _fcmToken; void Function(String errorMsg, {Uri? link})? onFcmError; @@ -85,7 +85,10 @@ class BackgroundPush { PushNotification.fromJson( Map.from(message['data'] ?? message), ), - client: client, + clients: clients, + //TODO: figure out if firebase supports + // multiple instances + instance: clients.first.clientName, l10n: l10n, activeRoomId: matrix?.activeRoomId, flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin, @@ -93,10 +96,10 @@ class BackgroundPush { ); if (Platform.isAndroid) { await UnifiedPush.initialize( - onNewEndpoint: _newUpEndpoint, - onRegistrationFailed: _upUnregistered, - onUnregistered: _upUnregistered, - onMessage: _onUpMessage, + onNewEndpoint: _newUPEndpoint, + onRegistrationFailed: _onUPUnregistered, + onUnregistered: _onUPUnregistered, + onMessage: _onUPMessage, ); } } catch (e, s) { @@ -104,26 +107,26 @@ class BackgroundPush { } } - BackgroundPush._(this.client) { + BackgroundPush._(this.clients) { _init(); } - factory BackgroundPush.clientOnly(Client client) { - return _instance ??= BackgroundPush._(client); + factory BackgroundPush.clientsOnly(List clients) { + return _instance ??= BackgroundPush._(clients); } factory BackgroundPush( MatrixState matrix, { final void Function(String errorMsg, {Uri? link})? onFcmError, }) { - final instance = BackgroundPush.clientOnly(matrix.client); + final instance = BackgroundPush.clientsOnly(matrix.widget.clients); instance.matrix = matrix; // ignore: prefer_initializing_formals instance.onFcmError = onFcmError; return instance; } - Future cancelNotification(String roomId) async { + Future cancelNotification(Client client, String roomId) async { Logs().v('Cancel notification for room', roomId); await _flutterLocalNotificationsPlugin.cancel(roomId.hashCode); @@ -146,6 +149,7 @@ class BackgroundPush { String? token, Set? oldTokens, bool useDeviceSpecificAppId = false, + required Client client, }) async { if (PlatformInfos.isIOS) { await firebase?.requestPermission(); @@ -246,9 +250,9 @@ class BackgroundPush { static bool _wentToRoomOnStartup = false; - Future setupPush() async { + Future setupPush(List clients) async { Logs().d("SetupPush"); - if (client.onLoginStateChanged.value != LoginState.loggedIn || + if (clients.first.onLoginStateChanged.value != LoginState.loggedIn || !PlatformInfos.isMobile || matrix == null) { return; @@ -260,7 +264,7 @@ class BackgroundPush { } if (!PlatformInfos.isIOS && (await UnifiedPush.getDistributors()).isNotEmpty) { - await setupUp(); + await setupUP(clients); } else { await setupFirebase(); } @@ -316,6 +320,7 @@ class BackgroundPush { await setupPusher( gatewayUrl: AppConfig.pushNotificationsGatewayUrl, token: _fcmToken, + client: clients.first, // Workaround: set todo in _init ); } @@ -326,6 +331,20 @@ class BackgroundPush { if (roomId == null) { return; } + + // Workaround because response does not give more information + // about who received the notification + Client? client; + for (final c in clients) { + if (c.getRoomById(roomId) != null) { + client = c; + break; + } + } + if (client == null) { + Logs().w('[Push] No client could be found for room $roomId...'); + return; + } await client.roomsLoading; await client.accountDataLoading; if (client.getRoomById(roomId) == null) { @@ -343,15 +362,16 @@ class BackgroundPush { } } - Future setupUp() async { - await UnifiedPushUi(matrix!.context, ["default"], UPFunctions()) + Future setupUP(List clients) async { + final names = clients.map((c) => c.clientName); + await UnifiedPushUi(matrix!.context, List.from(names), UPFunctions()) .registerAppWithDialog(); } - Future _newUpEndpoint(String newEndpoint, String i) async { + Future _newUPEndpoint(String newEndpoint, String instance) async { upAction = true; if (newEndpoint.isEmpty) { - await _upUnregistered(i); + await _onUPUnregistered(instance); return; } var endpoint = @@ -377,38 +397,45 @@ class BackgroundPush { '[Push] No self-hosted unified push gateway present: $newEndpoint', ); } - Logs().i('[Push] UnifiedPush using endpoint $endpoint'); + Logs().i('[Push] UnifiedPush $instance using endpoint $endpoint'); final oldTokens = {}; try { final fcmToken = await firebase?.getToken(); oldTokens.add(fcmToken); } catch (_) {} + final client = clientFromInstance(instance, clients); + if (client == null) { + throw "Not client found for $instance"; + } await setupPusher( gatewayUrl: endpoint, token: newEndpoint, oldTokens: oldTokens, useDeviceSpecificAppId: true, + client: client, ); await matrix?.store.setString(SettingKeys.unifiedPushEndpoint, newEndpoint); await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, true); } - Future _upUnregistered(String i) async { + Future _onUPUnregistered(String instance) async { upAction = true; + final client = clientFromInstance(instance, clients); Logs().i('[Push] Removing UnifiedPush endpoint...'); final oldEndpoint = matrix?.store.getString(SettingKeys.unifiedPushEndpoint); await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, false); await matrix?.store.remove(SettingKeys.unifiedPushEndpoint); - if (oldEndpoint?.isNotEmpty ?? false) { + if (client != null && (oldEndpoint?.isNotEmpty ?? false)) { // remove the old pusher await setupPusher( oldTokens: {oldEndpoint}, + client: client, ); } } - Future _onUpMessage(Uint8List message, String i) async { + Future _onUPMessage(Uint8List message, String instance) async { upAction = true; final data = Map.from( json.decode(utf8.decode(message))['notification'], @@ -417,7 +444,8 @@ class BackgroundPush { data['devices'] ??= []; await pushHelper( PushNotification.fromJson(data), - client: client, + clients: clients, + instance: instance, l10n: l10n, activeRoomId: matrix?.activeRoomId, flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin, @@ -447,3 +475,12 @@ class UPFunctions extends UnifiedPushFunctions { await UnifiedPush.saveDistributor(distributor); } } + +Client? clientFromInstance(String? instance, List clients) { + for (final c in clients) { + if (c.clientName == instance) { + return c; + } + } + return null; +} diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 05f0e90c71..c7a5d4f116 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -11,6 +11,7 @@ import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/background_push.dart' show clientFromInstance; import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -19,15 +20,17 @@ import 'package:fluffychat/utils/voip/callkeep_manager.dart'; Future pushHelper( PushNotification notification, { - Client? client, + List? clients, L10n? l10n, String? activeRoomId, required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, + required String instance, }) async { try { await _tryPushHelper( notification, - client: client, + clients: clients, + instance: instance, l10n: l10n, activeRoomId: activeRoomId, flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, @@ -62,12 +65,22 @@ Future pushHelper( Future _tryPushHelper( PushNotification notification, { - Client? client, + List? clients, L10n? l10n, String? activeRoomId, required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, + required String instance, }) async { - final isBackgroundMessage = client == null; + final isBackgroundMessage = clients == null; + clients ??= (await ClientManager.getClients( + initialize: false, + store: await SharedPreferences.getInstance(), + )); + final client = clientFromInstance(instance, clients); + if (client == null) { + throw "Not client could be found for $instance"; + } + Logs().v( 'Push helper has been started (background=$isBackgroundMessage).', notification.toJson(), @@ -76,15 +89,11 @@ Future _tryPushHelper( if (notification.roomId != null && activeRoomId == notification.roomId && WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { + //TODO: check if client is active Logs().v('Room is in foreground. Stop push helper here.'); return; } - client ??= (await ClientManager.getClients( - initialize: false, - store: await SharedPreferences.getInstance(), - )) - .first; final event = await client.getEventByPushNotification( notification, storeInDatabase: isBackgroundMessage, From b149f2e76929a382f14cb10c1fc35aca7ce7cd49 Mon Sep 17 00:00:00 2001 From: baltevl Date: Fri, 1 Nov 2024 08:05:07 +0100 Subject: [PATCH 2/6] feat[UP]: WIP store endpoint per user --- lib/utils/background_push.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 23f326b618..40c39a187e 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -414,19 +414,22 @@ class BackgroundPush { useDeviceSpecificAppId: true, client: client, ); - await matrix?.store.setString(SettingKeys.unifiedPushEndpoint, newEndpoint); - await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, true); + await matrix?.store.setString(client.clientName + SettingKeys.unifiedPushEndpoint, newEndpoint); + await matrix?.store.setBool(client.clientName + SettingKeys.unifiedPushRegistered, true); } Future _onUPUnregistered(String instance) async { upAction = true; final client = clientFromInstance(instance, clients); + if (client == null) { + return; + } Logs().i('[Push] Removing UnifiedPush endpoint...'); final oldEndpoint = - matrix?.store.getString(SettingKeys.unifiedPushEndpoint); - await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, false); - await matrix?.store.remove(SettingKeys.unifiedPushEndpoint); - if (client != null && (oldEndpoint?.isNotEmpty ?? false)) { + matrix?.store.getString(client.clientName + SettingKeys.unifiedPushEndpoint); + await matrix?.store.setBool(client.clientName + SettingKeys.unifiedPushRegistered, false); + await matrix?.store.remove(client.clientName + SettingKeys.unifiedPushEndpoint); + if (oldEndpoint?.isNotEmpty ?? false) { // remove the old pusher await setupPusher( oldTokens: {oldEndpoint}, From 0daf998382c96a6f5d582f8fad29e77a85f4b3eb Mon Sep 17 00:00:00 2001 From: baltevl Date: Sat, 2 Nov 2024 00:33:59 +0100 Subject: [PATCH 3/6] WIP --- lib/main.dart | 2 + lib/utils/background_push.dart | 49 ++++--- lib/utils/push_helper.dart | 252 ++++++++++++++++++++------------- 3 files changed, 187 insertions(+), 116 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c049187fd6..f4d1f796fa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,6 +25,8 @@ void main() async { final store = await SharedPreferences.getInstance(); final clients = await ClientManager.getClients(store: store); + //TODO: migrate single client push settings to multiclient settings + // If the app starts in detached mode, we assume that it is in // background fetch mode for processing push notifications. This is // currently only supported on Android. diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 40c39a187e..daae153bf3 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -91,6 +91,7 @@ class BackgroundPush { instance: clients.first.clientName, l10n: l10n, activeRoomId: matrix?.activeRoomId, + activeClient: matrix?.client, flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin, ), ); @@ -320,31 +321,39 @@ class BackgroundPush { await setupPusher( gatewayUrl: AppConfig.pushNotificationsGatewayUrl, token: _fcmToken, - client: clients.first, // Workaround: set todo in _init + client: clients.first, // Workaround: see todo in _init ); } Future goToRoom(NotificationResponse? response) async { try { - final roomId = response?.payload; - Logs().v('[Push] Attempting to go to room $roomId...'); - if (roomId == null) { + final payloadEncoded = response?.payload; + if (payloadEncoded == null || payloadEncoded.isEmpty) { return; } + final payload = + NotificationResponsePayload.fromJson(jsonDecode(payloadEncoded)); + if (payload.roomId.isEmpty) { + return; + } + Logs().v('[Push] Attempting to go to room ${payload.roomId}...'); - // Workaround because response does not give more information - // about who received the notification Client? client; for (final c in clients) { - if (c.getRoomById(roomId) != null) { + if (c.clientName == payload.clientName) { client = c; break; } } if (client == null) { - Logs().w('[Push] No client could be found for room $roomId...'); + Logs() + .w('[Push] No client could be found for room ${payload.roomId}...'); return; } + if (matrix!.client != client) { + matrix!.setActiveClient(client); + } + await client.roomsLoading; await client.accountDataLoading; if (client.getRoomById(roomId) == null) { @@ -353,9 +362,9 @@ class BackgroundPush { .timeout(const Duration(seconds: 30)); } FluffyChatApp.router.go( - client.getRoomById(roomId)?.membership == Membership.invite + client.getRoomById(payload.roomId)?.membership == Membership.invite ? '/rooms' - : '/rooms/$roomId', + : '/rooms/${payload.roomId}', ); } catch (e, s) { Logs().e('[Push] Failed to open room', e, s); @@ -405,7 +414,8 @@ class BackgroundPush { } catch (_) {} final client = clientFromInstance(instance, clients); if (client == null) { - throw "Not client found for $instance"; + Logs().e("Not client found for $instance"); + return; } await setupPusher( gatewayUrl: endpoint, @@ -414,8 +424,10 @@ class BackgroundPush { useDeviceSpecificAppId: true, client: client, ); - await matrix?.store.setString(client.clientName + SettingKeys.unifiedPushEndpoint, newEndpoint); - await matrix?.store.setBool(client.clientName + SettingKeys.unifiedPushRegistered, true); + await matrix?.store.setString( + client.clientName + SettingKeys.unifiedPushEndpoint, newEndpoint); + await matrix?.store + .setBool(client.clientName + SettingKeys.unifiedPushRegistered, true); } Future _onUPUnregistered(String instance) async { @@ -425,10 +437,12 @@ class BackgroundPush { return; } Logs().i('[Push] Removing UnifiedPush endpoint...'); - final oldEndpoint = - matrix?.store.getString(client.clientName + SettingKeys.unifiedPushEndpoint); - await matrix?.store.setBool(client.clientName + SettingKeys.unifiedPushRegistered, false); - await matrix?.store.remove(client.clientName + SettingKeys.unifiedPushEndpoint); + final oldEndpoint = matrix?.store + .getString(client.clientName + SettingKeys.unifiedPushEndpoint); + await matrix?.store + .setBool(client.clientName + SettingKeys.unifiedPushRegistered, false); + await matrix?.store + .remove(client.clientName + SettingKeys.unifiedPushEndpoint); if (oldEndpoint?.isNotEmpty ?? false) { // remove the old pusher await setupPusher( @@ -450,6 +464,7 @@ class BackgroundPush { clients: clients, instance: instance, l10n: l10n, + activeClient: matrix?.client, activeRoomId: matrix?.activeRoomId, flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin, ); diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index c7a5d4f116..ca52f57b34 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -18,11 +19,14 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/voip/callkeep_manager.dart'; +//TODO: maybe introduce a class + Future pushHelper( PushNotification notification, { List? clients, L10n? l10n, String? activeRoomId, + Client? activeClient, required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, required String instance, }) async { @@ -33,6 +37,7 @@ Future pushHelper( instance: instance, l10n: l10n, activeRoomId: activeRoomId, + activeClient: activeClient, flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, ); } catch (e, s) { @@ -68,28 +73,27 @@ Future _tryPushHelper( List? clients, L10n? l10n, String? activeRoomId, + Client? activeClient, required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, required String instance, }) async { final isBackgroundMessage = clients == null; + Logs().v( + 'Push helper has been started (background=$isBackgroundMessage).', + notification.toJson(), + ); + clients ??= (await ClientManager.getClients( initialize: false, store: await SharedPreferences.getInstance(), )); final client = clientFromInstance(instance, clients); if (client == null) { - throw "Not client could be found for $instance"; + Logs().e("Not client could be found for $instance"); + return; } - Logs().v( - 'Push helper has been started (background=$isBackgroundMessage).', - notification.toJson(), - ); - - if (notification.roomId != null && - activeRoomId == notification.roomId && - WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { - //TODO: check if client is active + if (isInForeground(notification, activeRoomId, activeClient, client)) { Logs().v('Room is in foreground. Stop push helper here.'); return; } @@ -135,26 +139,33 @@ Future _tryPushHelper( client.backgroundSync = false; } - if (event.type.startsWith('m.call') && event.type != EventTypes.CallInvite) { - Logs().v('Push message is a m.call but not invite. Do not display.'); - return; - } - if ((event.type.startsWith('m.call') && event.type != EventTypes.CallInvite) || - event.type == 'org.matrix.call.sdp_stream_metadata_changed') { + event.type == EventTypes.CallSDPStreamMetadataChangedPrefix) { Logs().v('Push message was for a call, but not call invite.'); return; } + showNotification( + l10n, event, notification, client, flutterLocalNotificationsPlugin); + Logs().v('Push helper has been completed!'); +} + +Future showNotification( + L10n? l10n, + Event event, + PushNotification notification, + Client client, + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, +) async { l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); - final matrixLocals = MatrixLocals(l10n); + final locals = MatrixLocals(l10n); // Calculate the body final body = event.type == EventTypes.Encrypted ? l10n.newMessageInFluffyChat : await event.calcLocalizedBody( - matrixLocals, + locals, plaintextBody: true, withSenderNamePrefix: false, hideReply: true, @@ -162,54 +173,83 @@ Future _tryPushHelper( removeMarkdown: true, ); + final id = notification.roomId.hashCode; + final title = event.room.getLocalizedDisplayname(locals); + final roomName = event.room.getLocalizedDisplayname(locals); + + var notificationGroupId = + event.room.isDirectChat ? 'directChats' : 'groupChats'; + notificationGroupId += client.clientName; + final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; + + final messageRooms = AndroidNotificationChannelGroup( + notificationGroupId, + groupName, + ); + final roomsChannel = AndroidNotificationChannel( + event.room.id, + roomName, + groupId: notificationGroupId, + ); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannelGroup(messageRooms); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(roomsChannel); + + final platformChannelSpecifics = await getPlatformChannelSpecifics( + l10n, + notification, + event, + client, + id, + body, + title, + roomName, + ); + + return await flutterLocalNotificationsPlugin.show( + id, + title, + body, + platformChannelSpecifics, + payload: jsonEncode( + NotificationResponsePayload( + event.roomId ?? "", + client.clientName, + ).toJson(), + ), + ); +} + +Future getPlatformChannelSpecifics( + L10n l10n, + PushNotification notification, + Event event, + Client client, + int notificationId, + String notificationBody, + String notificationTitle, + String roomName, +) async { // The person object for the android message style notification final avatar = event.room.avatar; final senderAvatar = event.room.isDirectChat ? avatar : event.senderFromMemoryOrFallback.avatarUrl; - Uint8List? roomAvatarFile, senderAvatarFile; - try { - roomAvatarFile = avatar == null - ? null - : await client - .downloadMxcCached( - avatar, - thumbnailMethod: ThumbnailMethod.scale, - width: 256, - height: 256, - animated: false, - isThumbnail: true, - ) - .timeout(const Duration(seconds: 3)); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - } - try { - senderAvatarFile = event.room.isDirectChat - ? roomAvatarFile - : senderAvatar == null - ? null - : await client - .downloadMxcCached( - senderAvatar, - thumbnailMethod: ThumbnailMethod.scale, - width: 256, - height: 256, - animated: false, - isThumbnail: true, - ) - .timeout(const Duration(seconds: 3)); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - } - - final id = notification.roomId.hashCode; + final roomAvatarFile = await getAvatarFile(client, avatar); + final senderAvatarFile = event.room.isDirectChat + ? roomAvatarFile + : await getAvatarFile(client, senderAvatar); // Show notification - final newMessage = Message( - body, + notificationTitle, event.originServerTs, Person( bot: event.messageType == MessageTypes.Notice, @@ -223,34 +263,13 @@ Future _tryPushHelper( final messagingStyleInformation = PlatformInfos.isAndroid ? await AndroidFlutterLocalNotificationsPlugin() - .getActiveNotificationMessagingStyle(id) + .getActiveNotificationMessagingStyle(notificationId) : null; messagingStyleInformation?.messages?.add(newMessage); - final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); - - final notificationGroupId = - event.room.isDirectChat ? 'directChats' : 'groupChats'; - final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; - - final messageRooms = AndroidNotificationChannelGroup( - notificationGroupId, - groupName, - ); - final roomsChannel = AndroidNotificationChannel( - event.room.id, - roomName, - groupId: notificationGroupId, - ); - - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannelGroup(messageRooms); - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(roomsChannel); + if (PlatformInfos.isAndroid && messagingStyleInformation == null) { + await _setShortcut(event, l10n, notificationTitle, roomAvatarFile); + } final androidPlatformChannelSpecifics = AndroidNotificationDetails( AppConfig.pushNotificationsChannelId, @@ -273,7 +292,7 @@ Future _tryPushHelper( messages: [newMessage], ), ticker: event.calcLocalizedBodyFallback( - matrixLocals, + MatrixLocals(l10n), plaintextBody: true, withSenderNamePrefix: true, hideReply: true, @@ -285,25 +304,10 @@ Future _tryPushHelper( groupKey: event.room.spaceParents.firstOrNull?.roomId ?? 'rooms', ); const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); - final platformChannelSpecifics = NotificationDetails( + return NotificationDetails( android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics, ); - - final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); - - if (PlatformInfos.isAndroid && messagingStyleInformation == null) { - await _setShortcut(event, l10n, title, roomAvatarFile); - } - - await flutterLocalNotificationsPlugin.show( - id, - title, - body, - platformChannelSpecifics, - payload: event.roomId, - ); - Logs().v('Push helper has been completed!'); } /// Creates a shortcut for Android platform but does not block displaying the @@ -333,3 +337,53 @@ Future _setShortcut( ), ); } + +Future getAvatarFile(Client client, Uri? avatar) async { + try { + return avatar == null + ? null + : await client + .downloadMxcCached( + avatar, + thumbnailMethod: ThumbnailMethod.scale, + width: 256, + height: 256, + animated: false, + isThumbnail: true, + ) + .timeout(const Duration(seconds: 3)); + } catch (e, s) { + Logs().e('Unable to get avatar picture', e, s); + return null; + } +} + +bool isInForeground( + PushNotification notification, + String? activeRoomId, + Client? activeClient, + Client notifiedClient, +) { + return notification.roomId != null && + activeRoomId == notification.roomId && + activeClient == notifiedClient && + WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed; +} + +class NotificationResponsePayload { + final String roomId; + final String clientName; + + NotificationResponsePayload(this.roomId, this.clientName); + + NotificationResponsePayload.fromJson(Map json) + : roomId = json['roomId'], + clientName = json['clientName']; + + Map toJson() { + return { + 'roomId': roomId, + 'clientName': clientName, + }; + } +} From 2747ab71ef952a03a1053f5d175273716c1301f1 Mon Sep 17 00:00:00 2001 From: baltevl Date: Sat, 2 Nov 2024 11:05:10 +0100 Subject: [PATCH 4/6] feat[UP]: Add settings migration --- lib/main.dart | 2 -- lib/utils/background_push.dart | 27 +++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f4d1f796fa..c049187fd6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,8 +25,6 @@ void main() async { final store = await SharedPreferences.getInstance(); final clients = await ClientManager.getClients(store: store); - //TODO: migrate single client push settings to multiclient settings - // If the app starts in detached mode, we assume that it is in // background fetch mode for processing push notifications. This is // currently only supported on Android. diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index daae153bf3..fecee9ed61 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -197,7 +197,7 @@ class BackgroundPush { )) { Logs().i('[Push] Pusher already set'); } else { - Logs().i('Need to set new pusher'); + Logs().i('[Push] Need to set new pusher'); oldTokens.add(token); if (client.isLogged()) { setNewPusher = true; @@ -253,6 +253,27 @@ class BackgroundPush { Future setupPush(List clients) async { Logs().d("SetupPush"); + + { + // migrate single client push settings to multiclient settings + final endpoint = matrix!.store.getString(SettingKeys.unifiedPushEndpoint); + if (endpoint != null) { + matrix!.store.setString( + clients.first.clientName + SettingKeys.unifiedPushEndpoint, + endpoint); + matrix!.store.remove(SettingKeys.unifiedPushEndpoint); + } + + final registered = + matrix!.store.getBool(SettingKeys.unifiedPushRegistered); + if (registered != null) { + matrix!.store.setBool( + clients.first.clientName + SettingKeys.unifiedPushRegistered, + registered); + matrix!.store.remove(SettingKeys.unifiedPushRegistered); + } + } + if (clients.first.onLoginStateChanged.value != LoginState.loggedIn || !PlatformInfos.isMobile || matrix == null) { @@ -425,7 +446,9 @@ class BackgroundPush { client: client, ); await matrix?.store.setString( - client.clientName + SettingKeys.unifiedPushEndpoint, newEndpoint); + client.clientName + SettingKeys.unifiedPushEndpoint, + newEndpoint, + ); await matrix?.store .setBool(client.clientName + SettingKeys.unifiedPushRegistered, true); } From e3747e5508224ec085101f72c1696e69727edd3a Mon Sep 17 00:00:00 2001 From: baltevl Date: Sat, 2 Nov 2024 11:58:59 +0100 Subject: [PATCH 5/6] refactor[UP]: refactor pushHelper --- lib/utils/background_push.dart | 8 +- lib/utils/push_helper.dart | 619 +++++++++++++++++---------------- 2 files changed, 317 insertions(+), 310 deletions(-) diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index fecee9ed61..6d073af56f 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -81,7 +81,7 @@ class BackgroundPush { ); Logs().v('Flutter Local Notifications initialized'); firebase?.setListeners( - onMessage: (message) => pushHelper( + onMessage: (message) => PushHelper.pushHelper( PushNotification.fromJson( Map.from(message['data'] ?? message), ), @@ -260,7 +260,7 @@ class BackgroundPush { if (endpoint != null) { matrix!.store.setString( clients.first.clientName + SettingKeys.unifiedPushEndpoint, - endpoint); + endpoint,); matrix!.store.remove(SettingKeys.unifiedPushEndpoint); } @@ -269,7 +269,7 @@ class BackgroundPush { if (registered != null) { matrix!.store.setBool( clients.first.clientName + SettingKeys.unifiedPushRegistered, - registered); + registered,); matrix!.store.remove(SettingKeys.unifiedPushRegistered); } } @@ -482,7 +482,7 @@ class BackgroundPush { ); // UP may strip the devices list data['devices'] ??= []; - await pushHelper( + await PushHelper.pushHelper( PushNotification.fromJson(data), clients: clients, instance: instance, diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index ca52f57b34..1b803d7664 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:ui'; @@ -19,42 +20,122 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/voip/callkeep_manager.dart'; -//TODO: maybe introduce a class - -Future pushHelper( - PushNotification notification, { - List? clients, - L10n? l10n, - String? activeRoomId, - Client? activeClient, - required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, - required String instance, -}) async { - try { - await _tryPushHelper( +class PushHelper { + final PushNotification notification; + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; + late Client client; + late Event event; + late bool isBackgroundMessage; + L10n? l10n; + + PushHelper._(this.notification, this.flutterLocalNotificationsPlugin); + + static Future pushHelper( + PushNotification notification, { + List? clients, + L10n? l10n, + String? activeRoomId, + Client? activeClient, + required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, + required String instance, + }) async { + final handler = await _newPushHandler( notification, clients: clients, - instance: instance, l10n: l10n, activeRoomId: activeRoomId, activeClient: activeClient, flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, + instance: instance, ); - } catch (e, s) { + await handler?._showNotification(); + } + + static FutureOr _newPushHandler( + PushNotification notification, { + List? clients, + L10n? l10n, + String? activeRoomId, + Client? activeClient, + required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, + required String instance, + }) async { + final helper = PushHelper._(notification, flutterLocalNotificationsPlugin); + + try { + helper.isBackgroundMessage = clients == null; + Logs().v( + 'Push helper has been started (background=$helper.isBackgroundMessage).', + notification.toJson(), + ); + + clients ??= (await ClientManager.getClients( + initialize: false, + store: await SharedPreferences.getInstance(), + )); + final client = clientFromInstance(instance, clients); + if (client == null) { + Logs().e("Not client could be found for $instance"); + return null; + } + + if (_isInForeground(notification, activeRoomId, activeClient, client)) { + Logs().v('Room is in foreground. Stop push helper here.'); + return null; + } + + final event = await client.getEventByPushNotification( + notification, + storeInDatabase: helper.isBackgroundMessage, + ); + + if (event == null) { + Logs().v('Notification is a clearing indicator.'); + if (notification.counts?.unread == null || + notification.counts?.unread == 0) { + await flutterLocalNotificationsPlugin.cancelAll(); + } else { + // Make sure client is fully loaded and synced before dismiss notifications: + await client.roomsLoading; + await client.oneShotSync(); + final activeNotifications = + await flutterLocalNotificationsPlugin.getActiveNotifications(); + for (final activeNotification in activeNotifications) { + final room = client.rooms.singleWhereOrNull( + (room) => room.id.hashCode == activeNotification.id, + ); + if (room == null || !room.isUnreadOrInvited) { + flutterLocalNotificationsPlugin.cancel(activeNotification.id!); + } + } + } + return null; + } + helper.event = event; + + Logs().v('Push helper got notification event of type ${event.type}.'); + return helper; + } catch (e, s) { + helper._crashHandler(e, s); + rethrow; + } + } + + _crashHandler(e, s) { Logs().v('Push Helper has crashed!', e, s); l10n ??= await lookupL10n(const Locale('en')); flutterLocalNotificationsPlugin.show( notification.roomId?.hashCode ?? 0, - l10n.newMessageInFluffyChat, - l10n.openAppToReadMessages, + l10n!.newMessageInFluffyChat, + l10n!.openAppToReadMessages, NotificationDetails( iOS: const DarwinNotificationDetails(), android: AndroidNotificationDetails( AppConfig.pushNotificationsChannelId, - l10n.incomingMessages, + l10n!.incomingMessages, number: notification.counts?.unread, - ticker: l10n.unreadChatsInApp( + ticker: l10n!.unreadChatsInApp( AppConfig.applicationName, (notification.counts?.unread ?? 0).toString(), ), @@ -64,310 +145,236 @@ Future pushHelper( ), ), ); - rethrow; - } -} - -Future _tryPushHelper( - PushNotification notification, { - List? clients, - L10n? l10n, - String? activeRoomId, - Client? activeClient, - required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, - required String instance, -}) async { - final isBackgroundMessage = clients == null; - Logs().v( - 'Push helper has been started (background=$isBackgroundMessage).', - notification.toJson(), - ); - - clients ??= (await ClientManager.getClients( - initialize: false, - store: await SharedPreferences.getInstance(), - )); - final client = clientFromInstance(instance, clients); - if (client == null) { - Logs().e("Not client could be found for $instance"); - return; } - if (isInForeground(notification, activeRoomId, activeClient, client)) { - Logs().v('Room is in foreground. Stop push helper here.'); - return; - } + Future _showNotification() async { + try { + if (event.type.startsWith('m.call')) { + // make sure bg sync is on (needed to update hold, unhold events) + // prevent over write from app life cycle change + client.backgroundSync = true; + } - final event = await client.getEventByPushNotification( - notification, - storeInDatabase: isBackgroundMessage, - ); - - if (event == null) { - Logs().v('Notification is a clearing indicator.'); - if (notification.counts?.unread == null || - notification.counts?.unread == 0) { - await flutterLocalNotificationsPlugin.cancelAll(); - } else { - // Make sure client is fully loaded and synced before dismiss notifications: - await client.roomsLoading; - await client.oneShotSync(); - final activeNotifications = - await flutterLocalNotificationsPlugin.getActiveNotifications(); - for (final activeNotification in activeNotifications) { - final room = client.rooms.singleWhereOrNull( - (room) => room.id.hashCode == activeNotification.id, - ); - if (room == null || !room.isUnreadOrInvited) { - flutterLocalNotificationsPlugin.cancel(activeNotification.id!); - } + if (event.type == EventTypes.CallInvite) { + CallKeepManager().initialize(); + } else if (event.type == EventTypes.CallHangup) { + client.backgroundSync = false; } - } - return; - } - Logs().v('Push helper got notification event of type ${event.type}.'); - if (event.type.startsWith('m.call')) { - // make sure bg sync is on (needed to update hold, unhold events) - // prevent over write from app life cycle change - client.backgroundSync = true; - } + if ((event.type.startsWith('m.call') && + event.type != EventTypes.CallInvite) || + event.type == EventTypes.CallSDPStreamMetadataChangedPrefix) { + Logs().v('Push message was for a call, but not call invite.'); + return; + } - if (event.type == EventTypes.CallInvite) { - CallKeepManager().initialize(); - } else if (event.type == EventTypes.CallHangup) { - client.backgroundSync = false; + l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); + final locals = MatrixLocals(l10n!); + + // Calculate the body + final body = event.type == EventTypes.Encrypted + ? l10n!.newMessageInFluffyChat + : await event.calcLocalizedBody( + locals, + plaintextBody: true, + withSenderNamePrefix: false, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ); + + final id = notification.roomId.hashCode; + final title = event.room.getLocalizedDisplayname(locals); + final roomName = event.room.getLocalizedDisplayname(locals); + + var notificationGroupId = + event.room.isDirectChat ? 'directChats' : 'groupChats'; + notificationGroupId += client.clientName; + final groupName = + event.room.isDirectChat ? l10n!.directChats : l10n!.groups; + + final messageRooms = AndroidNotificationChannelGroup( + notificationGroupId, + groupName, + ); + final roomsChannel = AndroidNotificationChannel( + event.room.id, + roomName, + groupId: notificationGroupId, + ); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannelGroup(messageRooms); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(roomsChannel); + + final platformChannelSpecifics = await _getPlatformChannelSpecifics( + id, + body, + title, + roomName, + ); + + await flutterLocalNotificationsPlugin.show( + id, + title, + body, + platformChannelSpecifics, + payload: jsonEncode( + NotificationResponsePayload( + event.roomId ?? "", + client.clientName, + ).toJson(), + ), + ); + Logs().v('Push helper has been completed!'); + } catch (e, s) { + _crashHandler(e, s); + rethrow; + } } - if ((event.type.startsWith('m.call') && - event.type != EventTypes.CallInvite) || - event.type == EventTypes.CallSDPStreamMetadataChangedPrefix) { - Logs().v('Push message was for a call, but not call invite.'); - return; - } + Future _getPlatformChannelSpecifics( + int notificationId, + String notificationBody, + String notificationTitle, + String roomName, + ) async { + // The person object for the android message style notification + final avatar = event.room.avatar; + final senderAvatar = event.room.isDirectChat + ? avatar + : event.senderFromMemoryOrFallback.avatarUrl; + + final roomAvatarFile = await _getAvatarFile(client, avatar); + final senderAvatarFile = event.room.isDirectChat + ? roomAvatarFile + : await _getAvatarFile(client, senderAvatar); + + // Show notification + final newMessage = Message( + notificationBody, + event.originServerTs, + Person( + bot: event.messageType == MessageTypes.Notice, + key: event.senderId, + name: event.senderFromMemoryOrFallback.calcDisplayname(), + icon: senderAvatarFile == null + ? null + : ByteArrayAndroidIcon(senderAvatarFile), + ), + ); - showNotification( - l10n, event, notification, client, flutterLocalNotificationsPlugin); - Logs().v('Push helper has been completed!'); -} + final messagingStyleInformation = PlatformInfos.isAndroid + ? await AndroidFlutterLocalNotificationsPlugin() + .getActiveNotificationMessagingStyle(notificationId) + : null; + messagingStyleInformation?.messages?.add(newMessage); -Future showNotification( - L10n? l10n, - Event event, - PushNotification notification, - Client client, - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, -) async { - l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); - final locals = MatrixLocals(l10n); - - // Calculate the body - final body = event.type == EventTypes.Encrypted - ? l10n.newMessageInFluffyChat - : await event.calcLocalizedBody( - locals, - plaintextBody: true, - withSenderNamePrefix: false, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ); - - final id = notification.roomId.hashCode; - final title = event.room.getLocalizedDisplayname(locals); - final roomName = event.room.getLocalizedDisplayname(locals); - - var notificationGroupId = - event.room.isDirectChat ? 'directChats' : 'groupChats'; - notificationGroupId += client.clientName; - final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; - - final messageRooms = AndroidNotificationChannelGroup( - notificationGroupId, - groupName, - ); - final roomsChannel = AndroidNotificationChannel( - event.room.id, - roomName, - groupId: notificationGroupId, - ); - - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannelGroup(messageRooms); - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(roomsChannel); - - final platformChannelSpecifics = await getPlatformChannelSpecifics( - l10n, - notification, - event, - client, - id, - body, - title, - roomName, - ); - - return await flutterLocalNotificationsPlugin.show( - id, - title, - body, - platformChannelSpecifics, - payload: jsonEncode( - NotificationResponsePayload( - event.roomId ?? "", - client.clientName, - ).toJson(), - ), - ); -} + if (PlatformInfos.isAndroid && messagingStyleInformation == null) { + await _setShortcut(notificationTitle, roomAvatarFile); + } -Future getPlatformChannelSpecifics( - L10n l10n, - PushNotification notification, - Event event, - Client client, - int notificationId, - String notificationBody, - String notificationTitle, - String roomName, -) async { - // The person object for the android message style notification - final avatar = event.room.avatar; - final senderAvatar = event.room.isDirectChat - ? avatar - : event.senderFromMemoryOrFallback.avatarUrl; - - final roomAvatarFile = await getAvatarFile(client, avatar); - final senderAvatarFile = event.room.isDirectChat - ? roomAvatarFile - : await getAvatarFile(client, senderAvatar); - - // Show notification - final newMessage = Message( - notificationTitle, - event.originServerTs, - Person( - bot: event.messageType == MessageTypes.Notice, - key: event.senderId, - name: event.senderFromMemoryOrFallback.calcDisplayname(), - icon: senderAvatarFile == null - ? null - : ByteArrayAndroidIcon(senderAvatarFile), - ), - ); - - final messagingStyleInformation = PlatformInfos.isAndroid - ? await AndroidFlutterLocalNotificationsPlugin() - .getActiveNotificationMessagingStyle(notificationId) - : null; - messagingStyleInformation?.messages?.add(newMessage); - - if (PlatformInfos.isAndroid && messagingStyleInformation == null) { - await _setShortcut(event, l10n, notificationTitle, roomAvatarFile); + final androidPlatformChannelSpecifics = AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n!.incomingMessages, + number: notification.counts?.unread, + category: AndroidNotificationCategory.message, + shortcutId: event.room.id, + styleInformation: messagingStyleInformation ?? + MessagingStyleInformation( + Person( + name: event.senderFromMemoryOrFallback.calcDisplayname(), + icon: roomAvatarFile == null + ? null + : ByteArrayAndroidIcon(roomAvatarFile), + key: event.roomId, + important: event.room.isFavourite, + ), + conversationTitle: roomName, + groupConversation: !event.room.isDirectChat, + messages: [newMessage], + ), + ticker: event.calcLocalizedBodyFallback( + MatrixLocals(l10n!), + plaintextBody: true, + withSenderNamePrefix: true, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ), + importance: Importance.high, + priority: Priority.max, + groupKey: event.room.spaceParents.firstOrNull?.roomId ?? 'rooms', + ); + const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); + return NotificationDetails( + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics, + ); } - final androidPlatformChannelSpecifics = AndroidNotificationDetails( - AppConfig.pushNotificationsChannelId, - l10n.incomingMessages, - number: notification.counts?.unread, - category: AndroidNotificationCategory.message, - shortcutId: event.room.id, - styleInformation: messagingStyleInformation ?? - MessagingStyleInformation( - Person( - name: event.senderFromMemoryOrFallback.calcDisplayname(), - icon: roomAvatarFile == null - ? null - : ByteArrayAndroidIcon(roomAvatarFile), - key: event.roomId, - important: event.room.isFavourite, - ), - conversationTitle: roomName, - groupConversation: !event.room.isDirectChat, - messages: [newMessage], - ), - ticker: event.calcLocalizedBodyFallback( - MatrixLocals(l10n), - plaintextBody: true, - withSenderNamePrefix: true, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ), - importance: Importance.high, - priority: Priority.max, - groupKey: event.room.spaceParents.firstOrNull?.roomId ?? 'rooms', - ); - const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); - return NotificationDetails( - android: androidPlatformChannelSpecifics, - iOS: iOSPlatformChannelSpecifics, - ); -} + /// Creates a shortcut for Android platform but does not block displaying the + /// notification. This is optional but provides a nicer view of the + /// notification popup. + Future _setShortcut( + String title, + Uint8List? avatarFile, + ) async { + final flutterShortcuts = FlutterShortcuts(); + await flutterShortcuts.initialize(debug: !kReleaseMode); + await flutterShortcuts.pushShortcutItem( + shortcut: ShortcutItem( + id: event.room.id, + action: AppConfig.inviteLinkPrefix + event.room.id, + shortLabel: title, + conversationShortcut: true, + icon: avatarFile == null + ? null + : ShortcutMemoryIcon(jpegImage: avatarFile).toString(), + shortcutIconAsset: avatarFile == null + ? ShortcutIconAsset.androidAsset + : ShortcutIconAsset.memoryAsset, + isImportant: event.room.isFavourite, + ), + ); + } -/// Creates a shortcut for Android platform but does not block displaying the -/// notification. This is optional but provides a nicer view of the -/// notification popup. -Future _setShortcut( - Event event, - L10n l10n, - String title, - Uint8List? avatarFile, -) async { - final flutterShortcuts = FlutterShortcuts(); - await flutterShortcuts.initialize(debug: !kReleaseMode); - await flutterShortcuts.pushShortcutItem( - shortcut: ShortcutItem( - id: event.room.id, - action: AppConfig.inviteLinkPrefix + event.room.id, - shortLabel: title, - conversationShortcut: true, - icon: avatarFile == null + static Future _getAvatarFile(Client client, Uri? avatar) async { + try { + return avatar == null ? null - : ShortcutMemoryIcon(jpegImage: avatarFile).toString(), - shortcutIconAsset: avatarFile == null - ? ShortcutIconAsset.androidAsset - : ShortcutIconAsset.memoryAsset, - isImportant: event.room.isFavourite, - ), - ); -} - -Future getAvatarFile(Client client, Uri? avatar) async { - try { - return avatar == null - ? null - : await client - .downloadMxcCached( - avatar, - thumbnailMethod: ThumbnailMethod.scale, - width: 256, - height: 256, - animated: false, - isThumbnail: true, - ) - .timeout(const Duration(seconds: 3)); - } catch (e, s) { - Logs().e('Unable to get avatar picture', e, s); - return null; + : await client + .downloadMxcCached( + avatar, + thumbnailMethod: ThumbnailMethod.scale, + width: 256, + height: 256, + animated: false, + isThumbnail: true, + ) + .timeout(const Duration(seconds: 3)); + } catch (e, s) { + Logs().e('Unable to get avatar picture', e, s); + return null; + } } -} -bool isInForeground( - PushNotification notification, - String? activeRoomId, - Client? activeClient, - Client notifiedClient, -) { - return notification.roomId != null && - activeRoomId == notification.roomId && - activeClient == notifiedClient && - WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed; + static bool _isInForeground( + PushNotification notification, + String? activeRoomId, + Client? activeClient, + Client notifiedClient, + ) { + return notification.roomId != null && + activeRoomId == notification.roomId && + activeClient == notifiedClient && + WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed; + } } class NotificationResponsePayload { From 967aabbf7f49438ee65be10179dd81241b1569b5 Mon Sep 17 00:00:00 2001 From: baltevl Date: Sat, 2 Nov 2024 14:02:53 +0100 Subject: [PATCH 6/6] fix: Fix small bugs introduced by refactor --- lib/utils/background_push.dart | 14 ++++++++------ lib/utils/push_helper.dart | 33 +++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 6d073af56f..3a4e3a55c4 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -259,8 +259,9 @@ class BackgroundPush { final endpoint = matrix!.store.getString(SettingKeys.unifiedPushEndpoint); if (endpoint != null) { matrix!.store.setString( - clients.first.clientName + SettingKeys.unifiedPushEndpoint, - endpoint,); + clients.first.clientName + SettingKeys.unifiedPushEndpoint, + endpoint, + ); matrix!.store.remove(SettingKeys.unifiedPushEndpoint); } @@ -268,8 +269,9 @@ class BackgroundPush { matrix!.store.getBool(SettingKeys.unifiedPushRegistered); if (registered != null) { matrix!.store.setBool( - clients.first.clientName + SettingKeys.unifiedPushRegistered, - registered,); + clients.first.clientName + SettingKeys.unifiedPushRegistered, + registered, + ); matrix!.store.remove(SettingKeys.unifiedPushRegistered); } } @@ -377,9 +379,9 @@ class BackgroundPush { await client.roomsLoading; await client.accountDataLoading; - if (client.getRoomById(roomId) == null) { + if (client.getRoomById(payload.roomId) == null) { await client - .waitForRoomInSync(roomId) + .waitForRoomInSync(payload.roomId) .timeout(const Duration(seconds: 30)); } FluffyChatApp.router.go( diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 1b803d7664..d0e21b749a 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -39,16 +39,20 @@ class PushHelper { required FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin, required String instance, }) async { - final handler = await _newPushHandler( - notification, - clients: clients, - l10n: l10n, - activeRoomId: activeRoomId, - activeClient: activeClient, - flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, - instance: instance, - ); - await handler?._showNotification(); + try { + final handler = await _newPushHandler( + notification, + clients: clients, + l10n: l10n, + activeRoomId: activeRoomId, + activeClient: activeClient, + flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, + instance: instance, + ); + await handler?._showNotification(); + } catch (e) { + rethrow; + } } static FutureOr _newPushHandler( @@ -65,7 +69,7 @@ class PushHelper { try { helper.isBackgroundMessage = clients == null; Logs().v( - 'Push helper has been started (background=$helper.isBackgroundMessage).', + 'Push helper has been started (background=${helper.isBackgroundMessage}).', notification.toJson(), ); @@ -78,6 +82,7 @@ class PushHelper { Logs().e("Not client could be found for $instance"); return null; } + helper.client = client; if (_isInForeground(notification, activeRoomId, activeClient, client)) { Logs().v('Room is in foreground. Stop push helper here.'); @@ -116,12 +121,12 @@ class PushHelper { Logs().v('Push helper got notification event of type ${event.type}.'); return helper; } catch (e, s) { - helper._crashHandler(e, s); + await helper._crashHandler(e, s); rethrow; } } - _crashHandler(e, s) { + _crashHandler(e, s) async { Logs().v('Push Helper has crashed!', e, s); l10n ??= await lookupL10n(const Locale('en')); @@ -233,7 +238,7 @@ class PushHelper { ); Logs().v('Push helper has been completed!'); } catch (e, s) { - _crashHandler(e, s); + await _crashHandler(e, s); rethrow; } }