added support for multiple accounts with an account switcher in settings
This commit is contained in:
+41
-70
@@ -4,11 +4,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter_app_badge/flutter_app_badge.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../api/cache_store.dart';
|
||||
import '../api/marianumcloud/talk/share_files_to_chat.dart';
|
||||
import '../background/widget_background_task.dart';
|
||||
import '../notification/notification_service.dart';
|
||||
@@ -18,20 +16,14 @@ import '../push/push_keypair.dart';
|
||||
import '../push/push_tap_router.dart';
|
||||
import '../routing/app_routes.dart';
|
||||
import '../share_intent/share_intent_listener.dart';
|
||||
import '../state/app/modules/breaker/bloc/breaker_bloc.dart';
|
||||
import '../state/app/modules/capabilities/bloc/capabilities_cubit.dart';
|
||||
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../state/app/modules/chat_list/bloc/chat_list_bloc.dart';
|
||||
import '../state/app/modules/nextcloud_capabilities/bloc/nextcloud_capabilities_cubit.dart';
|
||||
import '../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||
import '../utils/app_paths.dart';
|
||||
import '../utils/downloads/download_manager.dart';
|
||||
import '../utils/file_clipboard.dart';
|
||||
import '../widget_data/widget_sync.dart';
|
||||
|
||||
/// Removes everything the signed-out account left on the device. Every step
|
||||
/// is isolated: one failing step used to skip all later ones and leave the
|
||||
/// previous account's data behind.
|
||||
/// Removes what an account leaves on the device outside its own storage (see
|
||||
/// `AccountStorage` for that). Every step is isolated: one failing step used
|
||||
/// to skip all later ones and leave the previous account's data behind.
|
||||
abstract final class SessionWipe {
|
||||
static Future<void>? _running;
|
||||
|
||||
@@ -39,8 +31,9 @@ abstract final class SessionWipe {
|
||||
/// wipe could clear what the next account just stored (widget job, caches).
|
||||
static Future<void> get done => _running ?? Future.value();
|
||||
|
||||
/// State that must be gone before the next frame (a login screen can be
|
||||
/// reached right away): pending navigation and in-memory singletons.
|
||||
/// State that must be gone before the next frame (a login screen or another
|
||||
/// account can be reached right away): pending navigation and in-memory
|
||||
/// singletons.
|
||||
static void clearImmediate() {
|
||||
_step('share intents', ShareIntentListener.instance.clearAll);
|
||||
_step('share folder cache', resetTalkShareFolderCache);
|
||||
@@ -52,66 +45,17 @@ abstract final class SessionWipe {
|
||||
_step('downloads', DownloadManager.instance.clearAll);
|
||||
}
|
||||
|
||||
static Future<void> run({
|
||||
required TimetableBloc timetableBloc,
|
||||
required ChatListBloc chatListBloc,
|
||||
required ChatBloc chatBloc,
|
||||
required BreakerBloc breakerBloc,
|
||||
required CapabilitiesCubit capabilitiesCubit,
|
||||
required NextcloudCapabilitiesCubit nextcloudCapabilitiesCubit,
|
||||
}) {
|
||||
final wipe = _run(
|
||||
timetableBloc: timetableBloc,
|
||||
chatListBloc: chatListBloc,
|
||||
chatBloc: chatBloc,
|
||||
breakerBloc: breakerBloc,
|
||||
capabilitiesCubit: capabilitiesCubit,
|
||||
nextcloudCapabilitiesCubit: nextcloudCapabilitiesCubit,
|
||||
);
|
||||
_running = wipe;
|
||||
return wipe.whenComplete(() {
|
||||
if (identical(_running, wipe)) _running = null;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _run({
|
||||
required TimetableBloc timetableBloc,
|
||||
required ChatListBloc chatListBloc,
|
||||
required ChatBloc chatBloc,
|
||||
required BreakerBloc breakerBloc,
|
||||
required CapabilitiesCubit capabilitiesCubit,
|
||||
required NextcloudCapabilitiesCubit nextcloudCapabilitiesCubit,
|
||||
}) async {
|
||||
// SettingsCubit is deliberately left alone: its BlocBuilder wraps
|
||||
// MaterialApp, and emitting a fresh state here tore down the freshly
|
||||
// mounted Login tree (blank screen until the next interaction).
|
||||
await Future.wait([
|
||||
_stepAsync('timetable', timetableBloc.reset),
|
||||
_stepAsync('chat list', chatListBloc.reset),
|
||||
_stepAsync('chat', chatBloc.reset),
|
||||
_stepAsync('breakers', breakerBloc.reset),
|
||||
_stepAsync('capabilities', capabilitiesCubit.reset),
|
||||
_stepAsync('nc capabilities', nextcloudCapabilitiesCubit.reset),
|
||||
]);
|
||||
await _stepAsync('shared preferences', () async {
|
||||
await (await SharedPreferences.getInstance()).clear();
|
||||
});
|
||||
await _stepAsync('hydrated storage', HydratedBloc.storage.clear);
|
||||
await _stepAsync('request cache', CacheStore.instance.clear);
|
||||
await _stepAsync('chat background', () async {
|
||||
final image = File(AppPaths.chatBackgroundImage);
|
||||
if (image.existsSync()) await image.delete();
|
||||
});
|
||||
/// Runs whenever the active account stops being active (sign-out or
|
||||
/// switch). The tray and its bookkeeping belong to that account — a tap or
|
||||
/// inline reply there would otherwise act as the next one. A new keypair
|
||||
/// and FCM token make pushes still addressed to the previous registration
|
||||
/// undecryptable and undeliverable (an offline sign-out or switch could not
|
||||
/// unregister it).
|
||||
static Future<void> accountLeft() async {
|
||||
await _stepAsync('download files', () async {
|
||||
final dir = Directory('${(await getTemporaryDirectory()).path}/downloads');
|
||||
if (dir.existsSync()) await dir.delete(recursive: true);
|
||||
});
|
||||
|
||||
// Push: the tray and its bookkeeping belong to the previous account — a
|
||||
// tap or inline reply there would otherwise act as the next one. A new
|
||||
// keypair and FCM token make pushes still addressed to the previous
|
||||
// registration undecryptable and undeliverable (an offline sign-out
|
||||
// could not unregister it).
|
||||
await _stepAsync(
|
||||
'notification tray',
|
||||
NotificationService().flutterLocalNotificationsPlugin.cancelAll,
|
||||
@@ -120,8 +64,35 @@ abstract final class SessionWipe {
|
||||
await _stepAsync('push nid store', NidStore().clear);
|
||||
await _stepAsync('push thread store', ChatThreadStore().clearAll);
|
||||
await _stepAsync('push keypair', const PushKeypair().clear);
|
||||
await _stepAsync('fcm token', FirebaseMessaging.instance.deleteToken);
|
||||
// Bounded: the next account waits for this, also without a network.
|
||||
await _stepAsync(
|
||||
'fcm token',
|
||||
() => FirebaseMessaging.instance.deleteToken().timeout(
|
||||
const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Wipes what outlives accounts, once the last one signed out.
|
||||
static Future<void> deviceLeft() {
|
||||
final wipe = _deviceLeft();
|
||||
_running = wipe;
|
||||
return wipe.whenComplete(() {
|
||||
if (identical(_running, wipe)) _running = null;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _deviceLeft() async {
|
||||
// SettingsCubit is deliberately left alone: its selector wraps
|
||||
// MaterialApp, and emitting a fresh state here tore down the freshly
|
||||
// mounted Login tree (blank screen until the next interaction).
|
||||
await _stepAsync('shared preferences', () async {
|
||||
await (await SharedPreferences.getInstance()).clear();
|
||||
});
|
||||
await _stepAsync('chat background', () async {
|
||||
final image = File(AppPaths.chatBackgroundImage);
|
||||
if (image.existsSync()) await image.delete();
|
||||
});
|
||||
// Stop the periodic widget refresh job so the background isolate doesn't
|
||||
// wake up every 30 minutes only to write `loggedIn=false`. Re-registers
|
||||
// on the next successful login.
|
||||
|
||||
Reference in New Issue
Block a user