import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/models/account.dart'; import 'package:neon/src/neon.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:rx_bloc/rx_bloc.dart'; import 'package:rxdart/rxdart.dart'; part 'apps.rxb.g.dart'; typedef NextcloudApp = CoreNavigationAppsOcsData; abstract class AppsBlocEvents { void refresh(); void setActiveApp(final String? appID); } abstract class AppsBlocStates { BehaviorSubject>> get apps; BehaviorSubject>> get appImplementations; BehaviorSubject get activeAppID; } @RxBloc() class AppsBloc extends $AppsBloc { AppsBloc( this._requestManager, this._accountsBloc, this._account, this._allAppImplementations, ) { _$refreshEvent.listen((final _) => _loadApps); _$setActiveAppEvent.listen((final appId) async { final data = (await _appImplementationsSubject.firstWhere((final result) => result.data != null)).data!; if (data.where((final app) => app.id == appId).isNotEmpty) { if (_activeAppSubject.valueOrNull != appId) { _activeAppSubject.add(appId); } } else { debugPrint('App $appId not found'); } }); _appsSubject.listen((final result) { if (result is ResultLoading) { _appImplementationsSubject.add(Result.loading()); } else if (result is ResultError) { _appImplementationsSubject.add(Result.error((result as ResultError).error)); } else if (result is ResultSuccess) { _appImplementationsSubject.add( Result.success(_filteredAppImplementations((result as ResultSuccess>).data)), ); } else if (result is ResultCached && result.data != null) { _appImplementationsSubject.add( ResultCached(_filteredAppImplementations((result as ResultCached>).data)), ); } final appImplementations = result.data != null ? _filteredAppImplementations(result.data!) : []; if (result.data != null) { if (_accountsBloc.pushNotificationApp != null) { setActiveApp(_accountsBloc.pushNotificationApp); _accountsBloc.pushNotificationApp = null; } else { final options = _accountsBloc.getOptions(_account); unawaited( options.initialApp.stream.first.then((var initialApp) { if (initialApp == null) { if (appImplementations.where((final a) => a.id == 'files').isNotEmpty) { initialApp = 'files'; } else if (appImplementations.isNotEmpty) { // This should never happen, because the files app is always installed and can not be removed, but just in // case this changes at a later point. initialApp = appImplementations[0].id; } } if (!_activeAppSubject.hasValue) { setActiveApp(initialApp); } }), ); } } }); _loadApps(); } final _extraApps = ['notifications']; List _filteredAppImplementations(final List apps) { final appIds = apps.map((final a) => a.id).toList(); return _allAppImplementations.where((final a) => appIds.contains(a.id) || _extraApps.contains(a.id)).toList(); } void _loadApps() { _requestManager .wrapNextcloud, CoreNavigationApps>( _account.client.id, 'apps-apps', () async => _account.client.core.getNavigationApps(), (final response) => response.ocs!.data!, previousData: _appsSubject.valueOrNull?.data, ) .listen(_appsSubject.add); } final RequestManager _requestManager; final AccountsBloc _accountsBloc; final Account _account; final List _allAppImplementations; final _appsSubject = BehaviorSubject>>(); final _appImplementationsSubject = BehaviorSubject>>(); late final _activeAppSubject = BehaviorSubject(); final Map _blocs = {}; T getAppBloc(final AppImplementation appImplementation) { if (_blocs[appImplementation] != null) { return _blocs[appImplementation]! as T; } final bloc = appImplementation.buildBloc(_account.client); _blocs[appImplementation] = bloc; return bloc as T; } @override void dispose() { unawaited(_appsSubject.close()); unawaited(_activeAppSubject.close()); for (final key in _blocs.keys) { _blocs[key]!.dispose(); } super.dispose(); } @override BehaviorSubject>> _mapToAppsState() => _appsSubject; @override BehaviorSubject>>> _mapToAppImplementationsState() => _appImplementationsSubject; @override BehaviorSubject _mapToActiveAppIDState() => _activeAppSubject; }