fixed stale state after logout and replayed or lost share intents

This commit is contained in:
2026-09-24 21:43:21 +02:00
parent e4e2b1a4fb
commit 498d195138
32 changed files with 743 additions and 188 deletions
+11 -50
View File
@@ -15,7 +15,6 @@ import 'package:jiffy/jiffy.dart';
import 'package:loader_overlay/loader_overlay.dart';
import 'package:path_provider/path_provider.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'api/marianumcloud/webdav/queries/list_files/list_files_cache.dart';
import 'api/marianumconnect/auth/session_validator.dart';
@@ -27,6 +26,7 @@ import 'app.dart';
import 'background/widget_background_task.dart';
import 'firebase_options.dart';
import 'model/account_data.dart';
import 'model/session_wipe.dart';
import 'notification/notification_service.dart';
import 'push/push_message_handler.dart';
import 'push/push_registration.dart';
@@ -54,7 +54,6 @@ import 'view/login/login.dart';
import 'view/login/post_login_splash.dart';
import 'widget/avatar_disk_cache.dart';
import 'widget/breaker/breaker.dart';
import 'widget/debug/cache_view.dart';
import 'widget/downloads/download_tray.dart';
import 'widget/emergency/emergency_notice_gate.dart';
import 'widget_data/widget_sync.dart';
@@ -117,6 +116,7 @@ void _installErrorHandlers() {
Future<void> main() async {
log('MarianumMobile started');
WidgetsFlutterBinding.ensureInitialized();
AccountData().markUiEngine();
// Before any initialisation so startup failures reach the backend too.
_installErrorHandlers();
@@ -272,11 +272,13 @@ class Main extends StatefulWidget {
class _MainState extends State<Main> {
bool _showPostLoginSplash = false;
bool _appMounted = true;
late AccountStatus _lastStatus;
@override
void initState() {
super.initState();
Jiffy.setLocale('de');
_lastStatus = context.read<AccountBloc>().state.status;
AccountData().waitForPopulation().then((value) {
if (!mounted) return;
@@ -409,6 +411,8 @@ class _MainState extends State<Main> {
listenWhen: (previous, current) =>
previous.status != current.status,
listener: (context, accountState) {
final wasLoggedIn = _lastStatus == AccountStatus.loggedIn;
_lastStatus = accountState.status;
// Fresh login (loggedOut -> loggedIn): pull capability flags
// for the newly authenticated user, then register push right
// away instead of deferring it to the next app start.
@@ -434,9 +438,10 @@ class _MainState extends State<Main> {
}
if (accountState.status != AccountStatus.loggedOut) return;
// A pending share would otherwise survive logout and be
// re-applied after re-login with file paths the OS may
// already have evicted from the cache.
ShareIntentListener.instance.clear();
// re-applied to the next account. A cold-start share that
// is merely waiting for the stored session to resolve as
// signed out stays, to be sent after login.
if (wasLoggedIn) SessionWipe.clearImmediate();
// Routes pushed via AppRoutes (e.g. Settings) live on the
// root navigator and survive the home swap below, so they
// would still cover the Login screen after logout. Pop
@@ -448,7 +453,6 @@ class _MainState extends State<Main> {
// Capture bloc references before the post-frame callback
// — by the time it runs the dialog/Settings context is
// gone but this listener context is still valid.
final settingsCubit = context.read<SettingsCubit>();
final timetableBloc = context.read<TimetableBloc>();
final chatListBloc = context.read<ChatListBloc>();
final chatBloc = context.read<ChatBloc>();
@@ -462,8 +466,7 @@ class _MainState extends State<Main> {
// still in front caused a black-frame race.
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(
_wipeUserState(
settingsCubit: settingsCubit,
SessionWipe.run(
timetableBloc: timetableBloc,
chatListBloc: chatListBloc,
chatBloc: chatBloc,
@@ -507,45 +510,3 @@ class _MainState extends State<Main> {
),
);
}
Future<void> _wipeUserState({
required SettingsCubit settingsCubit,
required TimetableBloc timetableBloc,
required ChatListBloc chatListBloc,
required ChatBloc chatBloc,
required BreakerBloc breakerBloc,
required CapabilitiesCubit capabilitiesCubit,
required NextcloudCapabilitiesCubit nextcloudCapabilitiesCubit,
}) async {
try {
// Reset user-data blocs whose tree is no longer mounted after the
// home swap. We do NOT touch SettingsCubit here — its outer BlocBuilder
// wraps MaterialApp, so emit'ing a fresh state would tear down the
// freshly-mounted Login tree and leave the user with a blank screen
// (the MaterialApp.builder backdrop) until the next interaction.
await Future.wait([
timetableBloc.reset(),
chatListBloc.reset(),
chatBloc.reset(),
breakerBloc.reset(),
capabilitiesCubit.reset(),
nextcloudCapabilitiesCubit.reset(),
]);
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
await HydratedBloc.storage.clear();
await const CacheView().clear();
// The chat background image lives outside HydratedStorage, so clear it too
// (best-effort) to avoid orphaning the previous user's wallpaper.
final backgroundImage = File(AppPaths.chatBackgroundImage);
if (backgroundImage.existsSync()) backgroundImage.deleteSync();
// 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.
await WidgetBackgroundTask.cancelAll();
await WidgetSync.clear();
await WidgetSync.triggerUpdate();
} catch (e, s) {
log('User state wipe failed: $e', stackTrace: s);
}
}