Browse Source

Merge pull request #341 from Leptopoda/cleanup/accounts_bloc

Cleanup/accounts bloc
pull/376/head
Nikolas Rimikis 2 years ago committed by GitHub
parent
commit
7918047a66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      packages/neon/neon/lib/neon.dart
  2. 8
      packages/neon/neon/lib/src/app.dart
  3. 115
      packages/neon/neon/lib/src/blocs/accounts.dart
  4. 4
      packages/neon/neon/lib/src/blocs/apps.dart
  5. 23
      packages/neon/neon/lib/src/blocs/blocs.dart
  6. 2
      packages/neon/neon/lib/src/blocs/capabilities.dart
  7. 2
      packages/neon/neon/lib/src/blocs/first_launch.dart
  8. 2
      packages/neon/neon/lib/src/blocs/login.dart
  9. 2
      packages/neon/neon/lib/src/blocs/next_push.dart
  10. 2
      packages/neon/neon/lib/src/blocs/push_notifications.dart
  11. 2
      packages/neon/neon/lib/src/blocs/timer.dart
  12. 2
      packages/neon/neon/lib/src/blocs/user_details.dart
  13. 2
      packages/neon/neon/lib/src/blocs/user_statuses.dart
  14. 4
      packages/neon/neon/lib/src/pages/account_settings.dart
  15. 6
      packages/neon/neon/lib/src/pages/home.dart
  16. 4
      packages/neon/neon/lib/src/pages/settings.dart
  17. 2
      packages/neon/neon/lib/src/widgets/account_tile.dart
  18. 2
      packages/neon/neon/lib/src/widgets/user_avatar.dart
  19. 2
      packages/neon/neon_notifications/lib/pages/main.dart

13
packages/neon/neon/lib/neon.dart

@ -21,6 +21,7 @@ import 'package:http/http.dart';
import 'package:intl/intl_standalone.dart'; import 'package:intl/intl_standalone.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart'; import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/blocs/blocs.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/models/push_notification.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
@ -38,27 +39,17 @@ import 'package:sort_box/sort_box.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:tray_manager/tray_manager.dart' as tray; import 'package:tray_manager/tray_manager.dart' as tray;
import 'package:unifiedpush/unifiedpush.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:xdg_directories/xdg_directories.dart' as xdg; import 'package:xdg_directories/xdg_directories.dart' as xdg;
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
export 'src/blocs/blocs.dart';
export 'src/models/account.dart'; export 'src/models/account.dart';
export 'src/models/push_notification.dart'; export 'src/models/push_notification.dart';
part 'src/app.dart'; part 'src/app.dart';
part 'src/blocs/accounts.dart';
part 'src/blocs/apps.dart';
part 'src/blocs/capabilities.dart';
part 'src/blocs/first_launch.dart';
part 'src/blocs/login.dart';
part 'src/blocs/next_push.dart';
part 'src/blocs/push_notifications.dart';
part 'src/blocs/timer.dart';
part 'src/blocs/user_details.dart';
part 'src/blocs/user_statuses.dart';
part 'src/interfaces/notifications.dart'; part 'src/interfaces/notifications.dart';
part 'src/pages/account_settings.dart'; part 'src/pages/account_settings.dart';
part 'src/pages/home.dart'; part 'src/pages/home.dart';

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

