Browse Source

Merge pull request #495 from nextcloud/refactor/bloc-arguments-propagation

Refactor/bloc arguments propagation
pull/610/head
Kate 1 year ago committed by GitHub
parent
commit
c573648c31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      packages/app/lib/apps.dart
  2. 20
      packages/neon/neon/lib/neon.dart
  3. 22
      packages/neon/neon/lib/src/app.dart
  4. 2
      packages/neon/neon/lib/src/bloc/bloc.dart
  5. 76
      packages/neon/neon/lib/src/blocs/accounts.dart
  6. 4
      packages/neon/neon/lib/src/blocs/apps.dart
  7. 4
      packages/neon/neon/lib/src/blocs/capabilities.dart
  8. 4
      packages/neon/neon/lib/src/blocs/push_notifications.dart
  9. 4
      packages/neon/neon/lib/src/blocs/user_details.dart
  10. 12
      packages/neon/neon/lib/src/blocs/user_statuses.dart
  11. 6
      packages/neon/neon/lib/src/models/app_implementation.dart
  12. 6
      packages/neon/neon/lib/src/models/notifications_interface.dart
  13. 4
      packages/neon/neon/lib/src/pages/login.dart
  14. 392
      packages/neon/neon/lib/src/pages/settings.dart
  15. 4
      packages/neon/neon/lib/src/platform/android.dart
  16. 4
      packages/neon/neon/lib/src/platform/linux.dart
  17. 50
      packages/neon/neon/lib/src/platform/platform.dart
  18. 3
      packages/neon/neon/lib/src/utils/global_popups.dart
  19. 6
      packages/neon/neon/lib/src/utils/push_utils.dart
  20. 36
      packages/neon/neon/lib/src/utils/request_manager.dart
  21. 2
      packages/neon/neon/lib/utils.dart
  22. 4
      packages/neon/neon_files/lib/blocs/browser.dart
  23. 10
      packages/neon/neon_files/lib/blocs/files.dart
  24. 2
      packages/neon/neon_files/lib/dialogs/choose_create.dart
  25. 4
      packages/neon/neon_files/lib/neon_files.dart
  26. 5
      packages/neon/neon_news/lib/blocs/articles.dart
  27. 7
      packages/neon/neon_news/lib/blocs/news.dart
  28. 5
      packages/neon/neon_news/lib/neon_news.dart
  29. 6
      packages/neon/neon_news/lib/options.dart
  30. 1
      packages/neon/neon_news/lib/pages/feed.dart
  31. 2
      packages/neon/neon_news/lib/widgets/articles_view.dart
  32. 1
      packages/neon/neon_news/lib/widgets/folder_view.dart
  33. 4
      packages/neon/neon_notes/lib/blocs/notes.dart
  34. 3
      packages/neon/neon_notes/lib/neon_notes.dart
  35. 4
      packages/neon/neon_notifications/lib/blocs/notifications.dart
  36. 3
      packages/neon/neon_notifications/lib/neon_notifications.dart

12
packages/app/lib/apps.dart

@ -1,6 +1,4 @@
import 'package:neon/models.dart'; import 'package:neon/models.dart';
import 'package:neon/platform.dart';
import 'package:neon/utils.dart';
import 'package:neon_files/neon_files.dart'; import 'package:neon_files/neon_files.dart';
import 'package:neon_news/neon_news.dart'; import 'package:neon_news/neon_news.dart';
import 'package:neon_notes/neon_notes.dart'; import 'package:neon_notes/neon_notes.dart';
@ -9,12 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart';
List<AppImplementation> getAppImplementations( List<AppImplementation> getAppImplementations(
final SharedPreferences sharedPreferences, final SharedPreferences sharedPreferences,
final RequestManager requestManager,
final NeonPlatform platform,
) => ) =>
[ [
FilesApp(sharedPreferences, requestManager, platform), FilesApp(sharedPreferences),
NewsApp(sharedPreferences, requestManager, platform), NewsApp(sharedPreferences),
NotesApp(sharedPreferences, requestManager, platform), NotesApp(sharedPreferences),
NotificationsApp(sharedPreferences, requestManager, platform), NotificationsApp(sharedPreferences),
]; ];

