A framework for building convergent cross-platform Nextcloud clients using Flutter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
6.3 KiB

import 'dart:async';
2 years ago
import 'dart:convert';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/blocs/user_details.dart';
import 'package:neon/src/blocs/user_status.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/neon.dart';
import 'package:package_info_plus/package_info_plus.dart';
2 years ago
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'accounts.rxb.g.dart';
abstract class AccountsBlocEvents {
void addAccount(final Account account);
void removeAccount(final Account account);
void updateAccount(final Account account);
void setActiveAccount(final Account? account);
}
abstract class AccountsBlocStates {
BehaviorSubject<List<Account>> get accounts;
BehaviorSubject<Account?> get activeAccount;
}
@RxBloc()
class AccountsBloc extends $AccountsBloc {
AccountsBloc(
this._requestManager,
this._storage,
this._sharedPreferences,
this._globalOptions,
this._packageInfo,
this._allAppImplementations,
2 years ago
) {
_accountsSubject.listen((final accounts) async {
_globalOptions.updateAccounts(accounts);
await _storage.setStringList(_keyAccounts, accounts.map((final a) => json.encode(a.toJson())).toList());
});
_$setActiveAccountEvent.listen((final account) async {
if (account != null) {
if (_activeAccountSubject.value != account) {
await _storage.setString(_keyLastUsedAccount, account.id);
_activeAccountSubject.add(account);
}
2 years ago
} else {
final accounts = _accountsSubject.value;
if (accounts.isNotEmpty) {
setActiveAccount(accounts[0]);
} else {
await _storage.remove(_keyLastUsedAccount);
_activeAccountSubject.add(null);
}
}
});
_$addAccountEvent.listen((final account) async {
account.setupClient(_packageInfo);
2 years ago
if (_activeAccountSubject.valueOrNull == null) {
setActiveAccount(account);
}
final accounts = _accountsSubject.value;
_accountsSubject.add(accounts..add(account));
});
_$removeAccountEvent.listen((final account) async {
final accounts = _accountsSubject.value..removeWhere((final a) => a.id == account.id);
_accountsSubject.add(accounts);
final activeAccount = _activeAccountSubject.valueOrNull;
if (activeAccount != null && activeAccount.id == account.id) {
setActiveAccount(accounts.isNotEmpty ? accounts[0] : null);
}
});
_$updateAccountEvent.listen((final account) async {
account.setupClient(_packageInfo);
2 years ago
final accounts = _accountsSubject.value;
final index = accounts.indexWhere((final a) => a.id == account.id);
if (index == -1) {
// TODO: Figure out how we can remove the old account without potentially race conditioning
accounts.add(account);
} else {
accounts.replaceRange(
index,
index + 1,
[account],
);
}
_accountsSubject.add(accounts);
setActiveAccount(account);
});
if (_storage.containsKey(_keyAccounts)) {
_accountsSubject.add(
_storage
.getStringList(_keyAccounts)!
.map((final a) => (Account.fromJson(json.decode(a) as Map<String, dynamic>))..setupClient(_packageInfo))
2 years ago
.toList(),
);
}
final accounts = _accountsSubject.value;
if (_globalOptions.rememberLastUsedAccount.value && _storage.containsKey(_keyLastUsedAccount)) {
final lastUsedAccountID = _storage.getString(_keyLastUsedAccount);
_activeAccountSubject.add(accounts.singleWhere((final account) => account.id == lastUsedAccountID));
} else {
unawaited(
_globalOptions.initialAccount.stream.first.then((final lastAccount) {
final matches = accounts.where((final account) => account.id == lastAccount).toList();
if (matches.isNotEmpty) {
_activeAccountSubject.add(matches[0]);
}
}),
);
2 years ago
}
}
AccountSpecificOptions getOptions(final Account account) => _accountsOptions[account.id] ??= AccountSpecificOptions(
Storage('accounts-${account.id}', _sharedPreferences),
getAppsBloc(account),
);
2 years ago
AppsBloc getAppsBloc(final Account account) {
if (_accountsAppsBlocs[account.id] != null) {
return _accountsAppsBlocs[account.id]!;
2 years ago
}
return _accountsAppsBlocs[account.id] = AppsBloc(
_requestManager,
this,
account,
_allAppImplementations,
);
2 years ago
}
UserDetailsBloc getUserDetailsBloc(final Account account) {
if (_userDetailsBlocs[account.id] != null) {
return _userDetailsBlocs[account.id]!;
2 years ago
}
return _userDetailsBlocs[account.id] = UserDetailsBloc(
_requestManager,
account.client,
);
2 years ago
}
UserStatusBloc getUserStatusBloc(final Account account) {
if (_userStatusBlocs[account.id] != null) {
return _userStatusBlocs[account.id]!;
2 years ago
}
return _userStatusBlocs[account.id] = UserStatusBloc(
_requestManager,
account,
_activeAccountSubject,
);
2 years ago
}
final RequestManager _requestManager;
final Storage _storage;
final SharedPreferences _sharedPreferences;
final GlobalOptions _globalOptions;
final List<AppImplementation> _allAppImplementations;
final PackageInfo _packageInfo;
final _keyAccounts = 'accounts';
final _keyLastUsedAccount = 'last-used-account';
final _accountsOptions = <String, AccountSpecificOptions>{};
late final _activeAccountSubject = BehaviorSubject<Account?>.seeded(null);
late final _accountsSubject = BehaviorSubject<List<Account>>.seeded([]);
final _accountsAppsBlocs = <String, AppsBloc>{};
final _userDetailsBlocs = <String, UserDetailsBloc>{};
final _userStatusBlocs = <String, UserStatusBloc>{};
2 years ago
@override
void dispose() {
unawaited(_activeAccountSubject.close());
unawaited(_accountsSubject.close());
2 years ago
for (final bloc in _userDetailsBlocs.values) {
bloc.dispose();
}
for (final bloc in _userStatusBlocs.values) {
bloc.dispose();
}
for (final options in _accountsOptions.values) {
options.dispose();
}
super.dispose();
}
@override
BehaviorSubject<List<Account>> _mapToAccountsState() => _accountsSubject;
@override
BehaviorSubject<Account?> _mapToActiveAccountState() => _activeAccountSubject;
}