@ -124,7 +124,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
final app = Provider.of<List<AppImplementation>>(context, listen: false).find('notifications'); final app = Provider.of<List<AppImplementation>>(context, listen: false).find('notifications');
if (app != null) { if (app != null) {
await _accountsBloc.getAppsBloc(account).getAppBloc<NotificationsBlocInterface>(app).refresh(); await _accountsBloc.getAppsBlocFor(account).getAppBloc<NotificationsBlocInterface>(app).refresh();
} }
}; };
Global.onPushNotificationClicked = (final pushNotificationWithAccountID) async { Global.onPushNotificationClicked = (final pushNotificationWithAccountID) async {
@ -144,7 +144,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
if (app != null) { if (app != null) {
if (app.id != 'notifications') { if (app.id != 'notifications') {
_accountsBloc _accountsBloc
.getAppsBloc(account) .getAppsBlocFor(account)
.getAppBloc<NotificationsBlocInterface>(app) .getAppBloc<NotificationsBlocInterface>(app)
.deleteNotification(pushNotificationWithAccountID.subject.nid!); .deleteNotification(pushNotificationWithAccountID.subject.nid!);
} }
@ -211,7 +211,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
Future _openAppFromExternal(final Account account, final String id) async { Future _openAppFromExternal(final Account account, final String id) async {
await _accountsBloc.getAppsBloc(account).setActiveApp(id); await _accountsBloc.getAppsBlocFor(account).setActiveApp(id);
_navigatorKey.currentState!.popUntil((final route) => route.settings.name == 'home'); _navigatorKey.currentState!.popUntil((final route) => route.settings.name == 'home');
await _showAndRestoreWindow(); await _showAndRestoreWindow();
} }
@ -268,7 +268,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
return ResultBuilder<Capabilities?>( return ResultBuilder<Capabilities?>(
stream: activeAccountSnapshot.hasData stream: activeAccountSnapshot.hasData
? widget.accountsBloc.getCapabilitiesBloc(activeAccountSnapshot.data!).capabilities ? widget.accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities
: null, : null,
builder: (final context, final capabilitiesSnapshot) { builder: (final context, final capabilitiesSnapshot) {
final nextcloudTheme = capabilitiesSnapshot.data?.capabilities.theming; final nextcloudTheme = capabilitiesSnapshot.data?.capabilities.theming;

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

@ -1,16 +1,40 @@
part of '../../neon.dart'; part of 'blocs.dart';
const _keyAccounts = 'accounts'; const _keyAccounts = 'accounts';
abstract class AccountsBlocEvents { abstract interface class AccountsBlocEvents {
/// Logs in the given [account].
///
/// It will also call [setActiveAccount] when no other accounts are registered in [AccountsBlocStates.accounts].
void addAccount(final Account account); void addAccount(final Account account);
/// Logs out the given [account].
///
/// If [account] is the current [AccountsBlocStates.activeAccount] it will automatically activate the first one in [AccountsBlocStates.accounts].
/// It is not defined whether listeners of [AccountsBlocStates.accounts] or [AccountsBlocStates.activeAccount] are informed first.
void removeAccount(final Account account); void removeAccount(final Account account);
/// Updates the given [account].
///
/// It triggers an event in both [AccountsBlocStates.accounts] and [AccountsBlocStates.activeAccount] to inform all listeners.
void updateAccount(final Account account); void updateAccount(final Account account);
void setActiveAccount(final Account? account);
/// Sets the active [account].
///
/// It triggers an event in [AccountsBlocStates.activeAccount] to inform all listeners.
void setActiveAccount(final Account account);
} }
abstract class AccountsBlocStates { abstract interface class AccountsBlocStates {
/// All registered accounts.
///
/// An empty value represents a state where no account is logged in.
BehaviorSubject<List<Account>> get accounts; BehaviorSubject<List<Account>> get accounts;
/// Currently active account.
///
/// It should be ensured to only emit an event when it's value changes.
/// A null value represents a state where no user is logged in.
BehaviorSubject<Account?> get activeAccount; BehaviorSubject<Account?> get activeAccount;
} }
@ -111,7 +135,11 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
final as = accounts.value; final as = accounts.value;
final aa = activeAccount.valueOrNull; final aa = activeAccount.valueOrNull;
if (aa?.id == account.id) { if (aa?.id == account.id) {
setActiveAccount(as.firstOrNull); if (as.firstOrNull != null) {
setActiveAccount(as.first);
} else {
activeAccount.add(null);
}
} }
unawaited(() async { unawaited(() async {
@ -125,9 +153,8 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
} }
@override @override
void setActiveAccount(final Account? account) { void setActiveAccount(final Account account) {
final as = accounts.value; activeAccount.add(account);
activeAccount.add(account ?? as.firstOrNull);
} }
@override @override
@ -149,26 +176,65 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
setActiveAccount(account); setActiveAccount(account);
} }
AccountSpecificOptions getOptions(final Account account) => _accountsOptions[account.id] ??= AccountSpecificOptions( /// The currently active account.
///
/// Equivalent to activeAccount.value but throws a [StateError] when no user is logged in.
@visibleForTesting
Account get aa {
final aa = activeAccount.value;
if (aa == null) {
throw StateError('No user is logged in.');
}
return aa;
}
/// The options for the [activeAccount].
///
/// Convenience method for [getOptionsFor] with the currently active account.
AccountSpecificOptions get activeOptions => getOptionsFor(aa);
/// The options for the specified [account].
///
/// Use [activeOptions] to get them for the [activeAccount].
AccountSpecificOptions getOptionsFor(final Account account) =>
_accountsOptions[account.id] ??= AccountSpecificOptions(
AppStorage('accounts-${account.id}', _sharedPreferences), AppStorage('accounts-${account.id}', _sharedPreferences),
getAppsBloc(account), getAppsBlocFor(account),
); );
AppsBloc getAppsBloc(final Account account) { /// The appsBloc for the [activeAccount].
///
/// Convenience method for [getAppsBlocFor] with the currently active account.
AppsBloc get activeAppsBloc => getAppsBlocFor(aa);
/// The appsBloc for the specified [account].
///
/// Use [activeAppsBloc] to get them for the [activeAccount].
AppsBloc getAppsBlocFor(final Account account) {
if (_appsBlocs[account.id] != null) { if (_appsBlocs[account.id] != null) {
return _appsBlocs[account.id]!; return _appsBlocs[account.id]!;
} }
return _appsBlocs[account.id] = AppsBloc( return _appsBlocs[account.id] = AppsBloc(
_requestManager, _requestManager,
getCapabilitiesBloc(account), getCapabilitiesBlocFor(account),
this, this,
account, account,
_allAppImplementations, _allAppImplementations,
); );
} }
CapabilitiesBloc getCapabilitiesBloc(final Account account) { /// The capabilitiesBloc for the [activeAccount].
///
/// Convenience method for [getCapabilitiesBlocFor] with the currently active account.
CapabilitiesBloc get activeCapabilitiesBloc => getCapabilitiesBlocFor(aa);
/// The capabilitiesBloc for the specified [account].
///
/// Use [activeCapabilitiesBloc] to get them for the [activeAccount].
CapabilitiesBloc getCapabilitiesBlocFor(final Account account) {
if (_capabilitiesBlocs[account.id] != null) { if (_capabilitiesBlocs[account.id] != null) {
return _capabilitiesBlocs[account.id]!; return _capabilitiesBlocs[account.id]!;
} }
@ -179,7 +245,15 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
); );
} }
UserDetailsBloc getUserDetailsBloc(final Account account) { /// The userDetailsBloc for the [activeAccount].
///
/// Convenience method for [getUserDetailsBlocFor] with the currently active account.
UserDetailsBloc get activeUerDetailsBloc => getUserDetailsBlocFor(aa);
/// The userDetailsBloc for the specified [account].
///
/// Use [activeUerDetailsBloc] to get them for the [activeAccount].
UserDetailsBloc getUserDetailsBlocFor(final Account account) {
if (_userDetailsBlocs[account.id] != null) { if (_userDetailsBlocs[account.id] != null) {
return _userDetailsBlocs[account.id]!; return _userDetailsBlocs[account.id]!;
} }
@ -190,7 +264,15 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
); );
} }
UserStatusesBloc getUserStatusesBloc(final Account account) { /// The userStatusBloc for the [activeAccount].
///
/// Convenience method for [getUserStatusesBlocFor] with the currently active account.
UserStatusesBloc get activeUserStatusesBloc => getUserStatusesBlocFor(aa);
/// The userStatusBloc for the specified [account].
///
/// Use [activeUserStatusesBloc] to get them for the [activeAccount].
UserStatusesBloc getUserStatusesBlocFor(final Account account) {
if (_userStatusesBlocs[account.id] != null) { if (_userStatusesBlocs[account.id] != null) {
return _userStatusesBlocs[account.id]!; return _userStatusesBlocs[account.id]!;
} }
@ -202,6 +284,9 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
} }
} }
/// Get a list of logged in accounts from [storage].
///
/// It is not checked whether the stored information is still valid.
List<Account> loadAccounts(final AppStorage storage) { List<Account> loadAccounts(final AppStorage storage) {
if (storage.containsKey(_keyAccounts)) { if (storage.containsKey(_keyAccounts)) {
return storage return storage

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

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
typedef NextcloudApp = NextcloudCoreNavigationApps_Ocs_Data; typedef NextcloudApp = NextcloudCoreNavigationApps_Ocs_Data;
@ -33,7 +33,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
appImplementations.listen((final result) { appImplementations.listen((final result) {
if (result.data != null) { if (result.data != null) {
final options = _accountsBloc.getOptions(_account); final options = _accountsBloc.getOptionsFor(_account);
unawaited( unawaited(
options.initialApp.stream.first.then((var initialApp) async { options.initialApp.stream.first.then((var initialApp) async {
if (initialApp == null) { if (initialApp == null) {

23
packages/neon/neon/lib/src/blocs/blocs.dart

@ -0,0 +1,23 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:neon/neon.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:unifiedpush/unifiedpush.dart';
import 'package:window_manager/window_manager.dart';
part 'accounts.dart';
part 'apps.dart';
part 'capabilities.dart';
part 'first_launch.dart';
part 'login.dart';
part 'next_push.dart';
part 'push_notifications.dart';
part 'user_details.dart';
part 'timer.dart';
part 'user_statuses.dart';

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

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
typedef Capabilities = NextcloudCoreServerCapabilities_Ocs_Data; typedef Capabilities = NextcloudCoreServerCapabilities_Ocs_Data;
typedef NextcloudTheme = NextcloudCoreServerCapabilities_Ocs_Data_Capabilities_Theming; typedef NextcloudTheme = NextcloudCoreServerCapabilities_Ocs_Data_Capabilities_Theming;

2
packages/neon/neon/lib/src/blocs/first_launch.dart

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class FirstLaunchBlocEvents {} abstract class FirstLaunchBlocEvents {}

2
packages/neon/neon/lib/src/blocs/login.dart

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class LoginBlocEvents { abstract class LoginBlocEvents {
void setServerURL(final String? url); void setServerURL(final String? url);

2
packages/neon/neon/lib/src/blocs/next_push.dart

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class NextPushBlocEvents {} abstract class NextPushBlocEvents {}

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

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class PushNotificationsBlocEvents {} abstract class PushNotificationsBlocEvents {}

2
packages/neon/neon/lib/src/blocs/timer.dart

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class TimerBlocEvents { abstract class TimerBlocEvents {
/// Register a [callback] that will be called periodically. /// Register a [callback] that will be called periodically.

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

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class UserDetailsBlocEvents {} abstract class UserDetailsBlocEvents {}

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

@ -1,4 +1,4 @@
part of '../../neon.dart'; part of 'blocs.dart';
abstract class UserStatusesBlocEvents { abstract class UserStatusesBlocEvents {
void load(final String username, {final bool force = false}); void load(final String username, {final bool force = false});

4
packages/neon/neon/lib/src/pages/account_settings.dart

@ -10,8 +10,8 @@ class AccountSettingsPage extends StatelessWidget {
final AccountsBloc bloc; final AccountsBloc bloc;
final Account account; final Account account;
late final _options = bloc.getOptions(account); late final _options = bloc.getOptionsFor(account);
late final _userDetailsBloc = bloc.getUserDetailsBloc(account); late final _userDetailsBloc = bloc.getUserDetailsBlocFor(account);
late final _name = account.client.humanReadableID; late final _name = account.client.humanReadableID;
@override @override

6
packages/neon/neon/lib/src/pages/home.dart

@ -28,8 +28,8 @@ class _HomePageState extends State<HomePage> {
_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);
_account = _accountsBloc.activeAccount.value!; _account = _accountsBloc.activeAccount.value!;
_appsBloc = _accountsBloc.getAppsBloc(_account); _appsBloc = _accountsBloc.activeAppsBloc;
_capabilitiesBloc = _accountsBloc.getCapabilitiesBloc(_account); _capabilitiesBloc = _accountsBloc.activeCapabilitiesBloc;
_appsBloc.openNotifications.listen((final _) async { _appsBloc.openNotifications.listen((final _) async {
final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull; final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull;
@ -300,7 +300,7 @@ class _HomePageState extends State<HomePage> {
.toList(), .toList(),
onChanged: (final id) { onChanged: (final id) {
if (id != null) { if (id != null) {
_accountsBloc.setActiveAccount(accounts.find(id)); _accountsBloc.setActiveAccount(accounts.find(id)!);
} }
}, },
), ),

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

@ -31,7 +31,7 @@ class _SettingsPageState extends State<SettingsPage> {
} }
for (final account in accountsBloc.accounts.value) { for (final account in accountsBloc.accounts.value) {
await accountsBloc.getOptions(account).reset(); await accountsBloc.getOptionsFor(account).reset();
} }
} }
}, },
@ -52,7 +52,7 @@ class _SettingsPageState extends State<SettingsPage> {
accountSpecificOptions: { accountSpecificOptions: {
if (accountsSnapshot.hasData) ...{ if (accountsSnapshot.hasData) ...{
for (final account in accountsSnapshot.data!) ...{ for (final account in accountsSnapshot.data!) ...{
account: accountsBloc.getOptions(account).options, account: accountsBloc.getOptionsFor(account).options,
}, },
}, },
}, },

2
packages/neon/neon/lib/src/widgets/account_tile.dart

@ -20,7 +20,7 @@ class NeonAccountTile extends StatelessWidget {
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final userDetailsBloc = Provider.of<AccountsBloc>(context, listen: false).getUserDetailsBloc(account); final userDetailsBloc = Provider.of<AccountsBloc>(context, listen: false).getUserDetailsBlocFor(account);
return ListTile( return ListTile(
textColor: textColor, textColor: textColor,

2
packages/neon/neon/lib/src/widgets/user_avatar.dart

@ -24,7 +24,7 @@ class NeonUserAvatar extends StatefulWidget {
} }
class _UserAvatarState extends State<NeonUserAvatar> { class _UserAvatarState extends State<NeonUserAvatar> {
late final _userStatusBloc = Provider.of<AccountsBloc>(context, listen: false).getUserStatusesBloc(widget.account); late final _userStatusBloc = Provider.of<AccountsBloc>(context, listen: false).getUserStatusesBlocFor(widget.account);
late double size; late double size;
@override @override

2
packages/neon/neon_notifications/lib/pages/main.dart

@ -91,7 +91,7 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
} }
if (app != null) { if (app != null) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false); final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
await accountsBloc.getAppsBloc(accountsBloc.activeAccount.value!).setActiveApp(app.id); await accountsBloc.activeAppsBloc.setActiveApp(app.id);
} else { } else {
await showDialog( await showDialog(
context: context, context: context,

Loading…
Cancel
Save