diff --git a/packages/neon/integration_test/screenshot_test.dart b/packages/neon/integration_test/screenshot_test.dart index 4090701c..6adab889 100644 --- a/packages/neon/integration_test/screenshot_test.dart +++ b/packages/neon/integration_test/screenshot_test.dart @@ -126,7 +126,6 @@ Future pumpAppPage( accountsBloc, sharedPreferences, globalOptions, - null, platform, ); diff --git a/packages/neon/lib/l10n/en.arb b/packages/neon/lib/l10n/en.arb index 1bb7cc90..75809a42 100644 --- a/packages/neon/lib/l10n/en.arb +++ b/packages/neon/lib/l10n/en.arb @@ -81,16 +81,16 @@ "globalOptionsThemeModeAutomatic": "Automatic", "globalOptionsThemeOLEDAsDark": "OLED theme as dark theme", "globalOptionsThemeKeepOriginalAccentColor": "Keep the original accent color", - "globalOptionsPushNotificationsNotice": "External services are used for delivering push notifications. While the content is encrypted and can only be read by this app, extracting metadata like the time and count of notifications is still possible.", "globalOptionsPushNotificationsEnabled": "Enabled", - "globalOptionsPushNotificationsEnabledDisabledNotice": "No UnifiedPush distributor could be found. Please go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications", + "globalOptionsPushNotificationsEnabledDisabledNotice": "No UnifiedPush distributor could be found or you denied the permission for showing notifications. Please go to the app settings and allow notifications and go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications", "globalOptionsPushNotificationsDistributor": "UnifiedPush Distributor", - "globalOptionsPushNotificationsDistributorGotifyUP": "Gotify-UP", - "globalOptionsPushNotificationsDistributorFirebaseEmbedded": "Firebase (FOSS)", - "globalOptionsPushNotificationsDistributorNtfy": "ntfy", - "globalOptionsPushNotificationsDistributorFCMUP": "FCM-UP", - "globalOptionsPushNotificationsDistributorNextPush": "NextPush", - "globalOptionsPushNotificationsDistributorNoProvider2Push": "NoProvider2Push", + "globalOptionsPushNotificationsDistributorGotifyUP": "Gotify-UP (FOSS)", + "globalOptionsPushNotificationsDistributorFirebaseEmbedded": "Firebase (proprietary)", + "globalOptionsPushNotificationsDistributorNtfy": "ntfy (FOSS)", + "globalOptionsPushNotificationsDistributorFCMUP": "FCM-UP (proprietary)", + "globalOptionsPushNotificationsDistributorNextPush": "NextPush (FOSS)", + "globalOptionsPushNotificationsDistributorNoProvider2Push": "NoProvider2Push (FOSS)", + "globalOptionsPushNotificationsDistributorConversations": "Conversations", "globalOptionsStartupMinimized": "Start minimized", "globalOptionsStartupMinimizeInsteadOfExit": "Minimize instead of exit", "globalOptionsSystemTrayEnabled": "Enable system tray", diff --git a/packages/neon/lib/l10n/localizations.dart b/packages/neon/lib/l10n/localizations.dart index d57084e0..1fea3412 100644 --- a/packages/neon/lib/l10n/localizations.dart +++ b/packages/neon/lib/l10n/localizations.dart @@ -407,12 +407,6 @@ abstract class AppLocalizations { /// **'Keep the original accent color'** String get globalOptionsThemeKeepOriginalAccentColor; - /// No description provided for @globalOptionsPushNotificationsNotice. - /// - /// In en, this message translates to: - /// **'External services are used for delivering push notifications. While the content is encrypted and can only be read by this app, extracting metadata like the time and count of notifications is still possible.'** - String get globalOptionsPushNotificationsNotice; - /// No description provided for @globalOptionsPushNotificationsEnabled. /// /// In en, this message translates to: @@ -422,7 +416,7 @@ abstract class AppLocalizations { /// No description provided for @globalOptionsPushNotificationsEnabledDisabledNotice. /// /// In en, this message translates to: - /// **'No UnifiedPush distributor could be found. Please go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications'** + /// **'No UnifiedPush distributor could be found or you denied the permission for showing notifications. Please go to the app settings and allow notifications and go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications'** String get globalOptionsPushNotificationsEnabledDisabledNotice; /// No description provided for @globalOptionsPushNotificationsDistributor. @@ -434,39 +428,45 @@ abstract class AppLocalizations { /// No description provided for @globalOptionsPushNotificationsDistributorGotifyUP. /// /// In en, this message translates to: - /// **'Gotify-UP'** + /// **'Gotify-UP (FOSS)'** String get globalOptionsPushNotificationsDistributorGotifyUP; /// No description provided for @globalOptionsPushNotificationsDistributorFirebaseEmbedded. /// /// In en, this message translates to: - /// **'Firebase (FOSS)'** + /// **'Firebase (proprietary)'** String get globalOptionsPushNotificationsDistributorFirebaseEmbedded; /// No description provided for @globalOptionsPushNotificationsDistributorNtfy. /// /// In en, this message translates to: - /// **'ntfy'** + /// **'ntfy (FOSS)'** String get globalOptionsPushNotificationsDistributorNtfy; /// No description provided for @globalOptionsPushNotificationsDistributorFCMUP. /// /// In en, this message translates to: - /// **'FCM-UP'** + /// **'FCM-UP (proprietary)'** String get globalOptionsPushNotificationsDistributorFCMUP; /// No description provided for @globalOptionsPushNotificationsDistributorNextPush. /// /// In en, this message translates to: - /// **'NextPush'** + /// **'NextPush (FOSS)'** String get globalOptionsPushNotificationsDistributorNextPush; /// No description provided for @globalOptionsPushNotificationsDistributorNoProvider2Push. /// /// In en, this message translates to: - /// **'NoProvider2Push'** + /// **'NoProvider2Push (FOSS)'** String get globalOptionsPushNotificationsDistributorNoProvider2Push; + /// No description provided for @globalOptionsPushNotificationsDistributorConversations. + /// + /// In en, this message translates to: + /// **'Conversations'** + String get globalOptionsPushNotificationsDistributorConversations; + /// No description provided for @globalOptionsStartupMinimized. /// /// In en, this message translates to: diff --git a/packages/neon/lib/l10n/localizations_en.dart b/packages/neon/lib/l10n/localizations_en.dart index 76d8f9e4..45f2f92b 100644 --- a/packages/neon/lib/l10n/localizations_en.dart +++ b/packages/neon/lib/l10n/localizations_en.dart @@ -174,37 +174,36 @@ class AppLocalizationsEn extends AppLocalizations { @override String get globalOptionsThemeKeepOriginalAccentColor => 'Keep the original accent color'; - @override - String get globalOptionsPushNotificationsNotice => - 'External services are used for delivering push notifications. While the content is encrypted and can only be read by this app, extracting metadata like the time and count of notifications is still possible.'; - @override String get globalOptionsPushNotificationsEnabled => 'Enabled'; @override String get globalOptionsPushNotificationsEnabledDisabledNotice => - 'No UnifiedPush distributor could be found. Please go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications'; + 'No UnifiedPush distributor could be found or you denied the permission for showing notifications. Please go to the app settings and allow notifications and go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications'; @override String get globalOptionsPushNotificationsDistributor => 'UnifiedPush Distributor'; @override - String get globalOptionsPushNotificationsDistributorGotifyUP => 'Gotify-UP'; + String get globalOptionsPushNotificationsDistributorGotifyUP => 'Gotify-UP (FOSS)'; + + @override + String get globalOptionsPushNotificationsDistributorFirebaseEmbedded => 'Firebase (proprietary)'; @override - String get globalOptionsPushNotificationsDistributorFirebaseEmbedded => 'Firebase (FOSS)'; + String get globalOptionsPushNotificationsDistributorNtfy => 'ntfy (FOSS)'; @override - String get globalOptionsPushNotificationsDistributorNtfy => 'ntfy'; + String get globalOptionsPushNotificationsDistributorFCMUP => 'FCM-UP (proprietary)'; @override - String get globalOptionsPushNotificationsDistributorFCMUP => 'FCM-UP'; + String get globalOptionsPushNotificationsDistributorNextPush => 'NextPush (FOSS)'; @override - String get globalOptionsPushNotificationsDistributorNextPush => 'NextPush'; + String get globalOptionsPushNotificationsDistributorNoProvider2Push => 'NoProvider2Push (FOSS)'; @override - String get globalOptionsPushNotificationsDistributorNoProvider2Push => 'NoProvider2Push'; + String get globalOptionsPushNotificationsDistributorConversations => 'Conversations'; @override String get globalOptionsStartupMinimized => 'Start minimized'; diff --git a/packages/neon/lib/main.dart b/packages/neon/lib/main.dart index 0ad030ad..154d39ea 100644 --- a/packages/neon/lib/main.dart +++ b/packages/neon/lib/main.dart @@ -52,7 +52,6 @@ Future main() async { accountsBloc, sharedPreferences, globalOptions, - env, platform, ); diff --git a/packages/neon/lib/src/app.dart b/packages/neon/lib/src/app.dart index 751e596c..a49df05c 100644 --- a/packages/neon/lib/src/app.dart +++ b/packages/neon/lib/src/app.dart @@ -172,7 +172,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra final allAppImplementations = Provider.of>(context, listen: false); final matchingAppImplementations = - allAppImplementations.where((final a) => a.id == pushNotificationWithAccountID.notification.subject.app); + allAppImplementations.where((final a) => a.id == pushNotificationWithAccountID.subject.app); late AppImplementation appImplementation; if (matchingAppImplementations.isNotEmpty) { @@ -191,7 +191,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra _accountsBloc .getAppsBloc(account) .getAppBloc(appImplementation) - .deleteNotification(pushNotificationWithAccountID.notification.subject.nid!); + .deleteNotification(pushNotificationWithAccountID.subject.nid!); } await _openAppFromExternal(account, appImplementation.id); }; @@ -199,7 +199,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra final details = await localNotificationsPlugin.getNotificationAppLaunchDetails(); if (details != null && details.didNotificationLaunchApp && details.notificationResponse?.payload != null) { await Global.onPushNotificationClicked!( - PushNotificationWithAccountID.fromJson( + PushNotification.fromJson( json.decode(details.notificationResponse!.payload!) as Map, ), ); diff --git a/packages/neon/lib/src/blocs/push_notifications.dart b/packages/neon/lib/src/blocs/push_notifications.dart index 4934c4bd..077a4058 100644 --- a/packages/neon/lib/src/blocs/push_notifications.dart +++ b/packages/neon/lib/src/blocs/push_notifications.dart @@ -3,7 +3,7 @@ part of '../neon.dart'; abstract class PushNotificationsBlocEvents {} abstract class PushNotificationsBlocStates { - Stream get notifications; + Stream get notifications; } class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, PushNotificationsBlocStates { @@ -11,7 +11,6 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, this._accountsBloc, this._sharedPreferences, this._globalOptions, - this._env, this._platform, ) { if (_platform.canUsePushNotifications) { @@ -35,11 +34,10 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, final SharedPreferences _sharedPreferences; late final _storage = AppStorage('notifications', _sharedPreferences); final GlobalOptions _globalOptions; - final Env? _env; late RSAKeypair _keypair; bool? _pushNotificationsEnabled; - final _notificationsController = StreamController(); + final _notificationsController = StreamController(); @override void dispose() { @@ -47,20 +45,14 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, } @override - late Stream notifications = _notificationsController.stream.asBroadcastStream(); + late Stream notifications = _notificationsController.stream.asBroadcastStream(); String _keyLastEndpoint(final Account account) => 'last-endpoint-${account.id}'; Future _setupUnifiedPush() async { await UnifiedPush.initialize( onNewEndpoint: (final endpoint, final instance) async { - Account? account; - for (final a in _accountsBloc.accounts.value) { - if (a.id == instance) { - account = a; - break; - } - } + final account = _accountsBloc.accounts.value.find(instance); if (account == null) { debugPrint('Account for $instance not found, can not process endpoint'); return; @@ -73,28 +65,17 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, debugPrint('Registering account $instance for push notifications on $endpoint'); - var proxyServerForNextcloud = 'https://nc.proxy.neon.provokateurin.de/'; - var proxyServerForClient = proxyServerForNextcloud; - if (_env != null) { - proxyServerForNextcloud = 'http://host.docker.internal:8080/'; - proxyServerForClient = 'http://${_env!.testHost}:8080/'; - } - final subscription = await account.client.notifications.registerDevice( - pushTokenHash: account.client.notifications.generatePushTokenHash(endpoint), + pushTokenHash: generatePushTokenHash(endpoint), devicePublicKey: _keypair.publicKey.toFormattedPEM(), - proxyServer: proxyServerForNextcloud, - ); - - await account.client.notifications.registerDeviceAtPushProxy( - endpoint, - subscription.ocs.data, - proxyServerForClient, + proxyServer: '$endpoint#', // This is a hack to make the Nextcloud server directly push to the endpoint ); await _storage.setString(_keyLastEndpoint(account), endpoint); - debugPrint('Account $instance registered for push notifications'); + debugPrint( + 'Account $instance registered for push notifications ${json.encode(subscription.ocs.data.toJson())}', + ); }, onMessage: PushUtils.onMessage, ); @@ -120,7 +101,13 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, Future _unregisterUnifiedPushInstances(final List accounts) async { for (final account in accounts) { - await UnifiedPush.unregister(account.client.id); + try { + await account.client.notifications.removeDevice(); + await UnifiedPush.unregister(account.client.id); + await _storage.remove(_keyLastEndpoint(account)); + } catch (e) { + debugPrint('Failed to unregister device: $e'); + } } } @@ -131,17 +118,3 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, } } } - -class NextcloudPushNotification { - NextcloudPushNotification({ - required this.instance, - required this.priority, - required this.type, - required this.subject, - }); - - final String instance; - final String priority; - final String type; - final NextcloudNotificationsPushNotificationDecryptedSubject subject; -} diff --git a/packages/neon/lib/src/models/push_notification.dart b/packages/neon/lib/src/models/push_notification.dart new file mode 100644 index 00000000..5a9e4142 --- /dev/null +++ b/packages/neon/lib/src/models/push_notification.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:nextcloud/nextcloud.dart'; + +part 'push_notification.g.dart'; + +@JsonSerializable() +class PushNotification { + PushNotification({ + required this.accountID, + required this.priority, + required this.type, + required this.subject, + }); + + factory PushNotification.fromJson(final Map json) => _$PushNotificationFromJson(json); + Map toJson() => _$PushNotificationToJson(this); + + final String accountID; + + final String priority; + + final String type; + + final NextcloudNotificationsNotificationDecryptedSubject subject; +} diff --git a/packages/neon/lib/src/models/push_notification.g.dart b/packages/neon/lib/src/models/push_notification.g.dart new file mode 100644 index 00000000..dc2208f6 --- /dev/null +++ b/packages/neon/lib/src/models/push_notification.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotification _$PushNotificationFromJson(Map json) => PushNotification( + accountID: json['accountID'] as String, + priority: json['priority'] as String, + type: json['type'] as String, + subject: NextcloudNotificationsNotificationDecryptedSubject.fromJson(json['subject'] as Map), + ); + +Map _$PushNotificationToJson(PushNotification instance) => { + 'accountID': instance.accountID, + 'priority': instance.priority, + 'type': instance.type, + 'subject': instance.subject, + }; diff --git a/packages/neon/lib/src/models/push_notification_with_account.dart b/packages/neon/lib/src/models/push_notification_with_account.dart deleted file mode 100644 index e90b7342..00000000 --- a/packages/neon/lib/src/models/push_notification_with_account.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:nextcloud/nextcloud.dart'; - -part 'push_notification_with_account.g.dart'; - -@JsonSerializable() -class PushNotificationWithAccountID { - PushNotificationWithAccountID({ - required this.notification, - required this.accountID, - }); - - factory PushNotificationWithAccountID.fromJson(final Map json) => - _$PushNotificationWithAccountIDFromJson(json); - Map toJson() => _$PushNotificationWithAccountIDToJson(this); - - final NextcloudNotificationsPushNotification notification; - - final String accountID; -} diff --git a/packages/neon/lib/src/models/push_notification_with_account.g.dart b/packages/neon/lib/src/models/push_notification_with_account.g.dart deleted file mode 100644 index 569a6cea..00000000 --- a/packages/neon/lib/src/models/push_notification_with_account.g.dart +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'push_notification_with_account.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -PushNotificationWithAccountID _$PushNotificationWithAccountIDFromJson(Map json) => - PushNotificationWithAccountID( - notification: NextcloudNotificationsPushNotification.fromJson(json['notification'] as Map), - accountID: json['accountID'] as String, - ); - -Map _$PushNotificationWithAccountIDToJson(PushNotificationWithAccountID instance) => { - 'notification': instance.notification, - 'accountID': instance.accountID, - }; diff --git a/packages/neon/lib/src/neon.dart b/packages/neon/lib/src/neon.dart index b055d96c..adba8144 100644 --- a/packages/neon/lib/src/neon.dart +++ b/packages/neon/lib/src/neon.dart @@ -25,7 +25,7 @@ import 'package:neon/src/apps/news/app.dart'; import 'package:neon/src/apps/notes/app.dart'; import 'package:neon/src/apps/notifications/app.dart'; import 'package:neon/src/models/account.dart'; -import 'package:neon/src/models/push_notification_with_account.dart'; +import 'package:neon/src/models/push_notification.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as p; diff --git a/packages/neon/lib/src/pages/settings.dart b/packages/neon/lib/src/pages/settings.dart index 0ad7ad0a..eeb42dec 100644 --- a/packages/neon/lib/src/pages/settings.dart +++ b/packages/neon/lib/src/pages/settings.dart @@ -119,13 +119,6 @@ class _SettingsPageState extends State { SettingsCategory( title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), tiles: [ - TextSettingsTile( - text: AppLocalizations.of(context).globalOptionsPushNotificationsNotice, - style: const TextStyle( - fontWeight: FontWeight.w300, - fontStyle: FontStyle.italic, - ), - ), if (pushNotificationsEnabledEnabledSnapshot.data != null && !pushNotificationsEnabledEnabledSnapshot.data!) ...[ TextSettingsTile( diff --git a/packages/neon/lib/src/utils/global.dart b/packages/neon/lib/src/utils/global.dart index 32e5e174..a8fbaafa 100644 --- a/packages/neon/lib/src/utils/global.dart +++ b/packages/neon/lib/src/utils/global.dart @@ -2,5 +2,5 @@ part of '../neon.dart'; class Global { static Function(String accountID)? onPushNotificationReceived; - static Function(PushNotificationWithAccountID notification)? onPushNotificationClicked; + static Function(PushNotification notification)? onPushNotificationClicked; } diff --git a/packages/neon/lib/src/utils/global_options.dart b/packages/neon/lib/src/utils/global_options.dart index 529b410f..5df411c4 100644 --- a/packages/neon/lib/src/utils/global_options.dart +++ b/packages/neon/lib/src/utils/global_options.dart @@ -10,15 +10,25 @@ class GlobalOptions { }); _pushNotificationsDistributorsSubject.listen((final distributors) async { - _pushNotificationsEnabledEnabledSubject.add(distributors.isNotEmpty); - await _setDefaultDistributor(); + final allowed = distributors.isNotEmpty; + _pushNotificationsEnabledEnabledSubject.add(allowed); + if (!allowed) { + await pushNotificationsEnabled.set(false); + } }); pushNotificationsEnabled.stream.listen((final enabled) async { - if (!enabled) { + if (enabled) { + final response = await Permission.notification.request(); + if (response.isPermanentlyDenied) { + _pushNotificationsEnabledEnabledSubject.add(false); + } + if (!response.isGranted) { + await pushNotificationsEnabled.set(false); + } + } else { await pushNotificationsDistributor.set(null); } - await _setDefaultDistributor(); }); rememberLastUsedAccount.stream.listen((final remember) async { @@ -48,6 +58,8 @@ class GlobalOptions { AppLocalizations.of(context).globalOptionsPushNotificationsDistributorFirebaseEmbedded, 'com.github.gotify.up': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorGotifyUP, + 'eu.siacs.conversations': (final context) => + AppLocalizations.of(context).globalOptionsPushNotificationsDistributorConversations, 'io.heckel.ntfy': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorNtfy, 'org.unifiedpush.distributor.fcm': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorFCMUP, @@ -105,17 +117,6 @@ class GlobalOptions { }); } - Future _setDefaultDistributor() async { - if ((pushNotificationsEnabled.enabled.valueOrNull ?? false) && - pushNotificationsEnabled.value && - pushNotificationsDistributor.values.hasValue && - pushNotificationsDistributor.values.value.isNotEmpty && - pushNotificationsDistributor.stream.hasValue && - pushNotificationsDistributor.value == null) { - await pushNotificationsDistributor.set((await pushNotificationsDistributor.values.first).keys.toList()[0]); - } - } - late final themeMode = SelectOption( storage: _storage, key: 'theme-mode', @@ -147,7 +148,7 @@ class GlobalOptions { storage: _storage, key: 'push-notifications-enabled', label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsEnabled, - defaultValue: BehaviorSubject.seeded(true), + defaultValue: BehaviorSubject.seeded(false), enabled: _pushNotificationsEnabledEnabledSubject, ); diff --git a/packages/neon/lib/src/utils/push_utils.dart b/packages/neon/lib/src/utils/push_utils.dart index 84aefb0b..2ed47a5f 100644 --- a/packages/neon/lib/src/utils/push_utils.dart +++ b/packages/neon/lib/src/utils/push_utils.dart @@ -27,7 +27,7 @@ class PushUtils { android: const AndroidInitializationSettings('@mipmap/ic_launcher'), linux: LinuxInitializationSettings( defaultActionName: 'Open', - defaultIcon: AssetsLinuxIcon('assets/logo_harbour.svg'), + defaultIcon: AssetsLinuxIcon('assets/logo_neon.svg'), ), ), onDidReceiveNotificationResponse: onDidReceiveNotificationResponse, @@ -35,14 +35,14 @@ class PushUtils { return localNotificationsPlugin; } - static Future onMessage(final Uint8List message, final String instance) async { + static Future onMessage(final Uint8List messages, final String instance) async { WidgetsFlutterBinding.ensureInitialized(); final localNotificationsPlugin = await initLocalNotifications( onDidReceiveNotificationResponse: (final notification) async { if (Global.onPushNotificationClicked != null && notification.payload != null) { await Global.onPushNotificationClicked!( - PushNotificationWithAccountID.fromJson( + PushNotification.fromJson( json.decode(notification.payload!) as Map, ), ); @@ -52,82 +52,87 @@ class PushUtils { final sharedPreferences = await SharedPreferences.getInstance(); final keypair = await loadRSAKeypair(AppStorage('notifications', sharedPreferences)); - final data = json.decode(utf8.decode(message)) as Map; - final notification = NextcloudNotificationsPushNotification( - accountID: instance, - priority: data['priority']! as String, - type: data['type']! as String, - subject: decryptPushNotificationSubject(keypair.privateKey, data['subject']! as String), - ); - if (notification.subject.delete ?? false) { - await localNotificationsPlugin.cancel(_getNotificationID(instance, notification)); - return; - } - if (notification.subject.deleteAll ?? false) { - await localNotificationsPlugin.cancelAll(); - return; - } - if (notification.type == 'background') { - debugPrint('Got unknown background notification $notification.subject'); - return; - } + for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) { + final data = json.decode(message) as Map; + final notification = PushNotification( + accountID: instance, + priority: data['priority']! as String, + type: data['type']! as String, + subject: decryptPushNotificationSubject(keypair.privateKey, data['subject']! as String), + ); - final localizations = await appLocalizationsFromSystem(); + if (notification.subject.delete ?? false) { + await localNotificationsPlugin.cancel(_getNotificationID(instance, notification)); + return; + } + if (notification.subject.deleteAll ?? false) { + await localNotificationsPlugin.cancelAll(); + return; + } + if (notification.type == 'background') { + debugPrint('Got unknown background notification ${json.encode(notification.toJson())}'); + return; + } - final platform = await getNeonPlatform(); - final cache = Cache(platform); - await cache.init(); - final requestManager = RequestManager(cache); - final allAppImplementations = getAppImplementations(sharedPreferences, requestManager, platform); + final localizations = await appLocalizationsFromSystem(); - final matchingAppImplementations = - allAppImplementations.where((final a) => a.id == notification.subject.app).toList(); - late AppImplementation app; - if (matchingAppImplementations.isNotEmpty) { - app = matchingAppImplementations.single; - } else { - app = allAppImplementations.singleWhere((final a) => a.id == 'notifications'); - } + final platform = await getNeonPlatform(); + final cache = Cache(platform); + await cache.init(); + final requestManager = RequestManager(cache); + final allAppImplementations = getAppImplementations(sharedPreferences, requestManager, platform); - final appName = app.nameFromLocalization(localizations); - - await localNotificationsPlugin.show( - _getNotificationID(instance, notification), - appName, - notification.subject.subject, - NotificationDetails( - android: AndroidNotificationDetails( - app.id, - appName, - groupKey: 'app_${app.id}', - icon: '@mipmap/app_${app.id}', - color: themePrimaryColor, - category: notification.type == 'voip' ? AndroidNotificationCategory.call : null, - importance: Importance.max, - priority: notification.priority == 'high' - ? (notification.type == 'voip' ? Priority.max : Priority.high) - : Priority.defaultPriority, + final matchingAppImplementations = + allAppImplementations.where((final a) => a.id == notification.subject.app).toList(); + late AppImplementation app; + if (matchingAppImplementations.isNotEmpty) { + app = matchingAppImplementations.single; + } else { + app = allAppImplementations.singleWhere((final a) => a.id == 'notifications'); + } + + final appName = app.nameFromLocalization(localizations); + + await localNotificationsPlugin.show( + _getNotificationID(instance, notification), + appName, + notification.subject.subject, + NotificationDetails( + android: AndroidNotificationDetails( + app.id, + appName, + groupKey: 'app_${app.id}', + icon: '@mipmap/app_${app.id}', + color: themePrimaryColor, + category: notification.type == 'voip' ? AndroidNotificationCategory.call : null, + importance: Importance.max, + priority: notification.priority == 'high' + ? (notification.type == 'voip' ? Priority.max : Priority.high) + : Priority.defaultPriority, + ), + linux: LinuxNotificationDetails( + icon: AssetsLinuxIcon('assets/apps/${app.id}.svg'), + urgency: notification.type == 'voip' ? LinuxNotificationUrgency.critical : LinuxNotificationUrgency.normal, + ), ), - linux: LinuxNotificationDetails( - icon: AssetsLinuxIcon('assets/apps/${app.id}.svg'), - urgency: notification.type == 'voip' ? LinuxNotificationUrgency.critical : LinuxNotificationUrgency.normal, + payload: json.encode( + PushNotification( + accountID: instance, + priority: notification.priority, + type: notification.type, + subject: notification.subject, + ).toJson(), ), - ), - payload: json.encode( - PushNotificationWithAccountID( - notification: notification, - accountID: instance, - ).toJson(), - ), - ); + ); - Global.onPushNotificationReceived?.call(instance); + Global.onPushNotificationReceived?.call(instance); + } } static int _getNotificationID( final String instance, - final NextcloudNotificationsPushNotification notification, + final PushNotification notification, ) => sha256.convert(utf8.encode('$instance${notification.subject.nid}')).bytes.reduce((final a, final b) => a + b); }