Browse Source

neon: Adapt to push proxy changes and fix push notifications

pull/162/head
jld3103 2 years ago
parent
commit
961b5b0f0b
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 1
      packages/neon/integration_test/screenshot_test.dart
  2. 16
      packages/neon/lib/l10n/en.arb
  3. 26
      packages/neon/lib/l10n/localizations.dart
  4. 21
      packages/neon/lib/l10n/localizations_en.dart
  5. 1
      packages/neon/lib/main.dart
  6. 6
      packages/neon/lib/src/app.dart
  7. 57
      packages/neon/lib/src/blocs/push_notifications.dart
  8. 25
      packages/neon/lib/src/models/push_notification.dart
  9. 21
      packages/neon/lib/src/models/push_notification.g.dart
  10. 20
      packages/neon/lib/src/models/push_notification_with_account.dart
  11. 18
      packages/neon/lib/src/models/push_notification_with_account.g.dart
  12. 2
      packages/neon/lib/src/neon.dart
  13. 7
      packages/neon/lib/src/pages/settings.dart
  14. 2
      packages/neon/lib/src/utils/global.dart
  15. 33
      packages/neon/lib/src/utils/global_options.dart
  16. 23
      packages/neon/lib/src/utils/push_utils.dart

1
packages/neon/integration_test/screenshot_test.dart

@ -126,7 +126,6 @@ Future pumpAppPage(
accountsBloc, accountsBloc,
sharedPreferences, sharedPreferences,
globalOptions, globalOptions,
null,
platform, platform,
); );

16
packages/neon/lib/l10n/en.arb

@ -81,16 +81,16 @@
"globalOptionsThemeModeAutomatic": "Automatic", "globalOptionsThemeModeAutomatic": "Automatic",
"globalOptionsThemeOLEDAsDark": "OLED theme as dark theme", "globalOptionsThemeOLEDAsDark": "OLED theme as dark theme",
"globalOptionsThemeKeepOriginalAccentColor": "Keep the original accent color", "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", "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", "globalOptionsPushNotificationsDistributor": "UnifiedPush Distributor",
"globalOptionsPushNotificationsDistributorGotifyUP": "Gotify-UP", "globalOptionsPushNotificationsDistributorGotifyUP": "Gotify-UP (FOSS)",
"globalOptionsPushNotificationsDistributorFirebaseEmbedded": "Firebase (FOSS)", "globalOptionsPushNotificationsDistributorFirebaseEmbedded": "Firebase (proprietary)",
"globalOptionsPushNotificationsDistributorNtfy": "ntfy", "globalOptionsPushNotificationsDistributorNtfy": "ntfy (FOSS)",
"globalOptionsPushNotificationsDistributorFCMUP": "FCM-UP", "globalOptionsPushNotificationsDistributorFCMUP": "FCM-UP (proprietary)",
"globalOptionsPushNotificationsDistributorNextPush": "NextPush", "globalOptionsPushNotificationsDistributorNextPush": "NextPush (FOSS)",
"globalOptionsPushNotificationsDistributorNoProvider2Push": "NoProvider2Push", "globalOptionsPushNotificationsDistributorNoProvider2Push": "NoProvider2Push (FOSS)",
"globalOptionsPushNotificationsDistributorConversations": "Conversations",
"globalOptionsStartupMinimized": "Start minimized", "globalOptionsStartupMinimized": "Start minimized",
"globalOptionsStartupMinimizeInsteadOfExit": "Minimize instead of exit", "globalOptionsStartupMinimizeInsteadOfExit": "Minimize instead of exit",
"globalOptionsSystemTrayEnabled": "Enable system tray", "globalOptionsSystemTrayEnabled": "Enable system tray",

26
packages/neon/lib/l10n/localizations.dart

