refactor: Only initialize FlutterLocalNotificationsPlugin once

This commit is contained in:
krille-chan 2024-08-01 09:18:12 +02:00
commit d3a13705bd
No known key found for this signature in database
2 changed files with 309 additions and 296 deletions

View file

@ -71,7 +71,7 @@ class BackgroundPush {
BackgroundPush._(this.client) { BackgroundPush._(this.client) {
firebase?.setListeners( firebase?.setListeners(
onMessage: (message) => pushHelper( onMessage: (message) => PushHelper.processNotification(
PushNotification.fromJson( PushNotification.fromJson(
Map<String, dynamic>.from(message['data'] ?? message), Map<String, dynamic>.from(message['data'] ?? message),
), ),
@ -393,7 +393,7 @@ class BackgroundPush {
); );
// UP may strip the devices list // UP may strip the devices list
data['devices'] ??= []; data['devices'] ??= [];
await pushHelper( await PushHelper.processNotification(
PushNotification.fromJson(data), PushNotification.fromJson(data),
client: client, client: client,
l10n: l10n, l10n: l10n,

View file

@ -18,27 +18,20 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/voip/callkeep_manager.dart'; import 'package:fluffychat/utils/voip/callkeep_manager.dart';
Future<void> pushHelper( abstract class PushHelper {
PushNotification notification, { static FlutterLocalNotificationsPlugin? _flutterLocalNotificationsPlugin;
Client? client,
L10n? l10n,
String? activeRoomId,
void Function(NotificationResponse?)? onSelectNotification,
}) async {
try {
await _tryPushHelper(
notification,
client: client,
l10n: l10n,
activeRoomId: activeRoomId,
onSelectNotification: onSelectNotification,
);
} catch (e, s) {
Logs().v('Push Helper has crashed!', e, s);
// Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project static Future<FlutterLocalNotificationsPlugin> _getLocalNotificationsPlugin({
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); void Function(NotificationResponse?)? onSelectNotification,
await flutterLocalNotificationsPlugin.initialize( }) async {
var flutterlocalNotifcationsPlugin = _flutterLocalNotificationsPlugin;
if (flutterlocalNotifcationsPlugin != null) {
return flutterlocalNotifcationsPlugin;
}
flutterlocalNotifcationsPlugin =
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterlocalNotifcationsPlugin.initialize(
const InitializationSettings( const InitializationSettings(
android: AndroidInitializationSettings('notifications_icon'), android: AndroidInitializationSettings('notifications_icon'),
iOS: DarwinInitializationSettings(), iOS: DarwinInitializationSettings(),
@ -46,299 +39,319 @@ Future<void> pushHelper(
onDidReceiveNotificationResponse: onSelectNotification, onDidReceiveNotificationResponse: onSelectNotification,
onDidReceiveBackgroundNotificationResponse: onSelectNotification, onDidReceiveBackgroundNotificationResponse: onSelectNotification,
); );
return flutterlocalNotifcationsPlugin;
}
l10n ??= lookupL10n(const Locale('en')); static Future<void> processNotification(
flutterLocalNotificationsPlugin.show( PushNotification notification, {
notification.roomId?.hashCode ?? 0, Client? client,
l10n.newMessageInFluffyChat, L10n? l10n,
l10n.openAppToReadMessages, String? activeRoomId,
NotificationDetails( void Function(NotificationResponse?)? onSelectNotification,
iOS: const DarwinNotificationDetails(), }) async {
android: AndroidNotificationDetails( try {
AppConfig.pushNotificationsChannelId, await _tryPushHelper(
l10n.incomingMessages, notification,
number: notification.counts?.unread, client: client,
ticker: l10n.unreadChatsInApp( l10n: l10n,
AppConfig.applicationName, activeRoomId: activeRoomId,
(notification.counts?.unread ?? 0).toString(), onSelectNotification: onSelectNotification,
);
} catch (e, s) {
Logs().v('Push Helper has crashed!', e, s);
final flutterLocalNotificationsPlugin =
await _getLocalNotificationsPlugin(
onSelectNotification: onSelectNotification,
);
l10n ??= lookupL10n(const Locale('en'));
flutterLocalNotificationsPlugin.show(
notification.roomId?.hashCode ?? 0,
l10n.newMessageInFluffyChat,
l10n.openAppToReadMessages,
NotificationDetails(
iOS: const DarwinNotificationDetails(),
android: AndroidNotificationDetails(
AppConfig.pushNotificationsChannelId,
l10n.incomingMessages,
number: notification.counts?.unread,
ticker: l10n.unreadChatsInApp(
AppConfig.applicationName,
(notification.counts?.unread ?? 0).toString(),
),
importance: Importance.high,
priority: Priority.max,
shortcutId: notification.roomId,
), ),
importance: Importance.high,
priority: Priority.max,
shortcutId: notification.roomId,
), ),
), );
rethrow;
}
}
static Future<void> _tryPushHelper(
PushNotification notification, {
Client? client,
L10n? l10n,
String? activeRoomId,
void Function(NotificationResponse?)? onSelectNotification,
}) async {
final isBackgroundMessage = client == null;
Logs().v(
'Push helper has been started (background=$isBackgroundMessage).',
notification.toJson(),
); );
rethrow;
}
}
Future<void> _tryPushHelper( if (notification.roomId != null &&
PushNotification notification, { activeRoomId == notification.roomId &&
Client? client, WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
L10n? l10n, Logs().v('Room is in foreground. Stop push helper here.');
String? activeRoomId, return;
void Function(NotificationResponse?)? onSelectNotification, }
}) async {
final isBackgroundMessage = client == null;
Logs().v(
'Push helper has been started (background=$isBackgroundMessage).',
notification.toJson(),
);
if (notification.roomId != null && final flutterLocalNotificationsPlugin = await _getLocalNotificationsPlugin(
activeRoomId == notification.roomId && onSelectNotification: onSelectNotification,
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { );
Logs().v('Room is in foreground. Stop push helper here.');
return;
}
// Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project client ??= (await ClientManager.getClients(
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); initialize: false,
await flutterLocalNotificationsPlugin.initialize( store: await SharedPreferences.getInstance(),
const InitializationSettings( ))
android: AndroidInitializationSettings('notifications_icon'), .first;
iOS: DarwinInitializationSettings(), final event = await client.getEventByPushNotification(
), notification,
onDidReceiveNotificationResponse: onSelectNotification, storeInDatabase: isBackgroundMessage,
//onDidReceiveBackgroundNotificationResponse: onSelectNotification, );
);
client ??= (await ClientManager.getClients( if (event == null) {
initialize: false, Logs().v('Notification is a clearing indicator.');
store: await SharedPreferences.getInstance(), if (notification.counts?.unread == null ||
)) notification.counts?.unread == 0) {
.first; await flutterLocalNotificationsPlugin.cancelAll();
final event = await client.getEventByPushNotification( } else {
notification, // Make sure client is fully loaded and synced before dismiss notifications:
storeInDatabase: isBackgroundMessage, await client.roomsLoading;
); await client.oneShotSync();
final activeNotifications =
if (event == null) { await flutterLocalNotificationsPlugin.getActiveNotifications();
Logs().v('Notification is a clearing indicator.'); for (final activeNotification in activeNotifications) {
if (notification.counts?.unread == null || final room = client.rooms.singleWhereOrNull(
notification.counts?.unread == 0) { (room) => room.id.hashCode == activeNotification.id,
await flutterLocalNotificationsPlugin.cancelAll(); );
} else { if (room == null || !room.isUnreadOrInvited) {
// Make sure client is fully loaded and synced before dismiss notifications: flutterLocalNotificationsPlugin.cancel(activeNotification.id!);
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;
} }
return; Logs().v('Push helper got notification event of type ${event.type}.');
}
Logs().v('Push helper got notification event of type ${event.type}.');
if (event.type.startsWith('m.call')) { if (event.type.startsWith('m.call')) {
// make sure bg sync is on (needed to update hold, unhold events) // make sure bg sync is on (needed to update hold, unhold events)
// prevent over write from app life cycle change // prevent over write from app life cycle change
client.backgroundSync = true; client.backgroundSync = true;
} }
if (event.type == EventTypes.CallInvite) { if (event.type == EventTypes.CallInvite) {
CallKeepManager().initialize(); CallKeepManager().initialize();
} else if (event.type == EventTypes.CallHangup) { } else if (event.type == EventTypes.CallHangup) {
client.backgroundSync = false; client.backgroundSync = false;
} }
if (event.type.startsWith('m.call') && event.type != EventTypes.CallInvite) { if (event.type.startsWith('m.call') &&
Logs().v('Push message is a m.call but not invite. Do not display.'); event.type != EventTypes.CallInvite) {
return; Logs().v('Push message is a m.call but not invite. Do not display.');
} return;
}
if ((event.type.startsWith('m.call') && if ((event.type.startsWith('m.call') &&
event.type != EventTypes.CallInvite) || event.type != EventTypes.CallInvite) ||
event.type == 'org.matrix.call.sdp_stream_metadata_changed') { event.type == 'org.matrix.call.sdp_stream_metadata_changed') {
Logs().v('Push message was for a call, but not call invite.'); Logs().v('Push message was for a call, but not call invite.');
return; return;
} }
l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale); l10n ??= await L10n.delegate.load(PlatformDispatcher.instance.locale);
final matrixLocals = MatrixLocals(l10n); final matrixLocals = MatrixLocals(l10n);
// Calculate the body // Calculate the body
final body = event.type == EventTypes.Encrypted final body = event.type == EventTypes.Encrypted
? l10n.newMessageInFluffyChat ? l10n.newMessageInFluffyChat
: await event.calcLocalizedBody( : await event.calcLocalizedBody(
matrixLocals, matrixLocals,
plaintextBody: true, plaintextBody: true,
withSenderNamePrefix: false, withSenderNamePrefix: false,
hideReply: true, hideReply: true,
hideEdit: true, hideEdit: true,
removeMarkdown: true, removeMarkdown: true,
); );
// The person object for the android message style notification // The person object for the android message style notification
final avatar = event.room.avatar final avatar = event.room.avatar
?.getThumbnail( ?.getThumbnail(
client, client,
width: 256, width: 256,
height: 256, height: 256,
) )
.toString(); .toString();
final senderAvatar = event.room.isDirectChat final senderAvatar = event.room.isDirectChat
? avatar ? avatar
: event.senderFromMemoryOrFallback.avatarUrl : event.senderFromMemoryOrFallback.avatarUrl
?.getThumbnail( ?.getThumbnail(
client, client,
width: 256, width: 256,
height: 256, height: 256,
) )
.toString(); .toString();
File? roomAvatarFile, senderAvatarFile; File? roomAvatarFile, senderAvatarFile;
try { try {
roomAvatarFile = avatar == null roomAvatarFile = avatar == null
? null ? null
: await DefaultCacheManager().getSingleFile(avatar); : await DefaultCacheManager().getSingleFile(avatar);
} catch (e, s) { } catch (e, s) {
Logs().e('Unable to get avatar picture', e, s); Logs().e('Unable to get avatar picture', e, s);
} }
try { try {
senderAvatarFile = event.room.isDirectChat senderAvatarFile = event.room.isDirectChat
? roomAvatarFile ? roomAvatarFile
: senderAvatar == null : senderAvatar == null
? null
: await DefaultCacheManager().getSingleFile(senderAvatar);
} catch (e, s) {
Logs().e('Unable to get avatar picture', e, s);
}
final id = notification.roomId.hashCode;
// Show notification
final newMessage = Message(
body,
event.originServerTs,
Person(
bot: event.messageType == MessageTypes.Notice,
key: event.senderId,
name: event.senderFromMemoryOrFallback.calcDisplayname(),
icon: senderAvatarFile == null
? null ? null
: await DefaultCacheManager().getSingleFile(senderAvatar); : BitmapFilePathAndroidIcon(senderAvatarFile.path),
} catch (e, s) { ),
Logs().e('Unable to get avatar picture', e, s); );
}
final id = notification.roomId.hashCode; final messagingStyleInformation = PlatformInfos.isAndroid
? await AndroidFlutterLocalNotificationsPlugin()
.getActiveNotificationMessagingStyle(id)
: null;
messagingStyleInformation?.messages?.add(newMessage);
// Show notification final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n));
final newMessage = Message( final notificationGroupId =
body, event.room.isDirectChat ? 'directChats' : 'groupChats';
event.originServerTs, final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups;
Person(
bot: event.messageType == MessageTypes.Notice,
key: event.senderId,
name: event.senderFromMemoryOrFallback.calcDisplayname(),
icon: senderAvatarFile == null
? null
: BitmapFilePathAndroidIcon(senderAvatarFile.path),
),
);
final messagingStyleInformation = PlatformInfos.isAndroid final messageRooms = AndroidNotificationChannelGroup(
? await AndroidFlutterLocalNotificationsPlugin() notificationGroupId,
.getActiveNotificationMessagingStyle(id) groupName,
: null; );
messagingStyleInformation?.messages?.add(newMessage); final roomsChannel = AndroidNotificationChannel(
event.room.id,
roomName,
groupId: notificationGroupId,
);
final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannelGroup(messageRooms);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(roomsChannel);
final notificationGroupId = final androidPlatformChannelSpecifics = AndroidNotificationDetails(
event.room.isDirectChat ? 'directChats' : 'groupChats'; AppConfig.pushNotificationsChannelId,
final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups; l10n.incomingMessages,
number: notification.counts?.unread,
final messageRooms = AndroidNotificationChannelGroup( category: AndroidNotificationCategory.message,
notificationGroupId, shortcutId: event.room.id,
groupName, styleInformation: messagingStyleInformation ??
); MessagingStyleInformation(
final roomsChannel = AndroidNotificationChannel( Person(
event.room.id, name: event.senderFromMemoryOrFallback.calcDisplayname(),
roomName, icon: roomAvatarFile == null
groupId: notificationGroupId, ? null
); : BitmapFilePathAndroidIcon(roomAvatarFile.path),
key: event.roomId,
await flutterLocalNotificationsPlugin important: event.room.isFavourite,
.resolvePlatformSpecificImplementation< ),
AndroidFlutterLocalNotificationsPlugin>() conversationTitle: roomName,
?.createNotificationChannelGroup(messageRooms); groupConversation: !event.room.isDirectChat,
await flutterLocalNotificationsPlugin messages: [newMessage],
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(roomsChannel);
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
: BitmapFilePathAndroidIcon(roomAvatarFile.path),
key: event.roomId,
important: event.room.isFavourite,
), ),
conversationTitle: roomName, ticker: event.calcLocalizedBodyFallback(
groupConversation: !event.room.isDirectChat, matrixLocals,
messages: [newMessage], plaintextBody: true,
), withSenderNamePrefix: true,
ticker: event.calcLocalizedBodyFallback( hideReply: true,
matrixLocals, hideEdit: true,
plaintextBody: true, removeMarkdown: true,
withSenderNamePrefix: true, ),
hideReply: true, importance: Importance.high,
hideEdit: true, priority: Priority.max,
removeMarkdown: true, groupKey: notificationGroupId,
), );
importance: Importance.high, const iOSPlatformChannelSpecifics = DarwinNotificationDetails();
priority: Priority.max, final platformChannelSpecifics = NotificationDetails(
groupKey: notificationGroupId, android: androidPlatformChannelSpecifics,
); iOS: iOSPlatformChannelSpecifics,
const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); );
final platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n)); final title = event.room.getLocalizedDisplayname(MatrixLocals(l10n));
if (PlatformInfos.isAndroid && messagingStyleInformation == null) { if (PlatformInfos.isAndroid && messagingStyleInformation == null) {
await _setShortcut(event, l10n, title, roomAvatarFile); await _setShortcut(event, l10n, title, roomAvatarFile);
}
await flutterLocalNotificationsPlugin.show(
id,
title,
body,
platformChannelSpecifics,
payload: event.roomId,
);
Logs().v('Push helper has been completed!');
} }
await flutterLocalNotificationsPlugin.show( /// Creates a shortcut for Android platform but does not block displaying the
id, /// notification. This is optional but provides a nicer view of the
title, /// notification popup.
body, static Future<void> _setShortcut(
platformChannelSpecifics, Event event,
payload: event.roomId, L10n l10n,
); String title,
Logs().v('Push helper has been completed!'); File? avatarFile,
} ) async {
final flutterShortcuts = FlutterShortcuts();
/// Creates a shortcut for Android platform but does not block displaying the await flutterShortcuts.initialize(debug: !kReleaseMode);
/// notification. This is optional but provides a nicer view of the await flutterShortcuts.pushShortcutItem(
/// notification popup. shortcut: ShortcutItem(
Future<void> _setShortcut( id: event.room.id,
Event event, action: AppConfig.inviteLinkPrefix + event.room.id,
L10n l10n, shortLabel: title,
String title, conversationShortcut: true,
File? avatarFile, icon: avatarFile == null
) async { ? null
final flutterShortcuts = FlutterShortcuts(); : ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes())
await flutterShortcuts.initialize(debug: !kReleaseMode); .toString(),
await flutterShortcuts.pushShortcutItem( shortcutIconAsset: avatarFile == null
shortcut: ShortcutItem( ? ShortcutIconAsset.androidAsset
id: event.room.id, : ShortcutIconAsset.memoryAsset,
action: AppConfig.inviteLinkPrefix + event.room.id, isImportant: event.room.isFavourite,
shortLabel: title, ),
conversationShortcut: true, );
icon: avatarFile == null }
? null
: ShortcutMemoryIcon(jpegImage: await avatarFile.readAsBytes())
.toString(),
shortcutIconAsset: avatarFile == null
? ShortcutIconAsset.androidAsset
: ShortcutIconAsset.memoryAsset,
isImportant: event.room.isFavourite,
),
);
} }