diff --git a/packages/neon/integration_test/screenshot_test.dart b/packages/neon/integration_test/screenshot_test.dart index f07ce56f..c6ff344c 100644 --- a/packages/neon/integration_test/screenshot_test.dart +++ b/packages/neon/integration_test/screenshot_test.dart @@ -132,6 +132,11 @@ Future pumpAppPage( sharedPreferences, disabled: true, ); + final nextPushBloc = NextPushBloc( + accountsBloc, + globalOptions, + disabled: true, + ); // ignore: close_sinks final userThemeStream = BehaviorSubject(); @@ -163,6 +168,9 @@ Future pumpAppPage( Provider( create: (final _) => firstLaunchBloc, ), + Provider( + create: (final _) => nextPushBloc, + ), Provider>( create: (final _) => allAppImplementations, ), diff --git a/packages/neon/lib/l10n/en.arb b/packages/neon/lib/l10n/en.arb index e0fcb88f..b5802a01 100644 --- a/packages/neon/lib/l10n/en.arb +++ b/packages/neon/lib/l10n/en.arb @@ -51,6 +51,10 @@ "showSlashHide": "Show/Hide", "exit": "Exit", "disabled": "Disabled", + "firstLaunchGoToSettingsToEnablePushNotifications": "Go to the settings to enable push notifications", + "nextPushSupported": "NextPush is supported!", + "nextPushSupportedText": "NextPush is a FOSS way of receiving push notifications using the UnifiedPush protocol via a Nextcloud instance.\nYou can install NextPush from the F-Droid app store.", + "nextPushSupportedInstall": "Install NextPush", "settings": "Settings", "settingsApps": "Apps", "settingsExport": "Export settings", @@ -65,7 +69,6 @@ } } }, - "settingsGoToSettingsToEnablePushNotifications": "Go to the settings to enable push notifications", "optionsCategoryGeneral": "General", "optionsCategoryTheme": "Theme", "optionsCategoryPushNotifications": "Push notifications", diff --git a/packages/neon/lib/l10n/localizations.dart b/packages/neon/lib/l10n/localizations.dart index f7febc86..0a1ae2f9 100644 --- a/packages/neon/lib/l10n/localizations.dart +++ b/packages/neon/lib/l10n/localizations.dart @@ -269,6 +269,30 @@ abstract class AppLocalizations { /// **'Disabled'** String get disabled; + /// No description provided for @firstLaunchGoToSettingsToEnablePushNotifications. + /// + /// In en, this message translates to: + /// **'Go to the settings to enable push notifications'** + String get firstLaunchGoToSettingsToEnablePushNotifications; + + /// No description provided for @nextPushSupported. + /// + /// In en, this message translates to: + /// **'NextPush is supported!'** + String get nextPushSupported; + + /// No description provided for @nextPushSupportedText. + /// + /// In en, this message translates to: + /// **'NextPush is a FOSS way of receiving push notifications using the UnifiedPush protocol via a Nextcloud instance.\nYou can install NextPush from the F-Droid app store.'** + String get nextPushSupportedText; + + /// No description provided for @nextPushSupportedInstall. + /// + /// In en, this message translates to: + /// **'Install NextPush'** + String get nextPushSupportedInstall; + /// No description provided for @settings. /// /// In en, this message translates to: @@ -311,12 +335,6 @@ abstract class AppLocalizations { /// **'Do you want to reset all settings for {name}?'** String settingsResetForConfirmation(String name); - /// No description provided for @settingsGoToSettingsToEnablePushNotifications. - /// - /// In en, this message translates to: - /// **'Go to the settings to enable push notifications'** - String get settingsGoToSettingsToEnablePushNotifications; - /// No description provided for @optionsCategoryGeneral. /// /// 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 4f0bb430..917ca1ec 100644 --- a/packages/neon/lib/l10n/localizations_en.dart +++ b/packages/neon/lib/l10n/localizations_en.dart @@ -103,6 +103,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get disabled => 'Disabled'; + @override + String get firstLaunchGoToSettingsToEnablePushNotifications => 'Go to the settings to enable push notifications'; + + @override + String get nextPushSupported => 'NextPush is supported!'; + + @override + String get nextPushSupportedText => + 'NextPush is a FOSS way of receiving push notifications using the UnifiedPush protocol via a Nextcloud instance.\nYou can install NextPush from the F-Droid app store.'; + + @override + String get nextPushSupportedInstall => 'Install NextPush'; + @override String get settings => 'Settings'; @@ -126,9 +139,6 @@ class AppLocalizationsEn extends AppLocalizations { return 'Do you want to reset all settings for $name?'; } - @override - String get settingsGoToSettingsToEnablePushNotifications => 'Go to the settings to enable push notifications'; - @override String get optionsCategoryGeneral => 'General'; diff --git a/packages/neon/lib/main.dart b/packages/neon/lib/main.dart index a01d0d7a..aa848470 100644 --- a/packages/neon/lib/main.dart +++ b/packages/neon/lib/main.dart @@ -55,6 +55,7 @@ Future main() async { platform, ); final firstLaunchBloc = FirstLaunchBloc(sharedPreferences); + final nextPushBloc = NextPushBloc(accountsBloc, globalOptions); runApp( MultiProvider( @@ -83,6 +84,9 @@ Future main() async { Provider( create: (final _) => firstLaunchBloc, ), + Provider( + create: (final _) => nextPushBloc, + ), Provider>( create: (final _) => allAppImplementations, ), diff --git a/packages/neon/lib/src/blocs/next_push.dart b/packages/neon/lib/src/blocs/next_push.dart new file mode 100644 index 00000000..28027cf2 --- /dev/null +++ b/packages/neon/lib/src/blocs/next_push.dart @@ -0,0 +1,77 @@ +part of '../neon.dart'; + +abstract class NextPushBlocEvents {} + +abstract class NextPushBlocStates { + BehaviorSubject get onNextPushSupported; +} + +class NextPushBloc extends Bloc implements NextPushBlocEvents, NextPushBlocStates { + NextPushBloc( + this._accountsBloc, + this._globalOptions, { + final bool disabled = false, + }) { + if (disabled) { + return; + } + Rx.merge([ + _globalOptions.pushNotificationsEnabled.stream, + _globalOptions.pushNotificationsDistributor.stream, + _globalOptions.pushNotificationsDistributor.values, + _accountsBloc.accounts, + ]).debounceTime(const Duration(milliseconds: 100)).listen((final _) async { + if (!_globalOptions.pushNotificationsEnabled.enabled.hasValue || + !_globalOptions.pushNotificationsEnabled.enabled.value || + !_globalOptions.pushNotificationsEnabled.hasValue || + !_globalOptions.pushNotificationsEnabled.value) { + return; + } + if (_globalOptions.pushNotificationsDistributor.value != null) { + return; + } + if (_globalOptions.pushNotificationsDistributor.values.value.containsKey(unifiedPushNextPushID)) { + // NextPush is already installed + return; + } + + var supported = false; + for (final account in _accountsBloc.accounts.value) { + if (!_supported.containsKey(account)) { + try { + _supported[account] = (await account.client.unifiedPushProvider.check()).success; + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + _supported[account] = false; + } + } + if (_supported[account]!) { + supported = true; + break; + } + } + + if (!supported) { + return; + } + + if (onNextPushSupported.hasValue) { + return; + } + onNextPushSupported.add(null); + }); + } + + final AccountsBloc _accountsBloc; + final GlobalOptions _globalOptions; + final _supported = {}; + + @override + void dispose() { + unawaited(onNextPushSupported.close()); + } + + @override + BehaviorSubject onNextPushSupported = BehaviorSubject(); +} diff --git a/packages/neon/lib/src/neon.dart b/packages/neon/lib/src/neon.dart index 84b72364..64c4fb0b 100644 --- a/packages/neon/lib/src/neon.dart +++ b/packages/neon/lib/src/neon.dart @@ -52,6 +52,7 @@ part 'blocs/apps.dart'; part 'blocs/capabilities.dart'; part 'blocs/first_launch.dart'; part 'blocs/login.dart'; +part 'blocs/next_push.dart'; part 'blocs/push_notifications.dart'; part 'blocs/user_details.dart'; part 'blocs/user_status.dart'; diff --git a/packages/neon/lib/src/pages/home.dart b/packages/neon/lib/src/pages/home.dart index bc0e14f9..c6bacb75 100644 --- a/packages/neon/lib/src/pages/home.dart +++ b/packages/neon/lib/src/pages/home.dart @@ -25,6 +25,7 @@ class _HomePageState extends State { late AppsBloc _appsBloc; late CapabilitiesBloc _capabilitiesBloc; late FirstLaunchBloc _firstLaunchBloc; + late NextPushBloc _nextPushBloc; @override void initState() { @@ -35,6 +36,7 @@ class _HomePageState extends State { _appsBloc = _accountsBloc.getAppsBloc(widget.account); _capabilitiesBloc = _accountsBloc.getCapabilitiesBloc(widget.account); _firstLaunchBloc = Provider.of(context, listen: false); + _nextPushBloc = Provider.of(context, listen: false); _appsBloc.openNotifications.listen((final _) async { final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull; @@ -101,7 +103,7 @@ class _HomePageState extends State { _firstLaunchBloc.onFirstLaunch.listen((final _) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(AppLocalizations.of(context).settingsGoToSettingsToEnablePushNotifications), + content: Text(AppLocalizations.of(context).firstLaunchGoToSettingsToEnablePushNotifications), action: SnackBarAction( label: AppLocalizations.of(context).settings, onPressed: () async { @@ -116,6 +118,34 @@ class _HomePageState extends State { ); }); + _nextPushBloc.onNextPushSupported.listen((final _) async { + await showDialog( + context: context, + builder: (final context) => AlertDialog( + title: Text(AppLocalizations.of(context).nextPushSupported), + content: Text(AppLocalizations.of(context).nextPushSupportedText), + actions: [ + OutlinedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(AppLocalizations.of(context).no), + ), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + launchUrlString( + 'https://f-droid.org/packages/$unifiedPushNextPushID', + mode: LaunchMode.externalApplication, + ); + }, + child: Text(AppLocalizations.of(context).nextPushSupportedInstall), + ), + ], + ), + ); + }); + unawaited(_checkMaintenanceMode()); } diff --git a/packages/neon/lib/src/utils/global_options.dart b/packages/neon/lib/src/utils/global_options.dart index 5df411c4..8845faf9 100644 --- a/packages/neon/lib/src/utils/global_options.dart +++ b/packages/neon/lib/src/utils/global_options.dart @@ -1,5 +1,7 @@ part of '../neon.dart'; +const unifiedPushNextPushID = 'org.unifiedpush.distributor.nextpush'; + class GlobalOptions { GlobalOptions( this._storage, @@ -63,7 +65,7 @@ class GlobalOptions { 'io.heckel.ntfy': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorNtfy, 'org.unifiedpush.distributor.fcm': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorFCMUP, - 'org.unifiedpush.distributor.nextpush': (final context) => + unifiedPushNextPushID: (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorNextPush, 'org.unifiedpush.distributor.noprovider2push': (final context) => AppLocalizations.of(context).globalOptionsPushNotificationsDistributorNoProvider2Push,