@ -407,12 +407,6 @@ abstract class AppLocalizations {
/// **'Keep the original accent color'** /// **'Keep the original accent color'**
String get globalOptionsThemeKeepOriginalAccentColor; 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. /// No description provided for @globalOptionsPushNotificationsEnabled.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -422,7 +416,7 @@ abstract class AppLocalizations {
/// No description provided for @globalOptionsPushNotificationsEnabledDisabledNotice. /// No description provided for @globalOptionsPushNotificationsEnabledDisabledNotice.
/// ///
/// In en, this message translates to: /// 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; String get globalOptionsPushNotificationsEnabledDisabledNotice;
/// No description provided for @globalOptionsPushNotificationsDistributor. /// No description provided for @globalOptionsPushNotificationsDistributor.
@ -434,39 +428,45 @@ abstract class AppLocalizations {
/// No description provided for @globalOptionsPushNotificationsDistributorGotifyUP. /// No description provided for @globalOptionsPushNotificationsDistributorGotifyUP.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Gotify-UP'** /// **'Gotify-UP (FOSS)'**
String get globalOptionsPushNotificationsDistributorGotifyUP; String get globalOptionsPushNotificationsDistributorGotifyUP;
/// No description provided for @globalOptionsPushNotificationsDistributorFirebaseEmbedded. /// No description provided for @globalOptionsPushNotificationsDistributorFirebaseEmbedded.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Firebase (FOSS)'** /// **'Firebase (proprietary)'**
String get globalOptionsPushNotificationsDistributorFirebaseEmbedded; String get globalOptionsPushNotificationsDistributorFirebaseEmbedded;
/// No description provided for @globalOptionsPushNotificationsDistributorNtfy. /// No description provided for @globalOptionsPushNotificationsDistributorNtfy.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'ntfy'** /// **'ntfy (FOSS)'**
String get globalOptionsPushNotificationsDistributorNtfy; String get globalOptionsPushNotificationsDistributorNtfy;
/// No description provided for @globalOptionsPushNotificationsDistributorFCMUP. /// No description provided for @globalOptionsPushNotificationsDistributorFCMUP.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'FCM-UP'** /// **'FCM-UP (proprietary)'**
String get globalOptionsPushNotificationsDistributorFCMUP; String get globalOptionsPushNotificationsDistributorFCMUP;
/// No description provided for @globalOptionsPushNotificationsDistributorNextPush. /// No description provided for @globalOptionsPushNotificationsDistributorNextPush.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'NextPush'** /// **'NextPush (FOSS)'**
String get globalOptionsPushNotificationsDistributorNextPush; String get globalOptionsPushNotificationsDistributorNextPush;
/// No description provided for @globalOptionsPushNotificationsDistributorNoProvider2Push. /// No description provided for @globalOptionsPushNotificationsDistributorNoProvider2Push.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'NoProvider2Push'** /// **'NoProvider2Push (FOSS)'**
String get globalOptionsPushNotificationsDistributorNoProvider2Push; String get globalOptionsPushNotificationsDistributorNoProvider2Push;
/// No description provided for @globalOptionsPushNotificationsDistributorConversations.
///
/// In en, this message translates to:
/// **'Conversations'**
String get globalOptionsPushNotificationsDistributorConversations;
/// No description provided for @globalOptionsStartupMinimized. /// No description provided for @globalOptionsStartupMinimized.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

21
packages/neon/lib/l10n/localizations_en.dart

@ -174,37 +174,36 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get globalOptionsThemeKeepOriginalAccentColor => 'Keep the original accent color'; 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 @override
String get globalOptionsPushNotificationsEnabled => 'Enabled'; String get globalOptionsPushNotificationsEnabled => 'Enabled';
@override @override
String get globalOptionsPushNotificationsEnabledDisabledNotice => 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 @override
String get globalOptionsPushNotificationsDistributor => 'UnifiedPush Distributor'; String get globalOptionsPushNotificationsDistributor => 'UnifiedPush Distributor';
@override @override
String get globalOptionsPushNotificationsDistributorGotifyUP => 'Gotify-UP'; String get globalOptionsPushNotificationsDistributorGotifyUP => 'Gotify-UP (FOSS)';
@override
String get globalOptionsPushNotificationsDistributorFirebaseEmbedded => 'Firebase (proprietary)';
@override @override
String get globalOptionsPushNotificationsDistributorFirebaseEmbedded => 'Firebase (FOSS)'; String get globalOptionsPushNotificationsDistributorNtfy => 'ntfy (FOSS)';
@override @override
String get globalOptionsPushNotificationsDistributorNtfy => 'ntfy'; String get globalOptionsPushNotificationsDistributorFCMUP => 'FCM-UP (proprietary)';
@override @override
String get globalOptionsPushNotificationsDistributorFCMUP => 'FCM-UP'; String get globalOptionsPushNotificationsDistributorNextPush => 'NextPush (FOSS)';
@override @override
String get globalOptionsPushNotificationsDistributorNextPush => 'NextPush'; String get globalOptionsPushNotificationsDistributorNoProvider2Push => 'NoProvider2Push (FOSS)';
@override @override
String get globalOptionsPushNotificationsDistributorNoProvider2Push => 'NoProvider2Push'; String get globalOptionsPushNotificationsDistributorConversations => 'Conversations';
@override @override
String get globalOptionsStartupMinimized => 'Start minimized'; String get globalOptionsStartupMinimized => 'Start minimized';

1
packages/neon/lib/main.dart

@ -52,7 +52,6 @@ Future main() async {
accountsBloc, accountsBloc,
sharedPreferences, sharedPreferences,
globalOptions, globalOptions,
env,
platform, platform,
); );

6
packages/neon/lib/src/app.dart

@ -172,7 +172,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
final allAppImplementations = Provider.of<List<AppImplementation>>(context, listen: false); final allAppImplementations = Provider.of<List<AppImplementation>>(context, listen: false);
final matchingAppImplementations = final matchingAppImplementations =
allAppImplementations.where((final a) => a.id == pushNotificationWithAccountID.notification.subject.app); allAppImplementations.where((final a) => a.id == pushNotificationWithAccountID.subject.app);
late AppImplementation appImplementation; late AppImplementation appImplementation;
if (matchingAppImplementations.isNotEmpty) { if (matchingAppImplementations.isNotEmpty) {
@ -191,7 +191,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
_accountsBloc _accountsBloc
.getAppsBloc(account) .getAppsBloc(account)
.getAppBloc<NotificationsBloc>(appImplementation) .getAppBloc<NotificationsBloc>(appImplementation)
.deleteNotification(pushNotificationWithAccountID.notification.subject.nid!); .deleteNotification(pushNotificationWithAccountID.subject.nid!);
} }
await _openAppFromExternal(account, appImplementation.id); await _openAppFromExternal(account, appImplementation.id);
}; };
@ -199,7 +199,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
final details = await localNotificationsPlugin.getNotificationAppLaunchDetails(); final details = await localNotificationsPlugin.getNotificationAppLaunchDetails();
if (details != null && details.didNotificationLaunchApp && details.notificationResponse?.payload != null) { if (details != null && details.didNotificationLaunchApp && details.notificationResponse?.payload != null) {
await Global.onPushNotificationClicked!( await Global.onPushNotificationClicked!(
PushNotificationWithAccountID.fromJson( PushNotification.fromJson(
json.decode(details.notificationResponse!.payload!) as Map<String, dynamic>, json.decode(details.notificationResponse!.payload!) as Map<String, dynamic>,
), ),
); );

57
packages/neon/lib/src/blocs/push_notifications.dart

@ -3,7 +3,7 @@ part of '../neon.dart';
abstract class PushNotificationsBlocEvents {} abstract class PushNotificationsBlocEvents {}
abstract class PushNotificationsBlocStates { abstract class PushNotificationsBlocStates {
Stream<NextcloudPushNotification> get notifications; Stream<PushNotification> get notifications;
} }
class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, PushNotificationsBlocStates { class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, PushNotificationsBlocStates {
@ -11,7 +11,6 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
this._accountsBloc, this._accountsBloc,
this._sharedPreferences, this._sharedPreferences,
this._globalOptions, this._globalOptions,
this._env,
this._platform, this._platform,
) { ) {
if (_platform.canUsePushNotifications) { if (_platform.canUsePushNotifications) {
@ -35,11 +34,10 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
late final _storage = AppStorage('notifications', _sharedPreferences); late final _storage = AppStorage('notifications', _sharedPreferences);
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;
final Env? _env;
late RSAKeypair _keypair; late RSAKeypair _keypair;
bool? _pushNotificationsEnabled; bool? _pushNotificationsEnabled;
final _notificationsController = StreamController<NextcloudPushNotification>(); final _notificationsController = StreamController<PushNotification>();
@override @override
void dispose() { void dispose() {
@ -47,20 +45,14 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
} }
@override @override
late Stream<NextcloudPushNotification> notifications = _notificationsController.stream.asBroadcastStream(); late Stream<PushNotification> notifications = _notificationsController.stream.asBroadcastStream();
String _keyLastEndpoint(final Account account) => 'last-endpoint-${account.id}'; String _keyLastEndpoint(final Account account) => 'last-endpoint-${account.id}';
Future _setupUnifiedPush() async { Future _setupUnifiedPush() async {
await UnifiedPush.initialize( await UnifiedPush.initialize(
onNewEndpoint: (final endpoint, final instance) async { onNewEndpoint: (final endpoint, final instance) async {
Account? account; final account = _accountsBloc.accounts.value.find(instance);
for (final a in _accountsBloc.accounts.value) {
if (a.id == instance) {
account = a;
break;
}
}
if (account == null) { if (account == null) {
debugPrint('Account for $instance not found, can not process endpoint'); debugPrint('Account for $instance not found, can not process endpoint');
return; return;
@ -73,28 +65,17 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
debugPrint('Registering account $instance for push notifications on $endpoint'); 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( final subscription = await account.client.notifications.registerDevice(
pushTokenHash: account.client.notifications.generatePushTokenHash(endpoint), pushTokenHash: generatePushTokenHash(endpoint),
devicePublicKey: _keypair.publicKey.toFormattedPEM(), devicePublicKey: _keypair.publicKey.toFormattedPEM(),
proxyServer: proxyServerForNextcloud, proxyServer: '$endpoint#', // This is a hack to make the Nextcloud server directly push to the endpoint
);
await account.client.notifications.registerDeviceAtPushProxy(
endpoint,
subscription.ocs.data,
proxyServerForClient,
); );
await _storage.setString(_keyLastEndpoint(account), 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, onMessage: PushUtils.onMessage,
); );
@ -120,7 +101,13 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
Future _unregisterUnifiedPushInstances(final List<Account> accounts) async { Future _unregisterUnifiedPushInstances(final List<Account> accounts) async {
for (final account in accounts) { for (final account in accounts) {
try {
await account.client.notifications.removeDevice();
await UnifiedPush.unregister(account.client.id); 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;
}

25
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<String, dynamic> json) => _$PushNotificationFromJson(json);
Map<String, dynamic> toJson() => _$PushNotificationToJson(this);
final String accountID;
final String priority;
final String type;
final NextcloudNotificationsNotificationDecryptedSubject subject;
}

21
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<String, dynamic> json) => PushNotification(
accountID: json['accountID'] as String,
priority: json['priority'] as String,
type: json['type'] as String,
subject: NextcloudNotificationsNotificationDecryptedSubject.fromJson(json['subject'] as Map<String, dynamic>),
);
Map<String, dynamic> _$PushNotificationToJson(PushNotification instance) => <String, dynamic>{
'accountID': instance.accountID,
'priority': instance.priority,
'type': instance.type,
'subject': instance.subject,
};

20
packages/neon/lib/src/models/push_notification_with_account.dart

@ -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<String, dynamic> json) =>
_$PushNotificationWithAccountIDFromJson(json);
Map<String, dynamic> toJson() => _$PushNotificationWithAccountIDToJson(this);
final NextcloudNotificationsPushNotification notification;
final String accountID;
}

18
packages/neon/lib/src/models/push_notification_with_account.g.dart

@ -1,18 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'push_notification_with_account.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PushNotificationWithAccountID _$PushNotificationWithAccountIDFromJson(Map<String, dynamic> json) =>
PushNotificationWithAccountID(
notification: NextcloudNotificationsPushNotification.fromJson(json['notification'] as Map<String, dynamic>),
accountID: json['accountID'] as String,
);
Map<String, dynamic> _$PushNotificationWithAccountIDToJson(PushNotificationWithAccountID instance) => <String, dynamic>{
'notification': instance.notification,
'accountID': instance.accountID,
};

2
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/notes/app.dart';
import 'package:neon/src/apps/notifications/app.dart'; import 'package:neon/src/apps/notifications/app.dart';
import 'package:neon/src/models/account.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:nextcloud/nextcloud.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;

7
packages/neon/lib/src/pages/settings.dart

@ -119,13 +119,6 @@ class _SettingsPageState extends State<SettingsPage> {
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications),
tiles: [ tiles: [
TextSettingsTile(
text: AppLocalizations.of(context).globalOptionsPushNotificationsNotice,
style: const TextStyle(
fontWeight: FontWeight.w300,
fontStyle: FontStyle.italic,
),
),
if (pushNotificationsEnabledEnabledSnapshot.data != null && if (pushNotificationsEnabledEnabledSnapshot.data != null &&
!pushNotificationsEnabledEnabledSnapshot.data!) ...[ !pushNotificationsEnabledEnabledSnapshot.data!) ...[
TextSettingsTile( TextSettingsTile(

2
packages/neon/lib/src/utils/global.dart

@ -2,5 +2,5 @@ part of '../neon.dart';
class Global { class Global {
static Function(String accountID)? onPushNotificationReceived; static Function(String accountID)? onPushNotificationReceived;
static Function(PushNotificationWithAccountID notification)? onPushNotificationClicked; static Function(PushNotification notification)? onPushNotificationClicked;
} }

33
packages/neon/lib/src/utils/global_options.dart

@ -10,15 +10,25 @@ class GlobalOptions {
}); });
_pushNotificationsDistributorsSubject.listen((final distributors) async { _pushNotificationsDistributorsSubject.listen((final distributors) async {
_pushNotificationsEnabledEnabledSubject.add(distributors.isNotEmpty); final allowed = distributors.isNotEmpty;
await _setDefaultDistributor(); _pushNotificationsEnabledEnabledSubject.add(allowed);
if (!allowed) {
await pushNotificationsEnabled.set(false);
}
}); });
pushNotificationsEnabled.stream.listen((final enabled) async { 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 pushNotificationsDistributor.set(null);
} }
await _setDefaultDistributor();
}); });
rememberLastUsedAccount.stream.listen((final remember) async { rememberLastUsedAccount.stream.listen((final remember) async {
@ -48,6 +58,8 @@ class GlobalOptions {
AppLocalizations.of(context).globalOptionsPushNotificationsDistributorFirebaseEmbedded, AppLocalizations.of(context).globalOptionsPushNotificationsDistributorFirebaseEmbedded,
'com.github.gotify.up': (final context) => 'com.github.gotify.up': (final context) =>
AppLocalizations.of(context).globalOptionsPushNotificationsDistributorGotifyUP, AppLocalizations.of(context).globalOptionsPushNotificationsDistributorGotifyUP,
'eu.siacs.conversations': (final context) =>
AppLocalizations.of(context).globalOptionsPushNotificationsDistributorConversations,
'io.heckel.ntfy': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorNtfy, 'io.heckel.ntfy': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorNtfy,
'org.unifiedpush.distributor.fcm': (final context) => 'org.unifiedpush.distributor.fcm': (final context) =>
AppLocalizations.of(context).globalOptionsPushNotificationsDistributorFCMUP, 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<ThemeMode>( late final themeMode = SelectOption<ThemeMode>(
storage: _storage, storage: _storage,
key: 'theme-mode', key: 'theme-mode',
@ -147,7 +148,7 @@ class GlobalOptions {
storage: _storage, storage: _storage,
key: 'push-notifications-enabled', key: 'push-notifications-enabled',
label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsEnabled, label: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsEnabled,
defaultValue: BehaviorSubject.seeded(true), defaultValue: BehaviorSubject.seeded(false),
enabled: _pushNotificationsEnabledEnabledSubject, enabled: _pushNotificationsEnabledEnabledSubject,
); );

23
packages/neon/lib/src/utils/push_utils.dart

@ -27,7 +27,7 @@ class PushUtils {
android: const AndroidInitializationSettings('@mipmap/ic_launcher'), android: const AndroidInitializationSettings('@mipmap/ic_launcher'),
linux: LinuxInitializationSettings( linux: LinuxInitializationSettings(
defaultActionName: 'Open', defaultActionName: 'Open',
defaultIcon: AssetsLinuxIcon('assets/logo_harbour.svg'), defaultIcon: AssetsLinuxIcon('assets/logo_neon.svg'),
), ),
), ),
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse, onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
@ -35,14 +35,14 @@ class PushUtils {
return localNotificationsPlugin; return localNotificationsPlugin;
} }
static Future onMessage(final Uint8List message, final String instance) async { static Future onMessage(final Uint8List messages, final String instance) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
final localNotificationsPlugin = await initLocalNotifications( final localNotificationsPlugin = await initLocalNotifications(
onDidReceiveNotificationResponse: (final notification) async { onDidReceiveNotificationResponse: (final notification) async {
if (Global.onPushNotificationClicked != null && notification.payload != null) { if (Global.onPushNotificationClicked != null && notification.payload != null) {
await Global.onPushNotificationClicked!( await Global.onPushNotificationClicked!(
PushNotificationWithAccountID.fromJson( PushNotification.fromJson(
json.decode(notification.payload!) as Map<String, dynamic>, json.decode(notification.payload!) as Map<String, dynamic>,
), ),
); );
@ -52,8 +52,10 @@ class PushUtils {
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = await SharedPreferences.getInstance();
final keypair = await loadRSAKeypair(AppStorage('notifications', sharedPreferences)); final keypair = await loadRSAKeypair(AppStorage('notifications', sharedPreferences));
final data = json.decode(utf8.decode(message)) as Map<String, dynamic>;
final notification = NextcloudNotificationsPushNotification( for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) {
final data = json.decode(message) as Map<String, dynamic>;
final notification = PushNotification(
accountID: instance, accountID: instance,
priority: data['priority']! as String, priority: data['priority']! as String,
type: data['type']! as String, type: data['type']! as String,
@ -69,7 +71,7 @@ class PushUtils {
return; return;
} }
if (notification.type == 'background') { if (notification.type == 'background') {
debugPrint('Got unknown background notification $notification.subject'); debugPrint('Got unknown background notification ${json.encode(notification.toJson())}');
return; return;
} }
@ -115,19 +117,22 @@ class PushUtils {
), ),
), ),
payload: json.encode( payload: json.encode(
PushNotificationWithAccountID( PushNotification(
notification: notification,
accountID: instance, accountID: instance,
priority: notification.priority,
type: notification.type,
subject: notification.subject,
).toJson(), ).toJson(),
), ),
); );
Global.onPushNotificationReceived?.call(instance); Global.onPushNotificationReceived?.call(instance);
} }
}
static int _getNotificationID( static int _getNotificationID(
final String instance, 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); sha256.convert(utf8.encode('$instance${notification.subject.nid}')).bytes.reduce((final a, final b) => a + b);
} }

Loading…
Cancel
Save