From 7253352a9f9c7e5c4e73b5b6a7315df712ee1d0f Mon Sep 17 00:00:00 2001 From: jld3103 Date: Sun, 16 Jul 2023 10:40:24 +0200 Subject: [PATCH] refactor(neon,neon_files,neon_news,neon_notes,neon_notifications,app): Use global instance of NeonPlatform Signed-off-by: jld3103 --- packages/app/lib/apps.dart | 10 +- packages/neon/neon/lib/neon.dart | 13 +- packages/neon/neon/lib/src/app.dart | 22 +- .../neon/neon/lib/src/blocs/accounts.dart | 4 - .../lib/src/blocs/push_notifications.dart | 4 +- .../neon/lib/src/blocs/user_statuses.dart | 12 +- .../lib/src/models/app_implementation.dart | 3 - .../src/models/notifications_interface.dart | 5 +- packages/neon/neon/lib/src/pages/login.dart | 4 +- .../neon/neon/lib/src/pages/settings.dart | 392 +++++++++--------- .../neon/neon/lib/src/platform/platform.dart | 46 +- .../neon/lib/src/utils/global_popups.dart | 3 +- .../neon/lib/src/utils/request_manager.dart | 9 +- packages/neon/neon_files/lib/blocs/files.dart | 6 +- .../neon_files/lib/dialogs/choose_create.dart | 2 +- packages/neon/neon_files/lib/neon_files.dart | 3 +- packages/neon/neon_news/lib/neon_news.dart | 4 +- packages/neon/neon_news/lib/options.dart | 6 +- .../neon_news/lib/widgets/articles_view.dart | 2 +- packages/neon/neon_notes/lib/neon_notes.dart | 2 +- .../lib/neon_notifications.dart | 2 +- 21 files changed, 270 insertions(+), 284 deletions(-) diff --git a/packages/app/lib/apps.dart b/packages/app/lib/apps.dart index 093bbc81..b93b4baa 100644 --- a/packages/app/lib/apps.dart +++ b/packages/app/lib/apps.dart @@ -1,5 +1,4 @@ import 'package:neon/models.dart'; -import 'package:neon/platform.dart'; import 'package:neon_files/neon_files.dart'; import 'package:neon_news/neon_news.dart'; import 'package:neon_notes/neon_notes.dart'; @@ -8,11 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart'; List getAppImplementations( final SharedPreferences sharedPreferences, - final NeonPlatform platform, ) => [ - FilesApp(sharedPreferences, platform), - NewsApp(sharedPreferences, platform), - NotesApp(sharedPreferences, platform), - NotificationsApp(sharedPreferences, platform), + FilesApp(sharedPreferences), + NewsApp(sharedPreferences), + NotesApp(sharedPreferences), + NotificationsApp(sharedPreferences), ]; diff --git a/packages/neon/neon/lib/neon.dart b/packages/neon/neon/lib/neon.dart index ab74d0fc..5973bfea 100644 --- a/packages/neon/neon/lib/neon.dart +++ b/packages/neon/neon/lib/neon.dart @@ -9,7 +9,6 @@ import 'package:neon/src/blocs/next_push.dart'; import 'package:neon/src/blocs/push_notifications.dart'; import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/app_implementation.dart'; -import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/theme/neon.dart'; import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/request_manager.dart'; @@ -19,7 +18,7 @@ import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; Future runNeon({ - required final Iterable Function(SharedPreferences, NeonPlatform) getAppImplementations, + required final Iterable Function(SharedPreferences) getAppImplementations, required final NeonTheme theme, @visibleForTesting final WidgetsBinding? bindingOverride, @visibleForTesting final Account? account, @@ -31,9 +30,8 @@ Future runNeon({ final sharedPreferences = await SharedPreferences.getInstance(); - final platform = await getNeonPlatform(); - await RequestManager.instance.initCache(platform); - final allAppImplementations = getAppImplementations(sharedPreferences, platform); + await RequestManager.instance.initCache(); + final allAppImplementations = getAppImplementations(sharedPreferences); final packageInfo = await PackageInfo.fromPlatform(); buildUserAgent(packageInfo); @@ -44,7 +42,6 @@ Future runNeon({ ); final accountsBloc = AccountsBloc( - platform, sharedPreferences, globalOptions, allAppImplementations, @@ -58,7 +55,6 @@ Future runNeon({ accountsBloc, sharedPreferences, globalOptions, - platform, ); final firstLaunchBloc = FirstLaunchBloc( sharedPreferences, @@ -76,9 +72,6 @@ Future runNeon({ Provider( create: (final _) => sharedPreferences, ), - Provider( - create: (final _) => platform, - ), Provider( create: (final _) => globalOptions, ), diff --git a/packages/neon/neon/lib/src/app.dart b/packages/neon/neon/lib/src/app.dart index 5673d950..4018c94d 100644 --- a/packages/neon/neon/lib/src/app.dart +++ b/packages/neon/neon/lib/src/app.dart @@ -44,7 +44,6 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true); final _navigatorKey = GlobalKey(); late final Iterable _appImplementations; - late final NeonPlatform _platform; late final GlobalOptions _globalOptions; late final AccountsBloc _accountsBloc; late final _routerDelegate = AppRouter( @@ -59,15 +58,14 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra super.initState(); _appImplementations = Provider.of>(context, listen: false); - _platform = Provider.of(context, listen: false); _globalOptions = Provider.of(context, listen: false); _accountsBloc = Provider.of(context, listen: false); WidgetsBinding.instance.addObserver(this); - if (_platform.canUseSystemTray) { + if (NeonPlatform.instance.canUseSystemTray) { tray.trayManager.addListener(this); } - if (_platform.canUseWindowManager) { + if (NeonPlatform.instance.canUseWindowManager) { windowManager.addListener(this); } @@ -77,7 +75,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra if (!mounted) { return; } - if (_platform.canUseQuickActions) { + if (NeonPlatform.instance.canUseQuickActions) { const quickActions = QuickActions(); await quickActions.setShortcutItems( _appImplementations @@ -93,7 +91,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra await quickActions.initialize(_handleShortcut); } - if (_platform.canUseWindowManager) { + if (NeonPlatform.instance.canUseWindowManager) { await windowManager.setPreventClose(true); if (_globalOptions.startupMinimized.value) { @@ -101,7 +99,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra } } - if (_platform.canUseSystemTray) { + if (NeonPlatform.instance.canUseSystemTray) { _globalOptions.systemTrayEnabled.addListener(() async { if (_globalOptions.systemTrayEnabled.value) { // TODO: This works on Linux, but maybe not on macOS or Windows @@ -136,7 +134,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra }); } - if (_platform.canUsePushNotifications) { + if (NeonPlatform.instance.canUsePushNotifications) { final localNotificationsPlugin = await PushUtils.initLocalNotifications(); Global.onPushNotificationReceived = (final accountID) async { final account = _accountsBloc.accounts.value.tryFind(accountID); @@ -213,7 +211,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra Future _handleShortcut(final String shortcutType) async { if (shortcutType == 'show_hide') { - if (_platform.canUseWindowManager) { + if (NeonPlatform.instance.canUseWindowManager) { if (await windowManager.isVisible()) { await _saveAndMinimizeWindow(); } else { @@ -249,7 +247,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra } Future _showAndRestoreWindow() async { - if (!_platform.canUseWindowManager) { + if (!NeonPlatform.instance.canUseWindowManager) { return; } @@ -264,10 +262,10 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra @override void dispose() { WidgetsBinding.instance.removeObserver(this); - if (_platform.canUseSystemTray) { + if (NeonPlatform.instance.canUseSystemTray) { tray.trayManager.removeListener(this); } - if (_platform.canUseWindowManager) { + if (NeonPlatform.instance.canUseWindowManager) { windowManager.removeListener(this); } diff --git a/packages/neon/neon/lib/src/blocs/accounts.dart b/packages/neon/neon/lib/src/blocs/accounts.dart index 26d3ff07..ce3de5a5 100644 --- a/packages/neon/neon/lib/src/blocs/accounts.dart +++ b/packages/neon/neon/lib/src/blocs/accounts.dart @@ -10,7 +10,6 @@ import 'package:neon/src/blocs/user_details.dart'; import 'package:neon/src/blocs/user_statuses.dart'; import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/app_implementation.dart'; -import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/utils/account_options.dart'; import 'package:neon/src/utils/global_options.dart'; @@ -57,7 +56,6 @@ abstract interface class AccountsBlocStates { class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocStates { AccountsBloc( - this._platform, this._sharedPreferences, this._globalOptions, this._allAppImplementations, @@ -98,7 +96,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState } } - final NeonPlatform _platform; late final AppStorage _storage = AppStorage('accounts', _sharedPreferences); final SharedPreferences _sharedPreferences; final GlobalOptions _globalOptions; @@ -287,7 +284,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState } return _userStatusesBlocs[account.id] = UserStatusesBloc( - _platform, account, ); } diff --git a/packages/neon/neon/lib/src/blocs/push_notifications.dart b/packages/neon/neon/lib/src/blocs/push_notifications.dart index 7b5e0134..d280b379 100644 --- a/packages/neon/neon/lib/src/blocs/push_notifications.dart +++ b/packages/neon/neon/lib/src/blocs/push_notifications.dart @@ -28,9 +28,8 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, this._accountsBloc, this._sharedPreferences, this._globalOptions, - this._platform, ) { - if (_platform.canUsePushNotifications) { + if (NeonPlatform.instance.canUsePushNotifications) { unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors)); _globalOptions.pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener); @@ -40,7 +39,6 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, } final AccountsBloc _accountsBloc; - final NeonPlatform _platform; final SharedPreferences _sharedPreferences; late final _storage = AppStorage(AppIDs.notifications, _sharedPreferences); final GlobalOptions _globalOptions; diff --git a/packages/neon/neon/lib/src/blocs/user_statuses.dart b/packages/neon/neon/lib/src/blocs/user_statuses.dart index aa2989e3..d5867aad 100644 --- a/packages/neon/neon/lib/src/blocs/user_statuses.dart +++ b/packages/neon/neon/lib/src/blocs/user_statuses.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; +import 'package:neon/platform.dart'; import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/bloc/result.dart'; import 'package:neon/src/blocs/timer.dart'; import 'package:neon/src/models/account.dart'; -import 'package:neon/src/platform/platform.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:rxdart/rxdart.dart'; import 'package:window_manager/window_manager.dart'; @@ -22,14 +22,12 @@ abstract class UserStatusesBlocStates { @internal class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents, UserStatusesBlocStates { UserStatusesBloc( - this._platform, this._account, ) { unawaited(refresh()); _timer = TimerBloc().registerTimer(const Duration(minutes: 5), refresh); } - final NeonPlatform _platform; final Account _account; late final NeonTimer _timer; @@ -63,8 +61,12 @@ class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents UserStatusPublic? data; if (_account.username == username) { - final isAway = - _platform.canUseWindowManager && (!(await windowManager.isFocused()) || !(await windowManager.isVisible())); + var isAway = false; + if (NeonPlatform.instance.canUseWindowManager) { + final focused = await windowManager.isFocused(); + final visible = await windowManager.isFocused(); + isAway = !focused || !visible; + } try { final response = await _account.client.userStatus.heartbeat.heartbeat( status: isAway ? 'away' : 'online', diff --git a/packages/neon/neon/lib/src/models/app_implementation.dart b/packages/neon/neon/lib/src/models/app_implementation.dart index 8ead9a8d..fd59adc4 100644 --- a/packages/neon/neon/lib/src/models/app_implementation.dart +++ b/packages/neon/neon/lib/src/models/app_implementation.dart @@ -6,7 +6,6 @@ import 'package:neon/l10n/localizations.dart'; import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/models/account.dart'; -import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/settings/models/nextcloud_app_options.dart'; import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/widgets/drawer_destination.dart'; @@ -17,7 +16,6 @@ import 'package:shared_preferences/shared_preferences.dart'; abstract class AppImplementation { AppImplementation( final SharedPreferences sharedPreferences, - this.platform, ) { final storage = AppStorage('app-$id', sharedPreferences); options = buildOptions(storage); @@ -26,7 +24,6 @@ abstract class AppImplementation String get id; LocalizationsDelegate get localizationsDelegate; List get supportedLocales; - final NeonPlatform platform; String nameFromLocalization(final AppLocalizations localizations) => localizations.appImplementationName(id); String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); diff --git a/packages/neon/neon/lib/src/models/notifications_interface.dart b/packages/neon/neon/lib/src/models/notifications_interface.dart index 61bb56e6..bf2fcdb1 100644 --- a/packages/neon/neon/lib/src/models/notifications_interface.dart +++ b/packages/neon/neon/lib/src/models/notifications_interface.dart @@ -4,10 +4,7 @@ import 'package:neon/src/settings/models/nextcloud_app_options.dart'; abstract interface class NotificationsAppInterface extends AppImplementation { - NotificationsAppInterface( - super.sharedPreferences, - super.platform, - ); + NotificationsAppInterface(super.sharedPreferences); } abstract interface class NotificationsBlocInterface extends InteractiveBloc { diff --git a/packages/neon/neon/lib/src/pages/login.dart b/packages/neon/neon/lib/src/pages/login.dart index faa4726f..d0c9b9a7 100644 --- a/packages/neon/neon/lib/src/pages/login.dart +++ b/packages/neon/neon/lib/src/pages/login.dart @@ -6,7 +6,6 @@ import 'package:neon/src/theme/branding.dart'; import 'package:neon/src/theme/dialog.dart'; import 'package:neon/src/utils/validators.dart'; import 'package:neon/src/widgets/nextcloud_logo.dart'; -import 'package:provider/provider.dart'; class LoginPage extends StatefulWidget { const LoginPage({ @@ -40,7 +39,6 @@ class _LoginPageState extends State { @override Widget build(final BuildContext context) { final branding = Branding.of(context); - final platform = Provider.of(context, listen: false); return Scaffold( resizeToAvoidBottomInset: true, @@ -101,7 +99,7 @@ class _LoginPageState extends State { onFieldSubmitted: login, ), ), - if (platform.canUseCamera) ...[ + if (NeonPlatform.instance.canUseCamera) ...[ const SizedBox( height: 50, ), diff --git a/packages/neon/neon/lib/src/pages/settings.dart b/packages/neon/neon/lib/src/pages/settings.dart index 35aa37af..92a2fa58 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -86,236 +86,234 @@ class _SettingsPageState extends State { builder: ( final context, final accountsSnapshot, - ) { - final platform = Provider.of(context, listen: false); - return ValueListenableBuilder( - valueListenable: globalOptions.pushNotificationsEnabled, - builder: ( - final context, - final _, - final __, - ) => - SettingsList( - initialCategory: widget.initialCategory?.name, - categories: [ + ) => + ValueListenableBuilder( + valueListenable: globalOptions.pushNotificationsEnabled, + builder: ( + final context, + final _, + final __, + ) => + SettingsList( + initialCategory: widget.initialCategory?.name, + categories: [ + SettingsCategory( + title: Text(AppLocalizations.of(context).settingsApps), + key: ValueKey(SettingsCageories.apps.name), + tiles: [ + for (final appImplementation in appImplementations) ...[ + if (appImplementation.options.options.isNotEmpty) ...[ + CustomSettingsTile( + leading: appImplementation.buildIcon(), + title: Text(appImplementation.name(context)), + onTap: () { + NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); + }, + ), + ], + ], + ], + ), + SettingsCategory( + title: Text(AppLocalizations.of(context).optionsCategoryTheme), + key: ValueKey(SettingsCageories.theme.name), + tiles: [ + DropdownButtonSettingsTile( + option: globalOptions.themeMode, + ), + CheckBoxSettingsTile( + option: globalOptions.themeOLEDAsDark, + ), + CheckBoxSettingsTile( + option: globalOptions.themeKeepOriginalAccentColor, + ), + ], + ), + SettingsCategory( + title: Text(AppLocalizations.of(context).optionsCategoryNavigation), + key: ValueKey(SettingsCageories.navigation.name), + tiles: [ + DropdownButtonSettingsTile( + option: globalOptions.navigationMode, + ), + ], + ), + if (NeonPlatform.instance.canUsePushNotifications) ...[ SettingsCategory( - title: Text(AppLocalizations.of(context).settingsApps), - key: ValueKey(SettingsCageories.apps.name), - tiles: [ - for (final appImplementation in appImplementations) ...[ - if (appImplementation.options.options.isNotEmpty) ...[ - CustomSettingsTile( - leading: appImplementation.buildIcon(), - title: Text(appImplementation.name(context)), - onTap: () { - NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); - }, + title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), + key: ValueKey(SettingsCageories.pushNotifications.name), + tiles: [ + if (!globalOptions.pushNotificationsEnabled.enabled) ...[ + TextSettingsTile( + text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice, + style: TextStyle( + fontWeight: FontWeight.w600, + fontStyle: FontStyle.italic, + color: Theme.of(context).colorScheme.error, ), - ], + ), ], + CheckBoxSettingsTile( + option: globalOptions.pushNotificationsEnabled, + ), + DropdownButtonSettingsTile( + option: globalOptions.pushNotificationsDistributor, + ), ], ), + ], + if (NeonPlatform.instance.canUseWindowManager) ...[ SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategoryTheme), - key: ValueKey(SettingsCageories.theme.name), + title: Text(AppLocalizations.of(context).optionsCategoryStartup), + key: ValueKey(SettingsCageories.startup.name), tiles: [ - DropdownButtonSettingsTile( - option: globalOptions.themeMode, - ), CheckBoxSettingsTile( - option: globalOptions.themeOLEDAsDark, + option: globalOptions.startupMinimized, ), CheckBoxSettingsTile( - option: globalOptions.themeKeepOriginalAccentColor, + option: globalOptions.startupMinimizeInsteadOfExit, ), ], ), + ], + if (NeonPlatform.instance.canUseWindowManager && NeonPlatform.instance.canUseSystemTray) ...[ SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategoryNavigation), - key: ValueKey(SettingsCageories.navigation.name), + title: Text(AppLocalizations.of(context).optionsCategorySystemTray), + key: ValueKey(SettingsCageories.systemTray.name), tiles: [ - DropdownButtonSettingsTile( - option: globalOptions.navigationMode, + CheckBoxSettingsTile( + option: globalOptions.systemTrayEnabled, + ), + CheckBoxSettingsTile( + option: globalOptions.systemTrayHideToTrayWhenMinimized, ), ], ), - if (platform.canUsePushNotifications) ...[ - SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), - key: ValueKey(SettingsCageories.pushNotifications.name), - tiles: [ - if (!globalOptions.pushNotificationsEnabled.enabled) ...[ - TextSettingsTile( - text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice, - style: TextStyle( - fontWeight: FontWeight.w600, - fontStyle: FontStyle.italic, - color: Theme.of(context).colorScheme.error, - ), - ), - ], - CheckBoxSettingsTile( - option: globalOptions.pushNotificationsEnabled, - ), - DropdownButtonSettingsTile( - option: globalOptions.pushNotificationsDistributor, - ), - ], - ), - ], - if (platform.canUseWindowManager) ...[ - SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategoryStartup), - key: ValueKey(SettingsCageories.startup.name), - tiles: [ - CheckBoxSettingsTile( - option: globalOptions.startupMinimized, - ), - CheckBoxSettingsTile( - option: globalOptions.startupMinimizeInsteadOfExit, - ), - ], - ), - ], - if (platform.canUseWindowManager && platform.canUseSystemTray) ...[ - SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategorySystemTray), - key: ValueKey(SettingsCageories.systemTray.name), - tiles: [ - CheckBoxSettingsTile( - option: globalOptions.systemTrayEnabled, - ), - CheckBoxSettingsTile( - option: globalOptions.systemTrayHideToTrayWhenMinimized, - ), - ], - ), - ], - SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategoryAccounts), - key: ValueKey(SettingsCageories.accounts.name), - tiles: [ - if (accountsSnapshot.requireData.length > 1) ...[ - CheckBoxSettingsTile( - option: globalOptions.rememberLastUsedAccount, - ), - DropdownButtonSettingsTile( - option: globalOptions.initialAccount, - ), - ], - for (final account in accountsSnapshot.requireData) ...[ - AccountSettingsTile( - account: account, - onTap: () { - AccountSettingsRoute(accountid: account.id).go(context); - }, - ), - ], - CustomSettingsTile( - title: ElevatedButton.icon( - onPressed: () async => const LoginRoute().push(context), - icon: Icon(MdiIcons.accountPlus), - label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd), - ), + ], + SettingsCategory( + title: Text(AppLocalizations.of(context).optionsCategoryAccounts), + key: ValueKey(SettingsCageories.accounts.name), + tiles: [ + if (accountsSnapshot.requireData.length > 1) ...[ + CheckBoxSettingsTile( + option: globalOptions.rememberLastUsedAccount, + ), + DropdownButtonSettingsTile( + option: globalOptions.initialAccount, ), ], - ), - SettingsCategory( - title: Text(AppLocalizations.of(context).optionsCategoryOther), - key: ValueKey(SettingsCageories.other.name), - tiles: [ - CustomSettingsTile( - leading: Icon( - MdiIcons.scriptText, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(AppLocalizations.of(context).licenses), - onTap: () async { - final branding = Branding.of(context); - showLicensePage( - context: context, - applicationName: branding.name, - applicationIcon: branding.logo, - applicationLegalese: branding.legalese, - applicationVersion: Provider.of(context, listen: false).version, - ); + for (final account in accountsSnapshot.requireData) ...[ + AccountSettingsTile( + account: account, + onTap: () { + AccountSettingsRoute(accountid: account.id).go(context); }, ), - CustomSettingsTile( - leading: Icon( - MdiIcons.export, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(AppLocalizations.of(context).settingsExport), - onTap: () async { - final settingsExportHelper = _buildSettingsExportHelper(context); + ], + CustomSettingsTile( + title: ElevatedButton.icon( + onPressed: () async => const LoginRoute().push(context), + icon: Icon(MdiIcons.accountPlus), + label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd), + ), + ), + ], + ), + SettingsCategory( + title: Text(AppLocalizations.of(context).optionsCategoryOther), + key: ValueKey(SettingsCageories.other.name), + tiles: [ + CustomSettingsTile( + leading: Icon( + MdiIcons.scriptText, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(AppLocalizations.of(context).licenses), + onTap: () async { + final branding = Branding.of(context); + showLicensePage( + context: context, + applicationName: branding.name, + applicationIcon: branding.logo, + applicationLegalese: branding.legalese, + applicationVersion: Provider.of(context, listen: false).version, + ); + }, + ), + CustomSettingsTile( + leading: Icon( + MdiIcons.export, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(AppLocalizations.of(context).settingsExport), + onTap: () async { + final settingsExportHelper = _buildSettingsExportHelper(context); - try { - final fileName = - 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64'; - final data = base64.encode( - utf8.encode( - json.encode( - settingsExportHelper.toJsonExport(), - ), + try { + final fileName = + 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64'; + final data = base64.encode( + utf8.encode( + json.encode( + settingsExportHelper.toJsonExport(), ), - ); - await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List); - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - if (mounted) { - NeonException.showSnackbar(context, e); - } + ), + ); + await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List); + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + if (mounted) { + NeonException.showSnackbar(context, e); } - }, + } + }, + ), + CustomSettingsTile( + leading: Icon( + MdiIcons.import, + color: Theme.of(context).colorScheme.primary, ), - CustomSettingsTile( - leading: Icon( - MdiIcons.import, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(AppLocalizations.of(context).settingsImport), - onTap: () async { - final settingsExportHelper = _buildSettingsExportHelper(context); + title: Text(AppLocalizations.of(context).settingsImport), + onTap: () async { + final settingsExportHelper = _buildSettingsExportHelper(context); - try { - final result = await FilePicker.platform.pickFiles( - withData: true, - ); + try { + final result = await FilePicker.platform.pickFiles( + withData: true, + ); - if (result == null) { - return; - } + if (result == null) { + return; + } - if (!result.files.single.path!.endsWith('.json.base64')) { - if (mounted) { - NeonException.showSnackbar( - context, - AppLocalizations.of(context).settingsImportWrongFileExtension, - ); - } - return; + if (!result.files.single.path!.endsWith('.json.base64')) { + if (mounted) { + NeonException.showSnackbar( + context, + AppLocalizations.of(context).settingsImportWrongFileExtension, + ); } + return; + } - final data = json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!)))); + final data = json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!)))); - await settingsExportHelper.applyFromJson(data as Map); - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - if (mounted) { - NeonException.showSnackbar(context, e); - } + await settingsExportHelper.applyFromJson(data as Map); + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + if (mounted) { + NeonException.showSnackbar(context, e); } - }, - ), - ], - ), - ], - ), - ); - }, + } + }, + ), + ], + ), + ], + ), + ), ); return Scaffold( diff --git a/packages/neon/neon/lib/src/platform/platform.dart b/packages/neon/neon/lib/src/platform/platform.dart index af6a06fb..318c1a3e 100644 --- a/packages/neon/neon/lib/src/platform/platform.dart +++ b/packages/neon/neon/lib/src/platform/platform.dart @@ -5,22 +5,42 @@ import 'package:meta/meta.dart'; import 'package:neon/src/platform/android.dart'; import 'package:neon/src/platform/linux.dart'; -Future getNeonPlatform() async { - final NeonPlatform platform; - if (Platform.isAndroid) { - platform = const AndroidNeonPlatform(); - } else if (Platform.isLinux) { - platform = const LinuxNeonPlatform(); - } else { - throw UnimplementedError('No implementation for platform ${Platform.operatingSystem} found'); +/// Implements platform specific functionality and exposes the availability of certain features. +@immutable +abstract interface class NeonPlatform { + @visibleForTesting + factory NeonPlatform.mocked(final NeonPlatform platform) => _platform = platform; + + static NeonPlatform? _platform; + + /// Infers and configures the platform automatically. + /// + /// Required to be called before accessing [NeonPlatform.instance]. + static Future setup() async { + if (Platform.isAndroid) { + _platform = const AndroidNeonPlatform(); + } else if (Platform.isLinux) { + _platform = const LinuxNeonPlatform(); + } else { + throw UnimplementedError('No implementation for platform ${Platform.operatingSystem} found'); + } + + await _platform!.init(); } - await platform.init.call(); - return platform; -} + /// Gets the current instance of [NeonPlatform]. + /// + /// Make sure [NeonPlatform.setup] has been called before accessing the instance. + static NeonPlatform get instance { + if (_platform == null) { + throw StateError( + 'NeonPlatform has not been set up yet. Please make sure NeonPlatform.setup() has been called before and completed.', + ); + } + + return _platform!; + } -@immutable -abstract interface class NeonPlatform { abstract final bool canUseWebView; abstract final bool canUseQuickActions; diff --git a/packages/neon/neon/lib/src/utils/global_popups.dart b/packages/neon/neon/lib/src/utils/global_popups.dart index 10748f2b..b0d83022 100644 --- a/packages/neon/neon/lib/src/utils/global_popups.dart +++ b/packages/neon/neon/lib/src/utils/global_popups.dart @@ -45,10 +45,9 @@ class GlobalPopups { final globalOptions = Provider.of(context, listen: false); final firstLaunchBloc = Provider.of(context, listen: false); final nextPushBloc = Provider.of(context, listen: false); - final platform = Provider.of(context, listen: false); _subscriptions.addAll([ - if (platform.canUsePushNotifications) ...[ + if (NeonPlatform.instance.canUsePushNotifications) ...[ firstLaunchBloc.onFirstLaunch.listen((final _) { assert(context.mounted, 'Context should be mounted'); if (!globalOptions.pushNotificationsEnabled.enabled) { diff --git a/packages/neon/neon/lib/src/utils/request_manager.dart b/packages/neon/neon/lib/src/utils/request_manager.dart index ea16dad4..bccfd985 100644 --- a/packages/neon/neon/lib/src/utils/request_manager.dart +++ b/packages/neon/neon/lib/src/utils/request_manager.dart @@ -22,8 +22,8 @@ class RequestManager { // ignore: prefer_constructors_over_static_methods static RequestManager get instance => _requestManager ??= RequestManager(); - Future initCache(final NeonPlatform platform) async { - _cache = Cache(platform); + Future initCache() async { + _cache = Cache(); await _cache!.init(); } @@ -187,9 +187,6 @@ class RequestManager { @internal class Cache { - Cache(this._platform); - - final NeonPlatform _platform; Database? _database; Future init() async { @@ -199,7 +196,7 @@ class Cache { _database = await openDatabase( p.join( - await _platform.getApplicationCachePath(), + await NeonPlatform.instance.getApplicationCachePath(), 'cache.db', ), version: 1, diff --git a/packages/neon/neon_files/lib/blocs/files.dart b/packages/neon/neon_files/lib/blocs/files.dart index 182ba82e..fc86e497 100644 --- a/packages/neon/neon_files/lib/blocs/files.dart +++ b/packages/neon/neon_files/lib/blocs/files.dart @@ -28,7 +28,6 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta FilesBloc( this.options, this.account, - this._platform, ) { options.uploadQueueParallelism.addListener(_uploadParalelismListener); options.downloadQueueParallelism.addListener(_downloadParalelismListener); @@ -36,7 +35,6 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta final FilesAppSpecificOptions options; final Account account; - final NeonPlatform _platform; late final browser = getNewFilesBrowserBloc(); final _uploadQueue = Queue(); @@ -86,7 +84,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta () async { final file = File( p.join( - await _platform.getApplicationCachePath(), + await NeonPlatform.instance.getApplicationCachePath(), 'files', etag.replaceAll('"', ''), path.last, @@ -139,7 +137,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta () async { final file = File( p.join( - await _platform.getUserAccessibleAppDataPath(), + await NeonPlatform.instance.getUserAccessibleAppDataPath(), account.humanReadableID, 'files', path.join(Platform.pathSeparator), diff --git a/packages/neon/neon_files/lib/dialogs/choose_create.dart b/packages/neon/neon_files/lib/dialogs/choose_create.dart index 92332c00..c86b3f3e 100644 --- a/packages/neon/neon_files/lib/dialogs/choose_create.dart +++ b/packages/neon/neon_files/lib/dialogs/choose_create.dart @@ -77,7 +77,7 @@ class _FilesChooseCreateDialogState extends State { } }, ), - if (Provider.of(context, listen: false).canUseCamera) ...[ + if (NeonPlatform.instance.canUseCamera) ...[ ListTile( leading: Icon( MdiIcons.cameraPlus, diff --git a/packages/neon/neon_files/lib/neon_files.dart b/packages/neon/neon_files/lib/neon_files.dart index a065ca17..ea3f00e0 100644 --- a/packages/neon/neon_files/lib/neon_files.dart +++ b/packages/neon/neon_files/lib/neon_files.dart @@ -44,7 +44,7 @@ part 'widgets/browser_view.dart'; part 'widgets/file_preview.dart'; class FilesApp extends AppImplementation { - FilesApp(super.sharedPreferences, super.platform); + FilesApp(super.sharedPreferences); @override String id = AppIDs.files; @@ -62,7 +62,6 @@ class FilesApp extends AppImplementation { FilesBloc buildBloc(final Account account) => FilesBloc( options, account, - platform, ); @override diff --git a/packages/neon/neon_news/lib/neon_news.dart b/packages/neon/neon_news/lib/neon_news.dart index 8b69ea1d..47b3bf13 100644 --- a/packages/neon/neon_news/lib/neon_news.dart +++ b/packages/neon/neon_news/lib/neon_news.dart @@ -52,7 +52,7 @@ part 'widgets/folder_view.dart'; part 'widgets/folders_view.dart'; class NewsApp extends AppImplementation { - NewsApp(super.sharedPreferences, super.platform); + NewsApp(super.sharedPreferences); @override String id = AppIDs.news; @@ -64,7 +64,7 @@ class NewsApp extends AppImplementation { List supportedLocales = AppLocalizations.supportedLocales; @override - NewsAppSpecificOptions buildOptions(final AppStorage storage) => NewsAppSpecificOptions(storage, platform); + NewsAppSpecificOptions buildOptions(final AppStorage storage) => NewsAppSpecificOptions(storage); @override NewsBloc buildBloc(final Account account) => NewsBloc( diff --git a/packages/neon/neon_news/lib/options.dart b/packages/neon/neon_news/lib/options.dart index 03cb1fb0..6e5baf18 100644 --- a/packages/neon/neon_news/lib/options.dart +++ b/packages/neon/neon_news/lib/options.dart @@ -1,7 +1,7 @@ part of 'neon_news.dart'; class NewsAppSpecificOptions extends NextcloudAppOptions { - NewsAppSpecificOptions(super.storage, this._platform) { + NewsAppSpecificOptions(super.storage) { super.categories = [ generalCategory, articlesCategory, @@ -23,8 +23,6 @@ class NewsAppSpecificOptions extends NextcloudAppOptions { ]; } - final NeonPlatform _platform; - final generalCategory = OptionsCategory( name: (final context) => AppLocalizations.of(context).general, ); @@ -62,7 +60,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions { defaultValue: ArticleViewType.direct, values: { ArticleViewType.direct: (final context) => AppLocalizations.of(context).optionsArticleViewTypeDirect, - if (_platform.canUseWebView) + if (NeonPlatform.instance.canUseWebView) ArticleViewType.internalBrowser: (final context) => AppLocalizations.of(context).optionsArticleViewTypeInternalBrowser, ArticleViewType.externalBrowser: (final context) => diff --git a/packages/neon/neon_news/lib/widgets/articles_view.dart b/packages/neon/neon_news/lib/widgets/articles_view.dart index 1c63cf79..e9d9abb9 100644 --- a/packages/neon/neon_news/lib/widgets/articles_view.dart +++ b/packages/neon/neon_news/lib/widgets/articles_view.dart @@ -203,7 +203,7 @@ class _NewsArticlesViewState extends State { ); } else if (viewType == ArticleViewType.internalBrowser && article.url != null && - Provider.of(context, listen: false).canUseWebView) { + NeonPlatform.instance.canUseWebView) { await Navigator.of(context).push( MaterialPageRoute( builder: (final context) => NewsArticlePage( diff --git a/packages/neon/neon_notes/lib/neon_notes.dart b/packages/neon/neon_notes/lib/neon_notes.dart index af9ccfcf..7f511280 100644 --- a/packages/neon/neon_notes/lib/neon_notes.dart +++ b/packages/neon/neon_notes/lib/neon_notes.dart @@ -42,7 +42,7 @@ part 'widgets/notes_floating_action_button.dart'; part 'widgets/notes_view.dart'; class NotesApp extends AppImplementation { - NotesApp(super.sharedPreferences, super.platform); + NotesApp(super.sharedPreferences); @override String id = AppIDs.notes; diff --git a/packages/neon/neon_notifications/lib/neon_notifications.dart b/packages/neon/neon_notifications/lib/neon_notifications.dart index 3d55d24d..20e9aee6 100644 --- a/packages/neon/neon_notifications/lib/neon_notifications.dart +++ b/packages/neon/neon_notifications/lib/neon_notifications.dart @@ -22,7 +22,7 @@ part 'pages/main.dart'; class NotificationsApp extends AppImplementation implements NotificationsAppInterface { - NotificationsApp(super.sharedPreferences, super.platform); + NotificationsApp(super.sharedPreferences); @override String id = AppIDs.notifications;