optimized avatar and linkify performance, refined navigation to preserve popups, implemented read marker caching, and added file size limits for saving, minor timetable details changes

This commit is contained in:
2026-05-10 16:40:39 +02:00
parent 1458d8ce49
commit a0bc46f522
12 changed files with 234 additions and 64 deletions
+26 -8
View File
@@ -38,6 +38,9 @@ class App extends StatefulWidget {
class _AppState extends State<App> with WidgetsBindingObserver {
late Timer _updateTimings;
StreamSubscription<dynamic>? _timetableWidgetSync;
StreamSubscription<RemoteMessage>? _onMessageSub;
StreamSubscription<RemoteMessage>? _onMessageOpenedAppSub;
StreamSubscription<String>? _fcmTokenRefreshSub;
// Tracked via the bottom-nav controller's listener so it always reflects the
// user's actual position, even between rapid setting emits where the
// controller hasn't caught up to a scheduled jump yet.
@@ -84,10 +87,12 @@ class _AppState extends State<App> with WidgetsBindingObserver {
// Routes pushed with `withNavBar: false` (chat views, file viewers, …)
// sit on the root navigator above the bottom-nav, so a bare jumpToTab
// would swap the tab behind them and leave the user staring at the
// previous screen. Reset to the tab root first.
// previous screen. Reset to the tab root first — but stop at any open
// popup so a confirmation dialog or bottom sheet that the user hasn't
// dismissed yet doesn't get silently torn down.
final navigator = Navigator.of(context);
if (navigator.canPop()) {
navigator.popUntil((route) => route.isFirst);
navigator.popUntil((route) => route.isFirst || route is PopupRoute);
}
AppRoutes.goToTab(context, Modules.timetable);
}
@@ -98,10 +103,11 @@ class _AppState extends State<App> with WidgetsBindingObserver {
if (share == null) return;
// A second share arriving while a previous share-flow page is still on
// the stack would otherwise leave the old page sitting on top with stale
// (already-cleared) file paths. Reset to the tab root before pushing.
// (already-cleared) file paths. Reset to the tab root before pushing
// but stop at any open popup so dialogs/bottom-sheets remain intact.
final navigator = Navigator.of(context);
if (navigator.canPop()) {
navigator.popUntil((route) => route.isFirst);
navigator.popUntil((route) => route.isFirst || route is PopupRoute);
}
AppRoutes.openShareTarget(context, share);
}
@@ -165,11 +171,13 @@ class _AppState extends State<App> with WidgetsBindingObserver {
if (context.read<SettingsCubit>().val().notificationSettings.enabled) {
void update() => NotifyUpdater.registerToServer();
FirebaseMessaging.instance.onTokenRefresh.listen((_) => update());
_fcmTokenRefreshSub = FirebaseMessaging.instance.onTokenRefresh.listen(
(_) => update(),
);
update();
}
FirebaseMessaging.onMessage.listen((message) {
_onMessageSub = FirebaseMessaging.onMessage.listen((message) {
if (!mounted) return;
NotificationController.onForegroundMessageHandler(message, context);
});
@@ -177,7 +185,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
NotificationController.onBackgroundMessageHandler,
);
FirebaseMessaging.onMessageOpenedApp.listen((message) {
_onMessageOpenedAppSub = FirebaseMessaging.onMessageOpenedApp.listen((
message,
) {
if (!mounted) return;
NotificationController.onAppOpenedByNotification(message, context);
});
@@ -193,6 +203,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
void dispose() {
_updateTimings.cancel();
_timetableWidgetSync?.cancel();
_onMessageSub?.cancel();
_onMessageOpenedAppSub?.cancel();
_fcmTokenRefreshSub?.cancel();
ShareIntentListener.pending.removeListener(_handlePendingShare);
ShareIntentListener.instance.detach();
Main.bottomNavigator.removeListener(_onTabControllerChanged);
@@ -279,7 +292,12 @@ class _AppState extends State<App> with WidgetsBindingObserver {
key: ValueKey(config.items.length),
navBarConfig: config,
navBarDecoration: NavBarDecoration(
border: const Border(top: BorderSide(width: 1, color: Colors.grey)),
border: Border(
top: BorderSide(
width: 1,
color: Theme.of(context).colorScheme.outlineVariant,
),
),
color: Theme.of(context).colorScheme.surface,
),
),