20
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/blocs/push_notifications.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.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/theme/neon.dart';
import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/request_manager.dart'; import 'package:neon/src/utils/request_manager.dart';
@ -19,8 +18,7 @@ import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
Future runNeon({ Future runNeon({
required final Iterable<AppImplementation> Function(SharedPreferences, RequestManager, NeonPlatform) required final Iterable<AppImplementation> Function(SharedPreferences) getAppImplementations,
getAppImplementations,
required final NeonTheme theme, required final NeonTheme theme,
@visibleForTesting final WidgetsBinding? bindingOverride, @visibleForTesting final WidgetsBinding? bindingOverride,
@visibleForTesting final Account? account, @visibleForTesting final Account? account,
@ -32,11 +30,8 @@ Future runNeon({
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = await SharedPreferences.getInstance();
final platform = await getNeonPlatform(); await RequestManager.instance.initCache();
final cache = Cache(platform); final allAppImplementations = getAppImplementations(sharedPreferences);
await cache.init();
final requestManager = RequestManager(cache);
final allAppImplementations = getAppImplementations(sharedPreferences, requestManager, platform);
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
buildUserAgent(packageInfo); buildUserAgent(packageInfo);
@ -47,8 +42,6 @@ Future runNeon({
); );
final accountsBloc = AccountsBloc( final accountsBloc = AccountsBloc(
requestManager,
platform,
sharedPreferences, sharedPreferences,
globalOptions, globalOptions,
allAppImplementations, allAppImplementations,
@ -62,7 +55,6 @@ Future runNeon({
accountsBloc, accountsBloc,
sharedPreferences, sharedPreferences,
globalOptions, globalOptions,
platform,
); );
final firstLaunchBloc = FirstLaunchBloc( final firstLaunchBloc = FirstLaunchBloc(
sharedPreferences, sharedPreferences,
@ -80,15 +72,9 @@ Future runNeon({
Provider<SharedPreferences>( Provider<SharedPreferences>(
create: (final _) => sharedPreferences, create: (final _) => sharedPreferences,
), ),
Provider<NeonPlatform>(
create: (final _) => platform,
),
Provider<GlobalOptions>( Provider<GlobalOptions>(
create: (final _) => globalOptions, create: (final _) => globalOptions,
), ),
Provider<RequestManager>(
create: (final _) => requestManager,
),
Provider<AccountsBloc>( Provider<AccountsBloc>(
create: (final _) => accountsBloc, create: (final _) => accountsBloc,
), ),

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

@ -44,7 +44,6 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true); final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true);
final _navigatorKey = GlobalKey<NavigatorState>(); final _navigatorKey = GlobalKey<NavigatorState>();
late final Iterable<AppImplementation> _appImplementations; late final Iterable<AppImplementation> _appImplementations;
late final NeonPlatform _platform;
late final GlobalOptions _globalOptions; late final GlobalOptions _globalOptions;
late final AccountsBloc _accountsBloc; late final AccountsBloc _accountsBloc;
late final _routerDelegate = AppRouter( late final _routerDelegate = AppRouter(
@ -59,15 +58,14 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
super.initState(); super.initState();
_appImplementations = Provider.of<Iterable<AppImplementation>>(context, listen: false); _appImplementations = Provider.of<Iterable<AppImplementation>>(context, listen: false);
_platform = Provider.of<NeonPlatform>(context, listen: false);
_globalOptions = Provider.of<GlobalOptions>(context, listen: false); _globalOptions = Provider.of<GlobalOptions>(context, listen: false);
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false); _accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
if (_platform.canUseSystemTray) { if (NeonPlatform.instance.canUseSystemTray) {
tray.trayManager.addListener(this); tray.trayManager.addListener(this);
} }
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
windowManager.addListener(this); windowManager.addListener(this);
} }
@ -77,7 +75,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
if (!mounted) { if (!mounted) {
return; return;
} }
if (_platform.canUseQuickActions) { if (NeonPlatform.instance.canUseQuickActions) {
const quickActions = QuickActions(); const quickActions = QuickActions();
await quickActions.setShortcutItems( await quickActions.setShortcutItems(
_appImplementations _appImplementations
@ -93,7 +91,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
await quickActions.initialize(_handleShortcut); await quickActions.initialize(_handleShortcut);
} }
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
await windowManager.setPreventClose(true); await windowManager.setPreventClose(true);
if (_globalOptions.startupMinimized.value) { if (_globalOptions.startupMinimized.value) {
@ -101,7 +99,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
} }
if (_platform.canUseSystemTray) { if (NeonPlatform.instance.canUseSystemTray) {
_globalOptions.systemTrayEnabled.addListener(() async { _globalOptions.systemTrayEnabled.addListener(() async {
if (_globalOptions.systemTrayEnabled.value) { if (_globalOptions.systemTrayEnabled.value) {
// TODO: This works on Linux, but maybe not on macOS or Windows // TODO: This works on Linux, but maybe not on macOS or Windows
@ -136,7 +134,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
}); });
} }
if (_platform.canUsePushNotifications) { if (NeonPlatform.instance.canUsePushNotifications) {
final localNotificationsPlugin = await PushUtils.initLocalNotifications(); final localNotificationsPlugin = await PushUtils.initLocalNotifications();
Global.onPushNotificationReceived = (final accountID) async { Global.onPushNotificationReceived = (final accountID) async {
final account = _accountsBloc.accounts.value.tryFind(accountID); final account = _accountsBloc.accounts.value.tryFind(accountID);
@ -213,7 +211,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
Future _handleShortcut(final String shortcutType) async { Future _handleShortcut(final String shortcutType) async {
if (shortcutType == 'show_hide') { if (shortcutType == 'show_hide') {
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
if (await windowManager.isVisible()) { if (await windowManager.isVisible()) {
await _saveAndMinimizeWindow(); await _saveAndMinimizeWindow();
} else { } else {
@ -249,7 +247,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
Future _showAndRestoreWindow() async { Future _showAndRestoreWindow() async {
if (!_platform.canUseWindowManager) { if (!NeonPlatform.instance.canUseWindowManager) {
return; return;
} }
@ -264,10 +262,10 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
if (_platform.canUseSystemTray) { if (NeonPlatform.instance.canUseSystemTray) {
tray.trayManager.removeListener(this); tray.trayManager.removeListener(this);
} }
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
windowManager.removeListener(this); windowManager.removeListener(this);
} }

2
packages/neon/neon/lib/src/bloc/bloc.dart

@ -32,7 +32,7 @@ abstract class InteractiveBloc extends Bloc {
if (disableTimeout) { if (disableTimeout) {
await call(); await call();
} else { } else {
await RequestManager.timeout(call); await RequestManager.instance.timeout(call);
} }
await (refresh ?? this.refresh)(); await (refresh ?? this.refresh)();

76
packages/neon/neon/lib/src/blocs/accounts.dart

@ -10,11 +10,9 @@ import 'package:neon/src/blocs/user_details.dart';
import 'package:neon/src/blocs/user_statuses.dart'; import 'package:neon/src/blocs/user_statuses.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.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/settings/models/storage.dart';
import 'package:neon/src/utils/account_options.dart'; import 'package:neon/src/utils/account_options.dart';
import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/request_manager.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -58,8 +56,6 @@ abstract interface class AccountsBlocStates {
class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocStates { class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocStates {
AccountsBloc( AccountsBloc(
this._requestManager,
this._platform,
this._sharedPreferences, this._sharedPreferences,
this._globalOptions, this._globalOptions,
this._allAppImplementations, this._allAppImplementations,
@ -100,8 +96,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
} }
} }
final RequestManager _requestManager;
final NeonPlatform _platform;
late final AppStorage _storage = AppStorage('accounts', _sharedPreferences); late final AppStorage _storage = AppStorage('accounts', _sharedPreferences);
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;
@ -231,19 +225,12 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
/// The appsBloc for the specified [account]. /// The appsBloc for the specified [account].
/// ///
/// Use [activeAppsBloc] to get them for the [activeAccount]. /// Use [activeAppsBloc] to get them for the [activeAccount].
AppsBloc getAppsBlocFor(final Account account) { AppsBloc getAppsBlocFor(final Account account) => _appsBlocs[account.id] ??= AppsBloc(
if (_appsBlocs[account.id] != null) { getCapabilitiesBlocFor(account),
return _appsBlocs[account.id]!; this,
} account,
_allAppImplementations,
return _appsBlocs[account.id] = AppsBloc( );
_requestManager,
getCapabilitiesBlocFor(account),
this,
account,
_allAppImplementations,
);
}
/// The capabilitiesBloc for the [activeAccount]. /// The capabilitiesBloc for the [activeAccount].
/// ///
@ -253,16 +240,8 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
/// The capabilitiesBloc for the specified [account]. /// The capabilitiesBloc for the specified [account].
/// ///
/// Use [activeCapabilitiesBloc] to get them for the [activeAccount]. /// Use [activeCapabilitiesBloc] to get them for the [activeAccount].
CapabilitiesBloc getCapabilitiesBlocFor(final Account account) { CapabilitiesBloc getCapabilitiesBlocFor(final Account account) =>
if (_capabilitiesBlocs[account.id] != null) { _capabilitiesBlocs[account.id] ??= CapabilitiesBloc(account);
return _capabilitiesBlocs[account.id]!;
}
return _capabilitiesBlocs[account.id] = CapabilitiesBloc(
_requestManager,
account,
);
}
/// The userDetailsBloc for the [activeAccount]. /// The userDetailsBloc for the [activeAccount].
/// ///
@ -272,16 +251,8 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
/// The userDetailsBloc for the specified [account]. /// The userDetailsBloc for the specified [account].
/// ///
/// Use [activeUerDetailsBloc] to get them for the [activeAccount]. /// Use [activeUerDetailsBloc] to get them for the [activeAccount].
UserDetailsBloc getUserDetailsBlocFor(final Account account) { UserDetailsBloc getUserDetailsBlocFor(final Account account) =>
if (_userDetailsBlocs[account.id] != null) { _userDetailsBlocs[account.id] ??= UserDetailsBloc(account);
return _userDetailsBlocs[account.id]!;
}
return _userDetailsBlocs[account.id] = UserDetailsBloc(
_requestManager,
account,
);
}
/// The userStatusBloc for the [activeAccount]. /// The userStatusBloc for the [activeAccount].
/// ///
@ -291,16 +262,8 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
/// The userStatusBloc for the specified [account]. /// The userStatusBloc for the specified [account].
/// ///
/// Use [activeUserStatusesBloc] to get them for the [activeAccount]. /// Use [activeUserStatusesBloc] to get them for the [activeAccount].
UserStatusesBloc getUserStatusesBlocFor(final Account account) { UserStatusesBloc getUserStatusesBlocFor(final Account account) =>
if (_userStatusesBlocs[account.id] != null) { _userStatusesBlocs[account.id] ??= UserStatusesBloc(account);
return _userStatusesBlocs[account.id]!;
}
return _userStatusesBlocs[account.id] = UserStatusesBloc(
_platform,
account,
);
}
/// The UnifiedSearchBloc for the [activeAccount]. /// The UnifiedSearchBloc for the [activeAccount].
/// ///
@ -310,16 +273,11 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
/// The UnifiedSearchBloc for the specified [account]. /// The UnifiedSearchBloc for the specified [account].
/// ///
/// Use [activeUnifiedSearchBloc] to get them for the [activeAccount]. /// Use [activeUnifiedSearchBloc] to get them for the [activeAccount].
UnifiedSearchBloc getUnifiedSearchBlocFor(final Account account) { UnifiedSearchBloc getUnifiedSearchBlocFor(final Account account) =>
if (_unifiedSearchBlocs[account.id] != null) { _unifiedSearchBlocs[account.id] ??= UnifiedSearchBloc(
return _unifiedSearchBlocs[account.id]!; getAppsBlocFor(account),
} account,
);
return _unifiedSearchBlocs[account.id] = UnifiedSearchBloc(
getAppsBlocFor(account),
account,
);
}
} }
/// Get a list of logged in accounts from [storage]. /// Get a list of logged in accounts from [storage].

4
packages/neon/neon/lib/src/blocs/apps.dart

@ -41,7 +41,6 @@ abstract class AppsBlocStates {
@internal @internal
class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates { class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates {
AppsBloc( AppsBloc(
this._requestManager,
this._capabilitiesBloc, this._capabilitiesBloc,
this._accountsBloc, this._accountsBloc,
this._account, this._account,
@ -151,7 +150,6 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
Iterable<AppImplementation> _filteredAppImplementations(final Iterable<String> appIds) => Iterable<AppImplementation> _filteredAppImplementations(final Iterable<String> appIds) =>
_allAppImplementations.where((final a) => appIds.contains(a.id)); _allAppImplementations.where((final a) => appIds.contains(a.id));
final RequestManager _requestManager;
final CapabilitiesBloc _capabilitiesBloc; final CapabilitiesBloc _capabilitiesBloc;
final AccountsBloc _accountsBloc; final AccountsBloc _accountsBloc;
final Account _account; final Account _account;
@ -195,7 +193,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
@override @override
Future refresh() async { Future refresh() async {
await _requestManager await RequestManager.instance
.wrapNextcloud<List<CoreNavigationEntry>, CoreNavigationGetAppsNavigationResponse200ApplicationJson>( .wrapNextcloud<List<CoreNavigationEntry>, CoreNavigationGetAppsNavigationResponse200ApplicationJson>(
_account.id, _account.id,
'apps-apps', 'apps-apps',

4
packages/neon/neon/lib/src/blocs/capabilities.dart

@ -17,13 +17,11 @@ abstract class CapabilitiesBlocStates {
@internal @internal
class CapabilitiesBloc extends InteractiveBloc implements CapabilitiesBlocEvents, CapabilitiesBlocStates { class CapabilitiesBloc extends InteractiveBloc implements CapabilitiesBlocEvents, CapabilitiesBlocStates {
CapabilitiesBloc( CapabilitiesBloc(
this._requestManager,
this._account, this._account,
) { ) {
unawaited(refresh()); unawaited(refresh());
} }
final RequestManager _requestManager;
final Account _account; final Account _account;
@override @override
@ -38,7 +36,7 @@ class CapabilitiesBloc extends InteractiveBloc implements CapabilitiesBlocEvents
@override @override
Future refresh() async { Future refresh() async {
await _requestManager.wrapNextcloud<CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data, await RequestManager.instance.wrapNextcloud<CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data,
CoreOcsGetCapabilitiesResponse200ApplicationJson>( CoreOcsGetCapabilitiesResponse200ApplicationJson>(
_account.id, _account.id,
'capabilities', 'capabilities',

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

@ -28,9 +28,8 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
this._accountsBloc, this._accountsBloc,
this._sharedPreferences, this._sharedPreferences,
this._globalOptions, this._globalOptions,
this._platform,
) { ) {
if (_platform.canUsePushNotifications) { if (NeonPlatform.instance.canUsePushNotifications) {
unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors)); unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors));
_globalOptions.pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener); _globalOptions.pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener);
@ -40,7 +39,6 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
} }
final AccountsBloc _accountsBloc; final AccountsBloc _accountsBloc;
final NeonPlatform _platform;
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
late final _storage = AppStorage(AppIDs.notifications, _sharedPreferences); late final _storage = AppStorage(AppIDs.notifications, _sharedPreferences);
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;

4
packages/neon/neon/lib/src/blocs/user_details.dart

@ -17,13 +17,11 @@ abstract class UserDetailsBlocStates {
@internal @internal
class UserDetailsBloc extends InteractiveBloc implements UserDetailsBlocEvents, UserDetailsBlocStates { class UserDetailsBloc extends InteractiveBloc implements UserDetailsBlocEvents, UserDetailsBlocStates {
UserDetailsBloc( UserDetailsBloc(
this._requestManager,
this._account, this._account,
) { ) {
unawaited(refresh()); unawaited(refresh());
} }
final RequestManager _requestManager;
final Account _account; final Account _account;
@override @override
@ -38,7 +36,7 @@ class UserDetailsBloc extends InteractiveBloc implements UserDetailsBlocEvents,
@override @override
Future refresh() async { Future refresh() async {
await _requestManager await RequestManager.instance
.wrapNextcloud<ProvisioningApiUserDetails, ProvisioningApiUsersGetCurrentUserResponse200ApplicationJson>( .wrapNextcloud<ProvisioningApiUserDetails, ProvisioningApiUsersGetCurrentUserResponse200ApplicationJson>(
_account.id, _account.id,
'user-details', 'user-details',

12
packages/neon/neon/lib/src/blocs/user_statuses.dart

@ -2,11 +2,11 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/platform.dart';
import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/bloc/result.dart'; import 'package:neon/src/bloc/result.dart';
import 'package:neon/src/blocs/timer.dart'; import 'package:neon/src/blocs/timer.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -22,14 +22,12 @@ abstract class UserStatusesBlocStates {
@internal @internal
class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents, UserStatusesBlocStates { class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents, UserStatusesBlocStates {
UserStatusesBloc( UserStatusesBloc(
this._platform,
this._account, this._account,
) { ) {
unawaited(refresh()); unawaited(refresh());
_timer = TimerBloc().registerTimer(const Duration(minutes: 5), refresh); _timer = TimerBloc().registerTimer(const Duration(minutes: 5), refresh);
} }
final NeonPlatform _platform;
final Account _account; final Account _account;
late final NeonTimer _timer; late final NeonTimer _timer;
@ -63,8 +61,12 @@ class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents
UserStatusPublic? data; UserStatusPublic? data;
if (_account.username == username) { if (_account.username == username) {
final isAway = var isAway = false;
_platform.canUseWindowManager && (!(await windowManager.isFocused()) || !(await windowManager.isVisible())); if (NeonPlatform.instance.canUseWindowManager) {
final focused = await windowManager.isFocused();
final visible = await windowManager.isFocused();
isAway = !focused || !visible;
}
try { try {
final response = await _account.client.userStatus.heartbeat.heartbeat( final response = await _account.client.userStatus.heartbeat.heartbeat(
status: isAway ? 'away' : 'online', status: isAway ? 'away' : 'online',

6
packages/neon/neon/lib/src/models/app_implementation.dart

@ -6,10 +6,8 @@ import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.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/nextcloud_app_options.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/utils/request_manager.dart';
import 'package:neon/src/widgets/drawer_destination.dart'; import 'package:neon/src/widgets/drawer_destination.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
@ -18,8 +16,6 @@ import 'package:shared_preferences/shared_preferences.dart';
abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions> { abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions> {
AppImplementation( AppImplementation(
final SharedPreferences sharedPreferences, final SharedPreferences sharedPreferences,
this.requestManager,
this.platform,
) { ) {
final storage = AppStorage('app-$id', sharedPreferences); final storage = AppStorage('app-$id', sharedPreferences);
options = buildOptions(storage); options = buildOptions(storage);
@ -28,8 +24,6 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
String get id; String get id;
LocalizationsDelegate get localizationsDelegate; LocalizationsDelegate get localizationsDelegate;
List<Locale> get supportedLocales; List<Locale> get supportedLocales;
final RequestManager requestManager;
final NeonPlatform platform;
String nameFromLocalization(final AppLocalizations localizations) => localizations.appImplementationName(id); String nameFromLocalization(final AppLocalizations localizations) => localizations.appImplementationName(id);
String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context));

6
packages/neon/neon/lib/src/models/notifications_interface.dart

@ -4,11 +4,7 @@ import 'package:neon/src/settings/models/nextcloud_app_options.dart';
abstract interface class NotificationsAppInterface<T extends NotificationsBlocInterface, abstract interface class NotificationsAppInterface<T extends NotificationsBlocInterface,
R extends NotificationsOptionsInterface> extends AppImplementation<T, R> { R extends NotificationsOptionsInterface> extends AppImplementation<T, R> {
NotificationsAppInterface( NotificationsAppInterface(super.sharedPreferences);
super.sharedPreferences,
super.requestManager,
super.platform,
);
} }
abstract interface class NotificationsBlocInterface extends InteractiveBloc { abstract interface class NotificationsBlocInterface extends InteractiveBloc {

4
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/theme/dialog.dart';
import 'package:neon/src/utils/validators.dart'; import 'package:neon/src/utils/validators.dart';
import 'package:neon/src/widgets/nextcloud_logo.dart'; import 'package:neon/src/widgets/nextcloud_logo.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({ const LoginPage({
@ -40,7 +39,6 @@ class _LoginPageState extends State<LoginPage> {
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final branding = Branding.of(context); final branding = Branding.of(context);
final platform = Provider.of<NeonPlatform>(context, listen: false);
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
@ -101,7 +99,7 @@ class _LoginPageState extends State<LoginPage> {
onFieldSubmitted: login, onFieldSubmitted: login,
), ),
), ),
if (platform.canUseCamera) ...[ if (NeonPlatform.instance.canUseCamera) ...[
const SizedBox( const SizedBox(
height: 50, height: 50,
), ),

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

@ -86,236 +86,234 @@ class _SettingsPageState extends State<SettingsPage> {
builder: ( builder: (
final context, final context,
final accountsSnapshot, final accountsSnapshot,
) { ) =>
final platform = Provider.of<NeonPlatform>(context, listen: false); ValueListenableBuilder<bool>(
return ValueListenableBuilder<bool>( valueListenable: globalOptions.pushNotificationsEnabled,
valueListenable: globalOptions.pushNotificationsEnabled, builder: (
builder: ( final context,
final context, final _,
final _, final __,
final __, ) =>
) => SettingsList(
SettingsList( initialCategory: widget.initialCategory?.name,
initialCategory: widget.initialCategory?.name, categories: [
categories: [ SettingsCategory(
title: Text(AppLocalizations.of(context).settingsApps),
key: ValueKey(SettingsCageories.apps.name),
tiles: <SettingsTile>[
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( SettingsCategory(
title: Text(AppLocalizations.of(context).settingsApps), title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications),
key: ValueKey(SettingsCageories.apps.name), key: ValueKey(SettingsCageories.pushNotifications.name),
tiles: <SettingsTile>[ tiles: [
for (final appImplementation in appImplementations) ...[ if (!globalOptions.pushNotificationsEnabled.enabled) ...[
if (appImplementation.options.options.isNotEmpty) ...[ TextSettingsTile(
CustomSettingsTile( text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice,
leading: appImplementation.buildIcon(), style: TextStyle(
title: Text(appImplementation.name(context)), fontWeight: FontWeight.w600,
onTap: () { fontStyle: FontStyle.italic,
NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); color: Theme.of(context).colorScheme.error,
},
), ),
], ),
], ],
CheckBoxSettingsTile(
option: globalOptions.pushNotificationsEnabled,
),
DropdownButtonSettingsTile(
option: globalOptions.pushNotificationsDistributor,
),
], ],
), ),
],
if (NeonPlatform.instance.canUseWindowManager) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryTheme), title: Text(AppLocalizations.of(context).optionsCategoryStartup),
key: ValueKey(SettingsCageories.theme.name), key: ValueKey(SettingsCageories.startup.name),
tiles: [ tiles: [
DropdownButtonSettingsTile(
option: globalOptions.themeMode,
),
CheckBoxSettingsTile( CheckBoxSettingsTile(
option: globalOptions.themeOLEDAsDark, option: globalOptions.startupMinimized,
), ),
CheckBoxSettingsTile( CheckBoxSettingsTile(
option: globalOptions.themeKeepOriginalAccentColor, option: globalOptions.startupMinimizeInsteadOfExit,
), ),
], ],
), ),
],
if (NeonPlatform.instance.canUseWindowManager && NeonPlatform.instance.canUseSystemTray) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryNavigation), title: Text(AppLocalizations.of(context).optionsCategorySystemTray),
key: ValueKey(SettingsCageories.navigation.name), key: ValueKey(SettingsCageories.systemTray.name),
tiles: [ tiles: [
DropdownButtonSettingsTile( CheckBoxSettingsTile(
option: globalOptions.navigationMode, option: globalOptions.systemTrayEnabled,
),
CheckBoxSettingsTile(
option: globalOptions.systemTrayHideToTrayWhenMinimized,
), ),
], ],
), ),
if (platform.canUsePushNotifications) ...[ ],
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), title: Text(AppLocalizations.of(context).optionsCategoryAccounts),
key: ValueKey(SettingsCageories.pushNotifications.name), key: ValueKey(SettingsCageories.accounts.name),
tiles: [ tiles: [
if (!globalOptions.pushNotificationsEnabled.enabled) ...[ if (accountsSnapshot.requireData.length > 1) ...[
TextSettingsTile( CheckBoxSettingsTile(
text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice, option: globalOptions.rememberLastUsedAccount,
style: TextStyle( ),
fontWeight: FontWeight.w600, DropdownButtonSettingsTile(
fontStyle: FontStyle.italic, option: globalOptions.initialAccount,
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),
),
), ),
], ],
), for (final account in accountsSnapshot.requireData) ...[
SettingsCategory( AccountSettingsTile(
title: Text(AppLocalizations.of(context).optionsCategoryOther), account: account,
key: ValueKey(SettingsCageories.other.name), onTap: () {
tiles: <SettingsTile>[ AccountSettingsRoute(accountid: account.id).go(context);
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<PackageInfo>(context, listen: false).version,
);
}, },
), ),
CustomSettingsTile( ],
leading: Icon( CustomSettingsTile(
MdiIcons.export, title: ElevatedButton.icon(
color: Theme.of(context).colorScheme.primary, onPressed: () async => const LoginRoute().push(context),
), icon: Icon(MdiIcons.accountPlus),
title: Text(AppLocalizations.of(context).settingsExport), label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd),
onTap: () async { ),
final settingsExportHelper = _buildSettingsExportHelper(context); ),
],
),
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryOther),
key: ValueKey(SettingsCageories.other.name),
tiles: <SettingsTile>[
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<PackageInfo>(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 { try {
final fileName = final fileName =
'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64'; 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64';
final data = base64.encode( final data = base64.encode(
utf8.encode( utf8.encode(
json.encode( json.encode(
settingsExportHelper.toJsonExport(), settingsExportHelper.toJsonExport(),
),
), ),
); ),
await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List); );
} catch (e, s) { await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List);
debugPrint(e.toString()); } catch (e, s) {
debugPrint(s.toString()); debugPrint(e.toString());
if (mounted) { debugPrint(s.toString());
NeonException.showSnackbar(context, e); if (mounted) {
} NeonException.showSnackbar(context, e);
} }
}, }
},
),
CustomSettingsTile(
leading: Icon(
MdiIcons.import,
color: Theme.of(context).colorScheme.primary,
), ),
CustomSettingsTile( title: Text(AppLocalizations.of(context).settingsImport),
leading: Icon( onTap: () async {
MdiIcons.import, final settingsExportHelper = _buildSettingsExportHelper(context);
color: Theme.of(context).colorScheme.primary,
),
title: Text(AppLocalizations.of(context).settingsImport),
onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try { try {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
withData: true, withData: true,
); );
if (result == null) { if (result == null) {
return; return;
} }
if (!result.files.single.path!.endsWith('.json.base64')) { if (!result.files.single.path!.endsWith('.json.base64')) {
if (mounted) { if (mounted) {
NeonException.showSnackbar( NeonException.showSnackbar(
context, context,
AppLocalizations.of(context).settingsImportWrongFileExtension, AppLocalizations.of(context).settingsImportWrongFileExtension,
); );
}
return;
} }
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<String, dynamic>); await settingsExportHelper.applyFromJson(data as Map<String, dynamic>);
} catch (e, s) { } catch (e, s) {
debugPrint(e.toString()); debugPrint(e.toString());
debugPrint(s.toString()); debugPrint(s.toString());
if (mounted) { if (mounted) {
NeonException.showSnackbar(context, e); NeonException.showSnackbar(context, e);
}
} }
}, }
), },
], ),
), ],
], ),
), ],
); ),
}, ),
); );
return Scaffold( return Scaffold(

4
packages/neon/neon/lib/src/platform/android.dart

@ -29,13 +29,13 @@ class AndroidNeonPlatform implements NeonPlatform {
bool get canUseWindowManager => false; bool get canUseWindowManager => false;
@override @override
Future<String> getApplicationCachePath() async { Future<String> get applicationCachePath async {
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
return tempDir.absolute.path; return tempDir.absolute.path;
} }
@override @override
Future<String> getUserAccessibleAppDataPath() async { Future<String> get userAccessibleAppDataPath async {
if (!await Permission.storage.request().isGranted) { if (!await Permission.storage.request().isGranted) {
throw MissingPermissionException(Permission.storage); throw MissingPermissionException(Permission.storage);
} }

4
packages/neon/neon/lib/src/platform/linux.dart

@ -30,13 +30,13 @@ class LinuxNeonPlatform implements NeonPlatform {
bool get canUsePushNotifications => false; bool get canUsePushNotifications => false;
@override @override
String getApplicationCachePath() => p.join( String get applicationCachePath => p.join(
xdg.cacheHome.absolute.path, xdg.cacheHome.absolute.path,
'de.provokateurin.neon', 'de.provokateurin.neon',
); );
@override @override
String getUserAccessibleAppDataPath() => p.join(Platform.environment['HOME']!, 'Neon'); String get userAccessibleAppDataPath => p.join(Platform.environment['HOME']!, 'Neon');
@override @override
void init() { void init() {

50
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/android.dart';
import 'package:neon/src/platform/linux.dart'; import 'package:neon/src/platform/linux.dart';
Future<NeonPlatform> getNeonPlatform() async { /// Implements platform specific functionality and exposes the availability of certain features.
final NeonPlatform platform; @immutable
if (Platform.isAndroid) { abstract interface class NeonPlatform {
platform = const AndroidNeonPlatform(); @visibleForTesting
} else if (Platform.isLinux) { factory NeonPlatform.mocked(final NeonPlatform platform) => _platform = platform;
platform = const LinuxNeonPlatform();
} else { static NeonPlatform? _platform;
throw UnimplementedError('No implementation for platform ${Platform.operatingSystem} found');
/// 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(); /// Gets the current instance of [NeonPlatform].
return platform; ///
} /// 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 canUseWebView;
abstract final bool canUseQuickActions; abstract final bool canUseQuickActions;
@ -33,9 +53,9 @@ abstract interface class NeonPlatform {
abstract final bool canUsePushNotifications; abstract final bool canUsePushNotifications;
FutureOr<String> getApplicationCachePath(); FutureOr<String> get applicationCachePath;
FutureOr<String> getUserAccessibleAppDataPath(); FutureOr<String> get userAccessibleAppDataPath;
FutureOr init(); FutureOr init();
} }

3
packages/neon/neon/lib/src/utils/global_popups.dart

@ -45,10 +45,9 @@ class GlobalPopups {
final globalOptions = Provider.of<GlobalOptions>(context, listen: false); final globalOptions = Provider.of<GlobalOptions>(context, listen: false);
final firstLaunchBloc = Provider.of<FirstLaunchBloc>(context, listen: false); final firstLaunchBloc = Provider.of<FirstLaunchBloc>(context, listen: false);
final nextPushBloc = Provider.of<NextPushBloc>(context, listen: false); final nextPushBloc = Provider.of<NextPushBloc>(context, listen: false);
final platform = Provider.of<NeonPlatform>(context, listen: false);
_subscriptions.addAll([ _subscriptions.addAll([
if (platform.canUsePushNotifications) ...[ if (NeonPlatform.instance.canUsePushNotifications) ...[
firstLaunchBloc.onFirstLaunch.listen((final _) { firstLaunchBloc.onFirstLaunch.listen((final _) {
assert(context.mounted, 'Context should be mounted'); assert(context.mounted, 'Context should be mounted');
if (!globalOptions.pushNotificationsEnabled.enabled) { if (!globalOptions.pushNotificationsEnabled.enabled) {

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

@ -13,12 +13,10 @@ import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_ids.dart'; import 'package:neon/src/models/app_ids.dart';
import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/models/push_notification.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/theme/colors.dart'; import 'package:neon/src/theme/colors.dart';
import 'package:neon/src/utils/global.dart'; import 'package:neon/src/utils/global.dart';
import 'package:neon/src/utils/localizations.dart'; import 'package:neon/src/utils/localizations.dart';
import 'package:neon/src/utils/request_manager.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -96,10 +94,6 @@ class PushUtils {
} else { } else {
final localizations = await appLocalizationsFromSystem(); final localizations = await appLocalizationsFromSystem();
final platform = await getNeonPlatform();
final cache = Cache(platform);
await cache.init();
var accounts = <Account>[]; var accounts = <Account>[];
Account? account; Account? account;
NotificationsNotification? notification; NotificationsNotification? notification;

36
packages/neon/neon/lib/src/utils/request_manager.dart

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';
import 'package:neon/src/bloc/result.dart'; import 'package:neon/src/bloc/result.dart';
import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/platform/platform.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
@ -9,13 +10,24 @@ import 'package:rxdart/rxdart.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
@immutable
class RequestManager { class RequestManager {
const RequestManager([ RequestManager();
this.cache,
]);
final Cache? cache; @visibleForTesting
factory RequestManager.mocked(final RequestManager requestManager) => _requestManager = requestManager;
static RequestManager? _requestManager;
/// Gets the current instance of [RequestManager].
// ignore: prefer_constructors_over_static_methods
static RequestManager get instance => _requestManager ??= RequestManager();
Future initCache() async {
_cache = Cache();
await _cache!.init();
}
Cache? _cache;
Future wrapNextcloud<T, R>( Future wrapNextcloud<T, R>(
final String clientID, final String clientID,
@ -100,7 +112,7 @@ class RequestManager {
try { try {
final response = await (disableTimeout ? call() : timeout(call)); final response = await (disableTimeout ? call() : timeout(call));
await cache?.set(key, await compute(serialize, response)); await _cache?.set(key, await compute(serialize, response));
subject.add(Result.success(unwrap(response))); subject.add(Result.success(unwrap(response)));
} catch (e, s) { } catch (e, s) {
debugPrint(e.toString()); debugPrint(e.toString());
@ -146,8 +158,8 @@ class RequestManager {
) async { ) async {
T? cached; T? cached;
try { try {
if (cache != null && await cache!.has(key)) { if (_cache != null && await _cache!.has(key)) {
cached = unwrap(await compute(deserialize, (await cache!.get(key))!)); cached = unwrap(await compute(deserialize, (await _cache!.get(key))!));
} }
} catch (e, s) { } catch (e, s) {
debugPrint(e.toString()); debugPrint(e.toString());
@ -167,16 +179,14 @@ class RequestManager {
return false; return false;
} }
static Future<T> timeout<T>( Future<T> timeout<T>(
final Future<T> Function() call, final Future<T> Function() call,
) => ) =>
call().timeout(const Duration(seconds: 30)); call().timeout(const Duration(seconds: 30));
} }
@internal
class Cache { class Cache {
Cache(this._platform);
final NeonPlatform _platform;
Database? _database; Database? _database;
Future init() async { Future init() async {
@ -186,7 +196,7 @@ class Cache {
_database = await openDatabase( _database = await openDatabase(
p.join( p.join(
await _platform.getApplicationCachePath(), await NeonPlatform.instance.applicationCachePath,
'cache.db', 'cache.db',
), ),
version: 1, version: 1,

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

@ -3,5 +3,5 @@ export 'package:neon/src/utils/confirmation_dialog.dart';
export 'package:neon/src/utils/exceptions.dart'; export 'package:neon/src/utils/exceptions.dart';
export 'package:neon/src/utils/hex_color.dart'; export 'package:neon/src/utils/hex_color.dart';
export 'package:neon/src/utils/rename_dialog.dart'; export 'package:neon/src/utils/rename_dialog.dart';
export 'package:neon/src/utils/request_manager.dart'; export 'package:neon/src/utils/request_manager.dart' hide Cache;
export 'package:neon/src/utils/validators.dart'; export 'package:neon/src/utils/validators.dart';

4
packages/neon/neon_files/lib/blocs/browser.dart

@ -14,14 +14,12 @@ abstract class FilesBrowserBlocStates {
class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents, FilesBrowserBlocStates { class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents, FilesBrowserBlocStates {
FilesBrowserBloc( FilesBrowserBloc(
this._requestManager,
this.options, this.options,
this.account, this.account,
) { ) {
unawaited(refresh()); unawaited(refresh());
} }
final RequestManager _requestManager;
final FilesAppSpecificOptions options; final FilesAppSpecificOptions options;
final Account account; final Account account;
@ -40,7 +38,7 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents
@override @override
Future refresh() async { Future refresh() async {
await _requestManager.wrapWebDav<List<WebDavFile>>( await RequestManager.instance.wrapWebDav<List<WebDavFile>>(
account.id, account.id,
'files-${path.value.join('/')}', 'files-${path.value.join('/')}',
files, files,

10
packages/neon/neon_files/lib/blocs/files.dart

@ -28,8 +28,6 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
FilesBloc( FilesBloc(
this.options, this.options,
this.account, this.account,
this._requestManager,
this._platform,
) { ) {
options.uploadQueueParallelism.addListener(_uploadParalelismListener); options.uploadQueueParallelism.addListener(_uploadParalelismListener);
options.downloadQueueParallelism.addListener(_downloadParalelismListener); options.downloadQueueParallelism.addListener(_downloadParalelismListener);
@ -37,8 +35,6 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
final FilesAppSpecificOptions options; final FilesAppSpecificOptions options;
final Account account; final Account account;
final RequestManager _requestManager;
final NeonPlatform _platform;
late final browser = getNewFilesBrowserBloc(); late final browser = getNewFilesBrowserBloc();
final _uploadQueue = Queue(); final _uploadQueue = Queue();
@ -88,7 +84,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
() async { () async {
final file = File( final file = File(
p.join( p.join(
await _platform.getApplicationCachePath(), await NeonPlatform.instance.applicationCachePath,
'files', 'files',
etag.replaceAll('"', ''), etag.replaceAll('"', ''),
path.last, path.last,
@ -141,7 +137,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
() async { () async {
final file = File( final file = File(
p.join( p.join(
await _platform.getUserAccessibleAppDataPath(), await NeonPlatform.instance.userAccessibleAppDataPath,
account.humanReadableID, account.humanReadableID,
'files', 'files',
path.join(Platform.pathSeparator), path.join(Platform.pathSeparator),
@ -185,7 +181,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
tasks.add(tasks.value..remove(task)); tasks.add(tasks.value..remove(task));
} }
FilesBrowserBloc getNewFilesBrowserBloc() => FilesBrowserBloc(_requestManager, options, account); FilesBrowserBloc getNewFilesBrowserBloc() => FilesBrowserBloc(options, account);
void _downloadParalelismListener() { void _downloadParalelismListener() {
_downloadQueue.parallel = options.downloadQueueParallelism.value; _downloadQueue.parallel = options.downloadQueueParallelism.value;

2
packages/neon/neon_files/lib/dialogs/choose_create.dart

@ -77,7 +77,7 @@ class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> {
} }
}, },
), ),
if (Provider.of<NeonPlatform>(context, listen: false).canUseCamera) ...[ if (NeonPlatform.instance.canUseCamera) ...[
ListTile( ListTile(
leading: Icon( leading: Icon(
MdiIcons.cameraPlus, MdiIcons.cameraPlus,

4
packages/neon/neon_files/lib/neon_files.dart

@ -44,7 +44,7 @@ part 'widgets/browser_view.dart';
part 'widgets/file_preview.dart'; part 'widgets/file_preview.dart';
class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> { class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesApp(super.sharedPreferences, super.requestManager, super.platform); FilesApp(super.sharedPreferences);
@override @override
String id = AppIDs.files; String id = AppIDs.files;
@ -62,8 +62,6 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesBloc buildBloc(final Account account) => FilesBloc( FilesBloc buildBloc(final Account account) => FilesBloc(
options, options,
account, account,
requestManager,
platform,
); );
@override @override

5
packages/neon/neon_news/lib/blocs/articles.dart

@ -33,7 +33,6 @@ class NewsMainArticlesBloc extends NewsArticlesBloc {
NewsMainArticlesBloc( NewsMainArticlesBloc(
super._newsBloc, super._newsBloc,
super.options, super.options,
super.requestManager,
super.account, super.account,
); );
} }
@ -42,7 +41,6 @@ class NewsArticlesBloc extends InteractiveBloc implements NewsArticlesBlocEvents
NewsArticlesBloc( NewsArticlesBloc(
this._newsBloc, this._newsBloc,
this.options, this.options,
this.requestManager,
this.account, { this.account, {
this.id, this.id,
this.listType, this.listType,
@ -59,7 +57,6 @@ class NewsArticlesBloc extends InteractiveBloc implements NewsArticlesBlocEvents
final NewsBloc _newsBloc; final NewsBloc _newsBloc;
final NewsAppSpecificOptions options; final NewsAppSpecificOptions options;
final RequestManager requestManager;
final Account account; final Account account;
final int? id; final int? id;
final ListType? listType; final ListType? listType;
@ -118,7 +115,7 @@ class NewsArticlesBloc extends InteractiveBloc implements NewsArticlesBlocEvents
} }
} }
await requestManager.wrapNextcloud<List<NewsArticle>, NewsListArticles>( await RequestManager.instance.wrapNextcloud<List<NewsArticle>, NewsListArticles>(
account.id, account.id,
'news-articles-${type.index}-$id-$getRead', 'news-articles-${type.index}-$id-$getRead',
articles, articles,

7
packages/neon/neon_news/lib/blocs/news.dart

@ -31,7 +31,6 @@ abstract class NewsBlocStates {
class NewsBloc extends InteractiveBloc implements NewsBlocEvents, NewsBlocStates, NewsMainArticlesBloc { class NewsBloc extends InteractiveBloc implements NewsBlocEvents, NewsBlocStates, NewsMainArticlesBloc {
NewsBloc( NewsBloc(
this.options, this.options,
this.requestManager,
this.account, this.account,
) { ) {
mainArticlesBloc.articles.listen((final result) { mainArticlesBloc.articles.listen((final result) {
@ -50,13 +49,11 @@ class NewsBloc extends InteractiveBloc implements NewsBlocEvents, NewsBlocStates
@override @override
final NewsAppSpecificOptions options; final NewsAppSpecificOptions options;
@override @override
final RequestManager requestManager;
@override @override
final Account account; final Account account;
late final mainArticlesBloc = NewsMainArticlesBloc( late final mainArticlesBloc = NewsMainArticlesBloc(
this, this,
options, options,
requestManager,
account, account,
); );
@ -95,14 +92,14 @@ class NewsBloc extends InteractiveBloc implements NewsBlocEvents, NewsBlocStates
@override @override
Future refresh() async { Future refresh() async {
await Future.wait([ await Future.wait([
requestManager.wrapNextcloud<List<NewsFolder>, NewsListFolders>( RequestManager.instance.wrapNextcloud<List<NewsFolder>, NewsListFolders>(
account.id, account.id,
'news-folders', 'news-folders',
folders, folders,
() async => account.client.news.listFolders(), () async => account.client.news.listFolders(),
(final response) => response.folders.toList(), (final response) => response.folders.toList(),
), ),
requestManager.wrapNextcloud<List<NewsFeed>, NewsListFeeds>( RequestManager.instance.wrapNextcloud<List<NewsFeed>, NewsListFeeds>(
account.id, account.id,
'news-feeds', 'news-feeds',
feeds, feeds,

5
packages/neon/neon_news/lib/neon_news.dart

@ -52,7 +52,7 @@ part 'widgets/folder_view.dart';
part 'widgets/folders_view.dart'; part 'widgets/folders_view.dart';
class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> { class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
NewsApp(super.sharedPreferences, super.requestManager, super.platform); NewsApp(super.sharedPreferences);
@override @override
String id = AppIDs.news; String id = AppIDs.news;
@ -64,12 +64,11 @@ class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
List<Locale> supportedLocales = AppLocalizations.supportedLocales; List<Locale> supportedLocales = AppLocalizations.supportedLocales;
@override @override
NewsAppSpecificOptions buildOptions(final AppStorage storage) => NewsAppSpecificOptions(storage, platform); NewsAppSpecificOptions buildOptions(final AppStorage storage) => NewsAppSpecificOptions(storage);
@override @override
NewsBloc buildBloc(final Account account) => NewsBloc( NewsBloc buildBloc(final Account account) => NewsBloc(
options, options,
requestManager,
account, account,
); );

6
packages/neon/neon_news/lib/options.dart

@ -1,7 +1,7 @@
part of 'neon_news.dart'; part of 'neon_news.dart';
class NewsAppSpecificOptions extends NextcloudAppOptions { class NewsAppSpecificOptions extends NextcloudAppOptions {
NewsAppSpecificOptions(super.storage, this._platform) { NewsAppSpecificOptions(super.storage) {
super.categories = [ super.categories = [
generalCategory, generalCategory,
articlesCategory, articlesCategory,
@ -23,8 +23,6 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
]; ];
} }
final NeonPlatform _platform;
final generalCategory = OptionsCategory( final generalCategory = OptionsCategory(
name: (final context) => AppLocalizations.of(context).general, name: (final context) => AppLocalizations.of(context).general,
); );
@ -62,7 +60,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
defaultValue: ArticleViewType.direct, defaultValue: ArticleViewType.direct,
values: { values: {
ArticleViewType.direct: (final context) => AppLocalizations.of(context).optionsArticleViewTypeDirect, ArticleViewType.direct: (final context) => AppLocalizations.of(context).optionsArticleViewTypeDirect,
if (_platform.canUseWebView) if (NeonPlatform.instance.canUseWebView)
ArticleViewType.internalBrowser: (final context) => ArticleViewType.internalBrowser: (final context) =>
AppLocalizations.of(context).optionsArticleViewTypeInternalBrowser, AppLocalizations.of(context).optionsArticleViewTypeInternalBrowser,
ArticleViewType.externalBrowser: (final context) => ArticleViewType.externalBrowser: (final context) =>

1
packages/neon/neon_news/lib/pages/feed.dart

@ -20,7 +20,6 @@ class NewsFeedPage extends StatelessWidget {
bloc: NewsArticlesBloc( bloc: NewsArticlesBloc(
bloc, bloc,
bloc.options, bloc.options,
bloc.requestManager,
bloc.account, bloc.account,
id: feed.id, id: feed.id,
listType: ListType.feed, listType: ListType.feed,

2
packages/neon/neon_news/lib/widgets/articles_view.dart

@ -203,7 +203,7 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
); );
} else if (viewType == ArticleViewType.internalBrowser && } else if (viewType == ArticleViewType.internalBrowser &&
article.url != null && article.url != null &&
Provider.of<NeonPlatform>(context, listen: false).canUseWebView) { NeonPlatform.instance.canUseWebView) {
await Navigator.of(context).push( await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (final context) => NewsArticlePage( builder: (final context) => NewsArticlePage(

1
packages/neon/neon_news/lib/widgets/folder_view.dart

@ -47,7 +47,6 @@ class _NewsFolderViewState extends State<NewsFolderView> {
bloc: NewsArticlesBloc( bloc: NewsArticlesBloc(
widget.bloc, widget.bloc,
widget.bloc.options, widget.bloc.options,
widget.bloc.requestManager,
widget.bloc.account, widget.bloc.account,
id: widget.folder.id, id: widget.folder.id,
listType: ListType.folder, listType: ListType.folder,

4
packages/neon/neon_notes/lib/blocs/notes.dart

@ -25,14 +25,12 @@ abstract class NotesBlocStates {
class NotesBloc extends InteractiveBloc implements NotesBlocEvents, NotesBlocStates { class NotesBloc extends InteractiveBloc implements NotesBlocEvents, NotesBlocStates {
NotesBloc( NotesBloc(
this.options, this.options,
this.requestManager,
this.account, this.account,
) { ) {
unawaited(refresh()); unawaited(refresh());
} }
final NotesAppSpecificOptions options; final NotesAppSpecificOptions options;
final RequestManager requestManager;
final Account account; final Account account;
@override @override
@ -46,7 +44,7 @@ class NotesBloc extends InteractiveBloc implements NotesBlocEvents, NotesBlocSta
@override @override
Future refresh() async { Future refresh() async {
await requestManager.wrapNextcloud<List<NotesNote>, BuiltList>( await RequestManager.instance.wrapNextcloud<List<NotesNote>, BuiltList>(
account.id, account.id,
'notes-notes', 'notes-notes',
notes, notes,

3
packages/neon/neon_notes/lib/neon_notes.dart

@ -42,7 +42,7 @@ part 'widgets/notes_floating_action_button.dart';
part 'widgets/notes_view.dart'; part 'widgets/notes_view.dart';
class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> { class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
NotesApp(super.sharedPreferences, super.requestManager, super.platform); NotesApp(super.sharedPreferences);
@override @override
String id = AppIDs.notes; String id = AppIDs.notes;
@ -59,7 +59,6 @@ class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
@override @override
NotesBloc buildBloc(final Account account) => NotesBloc( NotesBloc buildBloc(final Account account) => NotesBloc(
options, options,
requestManager,
account, account,
); );

4
packages/neon/neon_notifications/lib/blocs/notifications.dart

@ -16,7 +16,6 @@ class NotificationsBloc extends InteractiveBloc
implements NotificationsBlocInterface, NotificationsBlocEvents, NotificationsBlocStates { implements NotificationsBlocInterface, NotificationsBlocEvents, NotificationsBlocStates {
NotificationsBloc( NotificationsBloc(
this.options, this.options,
this._requestManager,
this._account, this._account,
) { ) {
notifications.listen((final result) { notifications.listen((final result) {
@ -31,7 +30,6 @@ class NotificationsBloc extends InteractiveBloc
@override @override
final NotificationsAppSpecificOptions options; final NotificationsAppSpecificOptions options;
final RequestManager _requestManager;
final Account _account; final Account _account;
late final NeonTimer _timer; late final NeonTimer _timer;
@ -52,7 +50,7 @@ class NotificationsBloc extends InteractiveBloc
@override @override
Future refresh() async { Future refresh() async {
await _requestManager.wrapNextcloud<List<NotificationsNotification>, NotificationsListNotifications>( await RequestManager.instance.wrapNextcloud<List<NotificationsNotification>, NotificationsListNotifications>(
_account.id, _account.id,
'notifications-notifications', 'notifications-notifications',
notifications, notifications,

3
packages/neon/neon_notifications/lib/neon_notifications.dart

@ -22,7 +22,7 @@ part 'pages/main.dart';
class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions> class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions>
implements NotificationsAppInterface<NotificationsBloc, NotificationsAppSpecificOptions> { implements NotificationsAppInterface<NotificationsBloc, NotificationsAppSpecificOptions> {
NotificationsApp(super.sharedPreferences, super.requestManager, super.platform); NotificationsApp(super.sharedPreferences);
@override @override
String id = AppIDs.notifications; String id = AppIDs.notifications;
@ -39,7 +39,6 @@ class NotificationsApp extends AppImplementation<NotificationsBloc, Notification
@override @override
NotificationsBloc buildBloc(final Account account) => NotificationsBloc( NotificationsBloc buildBloc(final Account account) => NotificationsBloc(
options, options,
requestManager,
account, account,
); );

Loading…
Cancel
Save