From 0022bc1d7ac2a00c7cb5f8b2b3bc28a784eea8ea Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Sat, 8 Jul 2023 09:41:44 +0200 Subject: [PATCH] feat(neon): make Option listenable --- packages/neon/neon/lib/settings.dart | 1 - packages/neon/neon/lib/src/app.dart | 19 ++- .../neon/neon/lib/src/blocs/accounts.dart | 20 ++- packages/neon/neon/lib/src/blocs/apps.dart | 15 +-- .../neon/neon/lib/src/blocs/next_push.dart | 8 +- .../lib/src/blocs/push_notifications.dart | 63 ++++++---- .../neon/lib/src/pages/account_settings.dart | 2 +- packages/neon/neon/lib/src/pages/home.dart | 7 +- .../lib/src/pages/nextcloud_app_settings.dart | 2 +- .../neon/neon/lib/src/pages/settings.dart | 17 ++- .../models/nextcloud_app_options.dart | 4 +- .../neon/lib/src/settings/models/option.dart | 109 ++++++++++++----- .../src/settings/models/select_option.dart | 66 ++++++---- .../src/settings/models/toggle_option.dart | 28 +++-- .../widgets/checkbox_settings_tile.dart | 26 ++-- .../dropdown_button_settings_tile.dart | 85 +++++-------- .../src/settings/widgets/option_builder.dart | 24 ---- .../lib/src/sort_box/sort_box_builder.dart | 11 +- .../neon/lib/src/utils/account_options.dart | 28 ++--- .../neon/lib/src/utils/global_options.dart | 114 ++++++++---------- .../neon/lib/src/utils/global_popups.dart | 5 +- .../lib/src/utils/settings_export_helper.dart | 8 +- packages/neon/neon_files/lib/blocs/files.dart | 19 ++- packages/neon/neon_files/lib/options.dart | 18 +-- .../neon_files/lib/widgets/file_preview.dart | 27 +++-- packages/neon/neon_news/lib/options.dart | 38 +++--- .../neon_news/lib/widgets/folder_view.dart | 4 +- packages/neon/neon_notes/lib/options.dart | 20 +-- 28 files changed, 399 insertions(+), 389 deletions(-) delete mode 100644 packages/neon/neon/lib/src/settings/widgets/option_builder.dart diff --git a/packages/neon/neon/lib/settings.dart b/packages/neon/neon/lib/settings.dart index c7a77f97..e8d9b756 100644 --- a/packages/neon/neon/lib/settings.dart +++ b/packages/neon/neon/lib/settings.dart @@ -3,4 +3,3 @@ export 'package:neon/src/settings/models/options_category.dart'; export 'package:neon/src/settings/models/select_option.dart'; export 'package:neon/src/settings/models/storage.dart'; export 'package:neon/src/settings/models/toggle_option.dart'; -export 'package:neon/src/settings/widgets/option_builder.dart'; diff --git a/packages/neon/neon/lib/src/app.dart b/packages/neon/neon/lib/src/app.dart index e7340ef5..facb5fe9 100644 --- a/packages/neon/neon/lib/src/app.dart +++ b/packages/neon/neon/lib/src/app.dart @@ -15,7 +15,6 @@ import 'package:neon/src/models/notifications_interface.dart'; import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/router.dart'; -import 'package:neon/src/settings/widgets/option_builder.dart'; import 'package:neon/src/theme/neon.dart'; import 'package:neon/src/theme/theme.dart'; import 'package:neon/src/utils/global.dart'; @@ -102,8 +101,8 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra } if (_platform.canUseSystemTray) { - _globalOptions.systemTrayEnabled.stream.listen((final enabled) async { - if (enabled) { + _globalOptions.systemTrayEnabled.addListener(() async { + if (_globalOptions.systemTrayEnabled.value) { // TODO: This works on Linux, but maybe not on macOS or Windows await tray.trayManager.setIcon('assets/logo.svg'); if (mounted) { @@ -275,13 +274,13 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra } @override - Widget build(final BuildContext context) => OptionBuilder( - option: _globalOptions.themeMode, - builder: (final context, final themeMode) => OptionBuilder( - option: _globalOptions.themeOLEDAsDark, - builder: (final context, final themeOLEDAsDark) => OptionBuilder( - option: _globalOptions.themeKeepOriginalAccentColor, - builder: (final context, final themeKeepOriginalAccentColor) => StreamBuilder( + Widget build(final BuildContext context) => ValueListenableBuilder( + valueListenable: _globalOptions.themeMode, + builder: (final context, final themeMode, final _) => ValueListenableBuilder( + valueListenable: _globalOptions.themeOLEDAsDark, + builder: (final context, final themeOLEDAsDark, final _) => ValueListenableBuilder( + valueListenable: _globalOptions.themeKeepOriginalAccentColor, + builder: (final context, final themeKeepOriginalAccentColor, final _) => StreamBuilder( stream: _accountsBloc.activeAccount, builder: (final context, final activeAccountSnapshot) { FlutterNativeSplash.remove(); diff --git a/packages/neon/neon/lib/src/blocs/accounts.dart b/packages/neon/neon/lib/src/blocs/accounts.dart index 03e61d4b..407f9426 100644 --- a/packages/neon/neon/lib/src/blocs/accounts.dart +++ b/packages/neon/neon/lib/src/blocs/accounts.dart @@ -89,18 +89,14 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState } } - unawaited( - _globalOptions.initialAccount.stream.first.then((final lastAccount) { - final account = as.tryFind(lastAccount); - if (activeAccount.valueOrNull == null) { - if (account != null) { - setActiveAccount(account); - } else if (as.isNotEmpty) { - setActiveAccount(as.first); - } - } - }), - ); + final account = as.tryFind(_globalOptions.initialAccount.value); + if (activeAccount.valueOrNull == null) { + if (account != null) { + setActiveAccount(account); + } else if (as.isNotEmpty) { + setActiveAccount(as.first); + } + } } final RequestManager _requestManager; diff --git a/packages/neon/neon/lib/src/blocs/apps.dart b/packages/neon/neon/lib/src/blocs/apps.dart index b5b00059..bd2fee0d 100644 --- a/packages/neon/neon/lib/src/blocs/apps.dart +++ b/packages/neon/neon/lib/src/blocs/apps.dart @@ -49,21 +49,16 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates .add(result.transform((final data) => _filteredAppImplementations(data.map((final a) => a.id)))); }); - appImplementations.listen((final result) { + appImplementations.listen((final result) async { if (!result.hasData) { return; } final options = _accountsBloc.getOptionsFor(_account); - unawaited( - options.initialApp.stream.first.then((var initialApp) async { - initialApp ??= _getInitialAppFallback(); - - if (!activeApp.hasValue && initialApp != null) { - await setActiveApp(initialApp); - } - }), - ); + final initialApp = options.initialApp.value ?? _getInitialAppFallback(); + if (!activeApp.hasValue && initialApp != null) { + await setActiveApp(initialApp); + } unawaited(_checkCompatibility()); }); diff --git a/packages/neon/neon/lib/src/blocs/next_push.dart b/packages/neon/neon/lib/src/blocs/next_push.dart index 04e8c297..c1ba9bc3 100644 --- a/packages/neon/neon/lib/src/blocs/next_push.dart +++ b/packages/neon/neon/lib/src/blocs/next_push.dart @@ -27,19 +27,15 @@ class NextPushBloc extends Bloc implements NextPushBlocEvents, NextPushBlocState 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) { + if (!_globalOptions.pushNotificationsEnabled.enabled || !_globalOptions.pushNotificationsEnabled.value) { return; } if (_globalOptions.pushNotificationsDistributor.value != null) { return; } - if (_globalOptions.pushNotificationsDistributor.values.value.containsKey(unifiedPushNextPushID)) { + if (_globalOptions.pushNotificationsDistributor.values.containsKey(unifiedPushNextPushID)) { // NextPush is already installed return; } diff --git a/packages/neon/neon/lib/src/blocs/push_notifications.dart b/packages/neon/neon/lib/src/blocs/push_notifications.dart index d2e03cf9..a59a03f6 100644 --- a/packages/neon/neon/lib/src/blocs/push_notifications.dart +++ b/packages/neon/neon/lib/src/blocs/push_notifications.dart @@ -32,16 +32,22 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, if (_platform.canUsePushNotifications) { unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors)); - _globalOptions.pushNotificationsEnabled.stream.listen((final enabled) async { - if (enabled != _pushNotificationsEnabled) { - _pushNotificationsEnabled = enabled; - if (enabled) { - // We just use a single RSA keypair for all accounts - _keypair = await PushUtils.loadRSAKeypair(_storage); - await _setupUnifiedPush(); - } - } - }); + _globalOptions.pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener); + } + } + + Future _pushNotificationsEnabledListener() async { + final enabled = _globalOptions.pushNotificationsEnabled.value; + if (enabled != _pushNotificationsEnabled) { + _pushNotificationsEnabled = enabled; + if (enabled) { + // We just use a single RSA keypair for all accounts + _keypair = await PushUtils.loadRSAKeypair(_storage); + await _setupUnifiedPush(); + } else { + _globalOptions.pushNotificationsDistributor.removeListener(_distributorListener); + unawaited(_accountsListener?.cancel()); + } } } @@ -54,10 +60,13 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, bool? _pushNotificationsEnabled; final _notificationsController = StreamController(); + StreamSubscription? _accountsListener; @override void dispose() { unawaited(_notificationsController.close()); + unawaited(_accountsListener?.cancel()); + _globalOptions.pushNotificationsEnabled.removeListener(_pushNotificationsEnabledListener); } @override @@ -96,23 +105,25 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, onMessage: PushUtils.onMessage, ); - _globalOptions.pushNotificationsDistributor.stream.listen((final distributor) async { - final disabled = distributor == null; - final sameDistributor = distributor == await UnifiedPush.getDistributor(); - final accounts = _accountsBloc.accounts.value; - if (disabled || !sameDistributor) { - await _unregisterUnifiedPushInstances(accounts); - } - if (!disabled && !sameDistributor) { - debugPrint('UnifiedPush distributor changed to $distributor'); - await UnifiedPush.saveDistributor(distributor); - } - if (!disabled) { - await _registerUnifiedPushInstances(accounts); - } - }); + _globalOptions.pushNotificationsDistributor.addListener(_distributorListener); + _accountsListener = _accountsBloc.accounts.listen(_registerUnifiedPushInstances); + } - _accountsBloc.accounts.listen(_registerUnifiedPushInstances); + Future _distributorListener() async { + final distributor = _globalOptions.pushNotificationsDistributor.value; + final disabled = distributor == null; + final sameDistributor = distributor == await UnifiedPush.getDistributor(); + final accounts = _accountsBloc.accounts.value; + if (disabled || !sameDistributor) { + await _unregisterUnifiedPushInstances(accounts); + } + if (!disabled && !sameDistributor) { + debugPrint('UnifiedPush distributor changed to $distributor'); + await UnifiedPush.saveDistributor(distributor); + } + if (!disabled) { + await _registerUnifiedPushInstances(accounts); + } } Future _unregisterUnifiedPushInstances(final List accounts) async { diff --git a/packages/neon/neon/lib/src/pages/account_settings.dart b/packages/neon/neon/lib/src/pages/account_settings.dart index f99d90b8..618af5f4 100644 --- a/packages/neon/neon/lib/src/pages/account_settings.dart +++ b/packages/neon/neon/lib/src/pages/account_settings.dart @@ -66,7 +66,7 @@ class AccountSettingsPage extends StatelessWidget { context, AppLocalizations.of(context).settingsResetForConfirmation(name), )) { - await options.reset(); + options.reset(); } }, tooltip: AppLocalizations.of(context).settingsResetFor(name), diff --git a/packages/neon/neon/lib/src/pages/home.dart b/packages/neon/neon/lib/src/pages/home.dart index 144565ee..b0e31211 100644 --- a/packages/neon/neon/lib/src/pages/home.dart +++ b/packages/neon/neon/lib/src/pages/home.dart @@ -7,7 +7,6 @@ import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/apps.dart'; import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/app_implementation.dart'; -import 'package:neon/src/settings/widgets/option_builder.dart'; import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart' as global_options; import 'package:neon/src/utils/global_popups.dart'; @@ -150,9 +149,9 @@ class _HomePageState extends State { }, ); - final body = OptionBuilder( - option: _globalOptions.navigationMode, - builder: (final context, final navigationMode) { + final body = ValueListenableBuilder( + valueListenable: _globalOptions.navigationMode, + builder: (final context, final navigationMode, final _) { final drawerAlwaysVisible = navigationMode == global_options.NavigationMode.drawerAlwaysVisible; final body = Scaffold( diff --git a/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart b/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart index 6d236458..b67458d4 100644 --- a/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart +++ b/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart @@ -30,7 +30,7 @@ class NextcloudAppSettingsPage extends StatelessWidget { context, AppLocalizations.of(context).settingsResetForConfirmation(appImplementation.name(context)), )) { - await appImplementation.options.reset(); + appImplementation.options.reset(); } }, tooltip: AppLocalizations.of(context).settingsResetFor(appImplementation.name(context)), diff --git a/packages/neon/neon/lib/src/pages/settings.dart b/packages/neon/neon/lib/src/pages/settings.dart index 61d4930e..5065abc5 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -64,14 +64,14 @@ class _SettingsPageState extends State { IconButton( onPressed: () async { if (await showConfirmationDialog(context, AppLocalizations.of(context).settingsResetAllConfirmation)) { - await globalOptions.reset(); + globalOptions.reset(); for (final appImplementation in appImplementations) { - await appImplementation.options.reset(); + appImplementation.options.reset(); } for (final account in accountsBloc.accounts.value) { - await accountsBloc.getOptionsFor(account).reset(); + accountsBloc.getOptionsFor(account).reset(); } } }, @@ -88,12 +88,12 @@ class _SettingsPageState extends State { final accountsSnapshot, ) { final platform = Provider.of(context, listen: false); - return StreamBuilder( - stream: globalOptions.pushNotificationsEnabled.enabled, - initialData: globalOptions.pushNotificationsEnabled.enabled.valueOrNull, + return ValueListenableBuilder( + valueListenable: globalOptions.pushNotificationsEnabled, builder: ( final context, - final pushNotificationsEnabledEnabledSnapshot, + final _, + final __, ) => SettingsList( initialCategory: widget.initialCategory?.name, @@ -144,8 +144,7 @@ class _SettingsPageState extends State { title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), key: ValueKey(SettingsCageories.pushNotifications.name), tiles: [ - if (pushNotificationsEnabledEnabledSnapshot.hasData && - !pushNotificationsEnabledEnabledSnapshot.requireData) ...[ + if (!globalOptions.pushNotificationsEnabled.enabled) ...[ TextSettingsTile( text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice, style: TextStyle( diff --git a/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart b/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart index 2f6ceeb9..53b32f10 100644 --- a/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart +++ b/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart @@ -9,9 +9,9 @@ abstract class NextcloudAppOptions { late final List categories; late final List