added support for multiple accounts with an account switcher in settings

This commit is contained in:
2026-09-27 14:54:10 +02:00
parent 5e65c4671a
commit 27563a6dc1
33 changed files with 1801 additions and 462 deletions
+78
View File
@@ -0,0 +1,78 @@
import 'dart:developer';
import 'dart:io';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import '../api/cache_store.dart';
import '../session/account_codec.dart';
import 'hydrated_storage_bootstrap.dart';
/// Per-account local data: every account gets its own HydratedBloc box and
/// request cache directory (see [CacheStore]), so switching back is instant
/// and offline. Only the app settings live in the shared [global] box.
abstract final class AccountStorage {
static const _settingsKey = 'SettingsCubit';
static late String _baseDir;
static final Map<String, Storage> _open = {};
static Storage _global = InMemoryStorage();
static Storage get global => _global;
/// Opens the global box. Settings of installs from before multi-account
/// support are copied over from the legacy box once.
static Future<void> init(String baseDir) async {
_baseDir = baseDir;
_global = await buildHydratedStorageWithFallback(
_ensureDir('$baseDir/hydrated_global'),
);
HydratedBloc.storage = InMemoryStorage();
if (_global.read(_settingsKey) != null) return;
final legacySettings = (await _storageFor('')).read(_settingsKey);
if (legacySettings != null) {
await _global.write(_settingsKey, legacySettings);
}
}
/// Points HydratedBloc at [account]'s data; the request cache follows the
/// active account on its own. Blocs keep the storage they were created
/// with, so the per-account blocs have to be recreated afterwards.
static Future<void> activate(AccountEntry? account) async {
HydratedBloc.storage = account == null
? InMemoryStorage()
: await _storageFor(account.namespace);
}
/// Removes all local data of the account with [namespace].
static Future<void> delete(String namespace) async {
try {
final storage = await _storageFor(namespace);
await storage.clear();
// Closed boxes ignore writes, so blocs of the account that are still
// mounted until the remount cannot leave data behind.
await storage.close();
_open.remove(namespace);
if (namespace.isNotEmpty) {
final dir = Directory(_dirFor(namespace));
if (dir.existsSync()) await dir.delete(recursive: true);
}
} on Object catch (e, s) {
log('Account storage delete failed: $e', stackTrace: s);
}
await CacheStore.instance.deleteNamespace(namespace);
}
static Future<Storage> _storageFor(String namespace) async =>
_open[namespace] ??= await buildHydratedStorageWithFallback(
_ensureDir(_dirFor(namespace)),
);
// The legacy account keeps the box at the root of the base directory.
static String _dirFor(String namespace) =>
namespace.isEmpty ? _baseDir : '$_baseDir/accounts/$namespace';
static String _ensureDir(String path) {
Directory(path).createSync(recursive: true);
return path;
}
}