improved performance and battery usage on older devices
This commit is contained in:
+51
-20
@@ -10,6 +10,7 @@ import 'api/marianumconnect/marianumconnect_api.dart';
|
||||
import 'api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||
import 'api/marianumconnect/queries/telemetry_heartbeat/telemetry_heartbeat.dart';
|
||||
import 'main.dart';
|
||||
import 'model/account_data.dart';
|
||||
import 'model/data_cleaner.dart';
|
||||
import 'notification/notification_controller.dart';
|
||||
import 'notification/notification_tasks.dart';
|
||||
@@ -40,7 +41,6 @@ class App extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
late Timer _updateTimings;
|
||||
StreamSubscription<dynamic>? _timetableWidgetSync;
|
||||
StreamSubscription<RemoteMessage>? _onMessageSub;
|
||||
StreamSubscription<RemoteMessage>? _onMessageOpenedAppSub;
|
||||
@@ -74,7 +74,11 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
bloc.setAutoRefreshInterval(
|
||||
talkIsActive ? _chatListActiveInterval : _chatListIdleInterval,
|
||||
);
|
||||
if (talkIsActive && refresh) bloc.refresh();
|
||||
// The poll keeps the list current; switching back to the tab shortly
|
||||
// after a fetch doesn't need another one mid tab transition.
|
||||
if (talkIsActive && refresh) {
|
||||
bloc.refreshIfOlderThan(_chatListActiveInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// Wall-clock throttle rather than Debouncer.throttle: a Timer does not tick
|
||||
@@ -110,6 +114,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
_syncChatListPolling(refresh: false);
|
||||
_handlePendingWidgetNavigation();
|
||||
} else if (mounted) {
|
||||
context.read<SettingsCubit>().flushSilentSave();
|
||||
// Stop polling while backgrounded: a silent refresh failing in the
|
||||
// background would otherwise leave an error that flashes on the next
|
||||
// resume before the foreground refetch replaces it.
|
||||
@@ -179,26 +184,42 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
final settingsCubit = context.read<SettingsCubit>();
|
||||
final capabilitiesCubit = context.read<CapabilitiesCubit>();
|
||||
_timetableWidgetSync?.cancel();
|
||||
// Debounced: a refresh or week swipe emits several times in a row, and
|
||||
// only the settled state is worth mirroring.
|
||||
_timetableWidgetSync = timetable.stream.listen((state) {
|
||||
final data = state.data;
|
||||
if (data is TimetableState && !state.isLoading) {
|
||||
unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
data,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
final epoch = AccountData().sessionEpoch;
|
||||
Debouncer.debounce(
|
||||
'widgetPublish',
|
||||
const Duration(seconds: 1),
|
||||
() => unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
data,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
epoch: epoch,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
// Initial publish in case hydrated storage already has data.
|
||||
// Initial publish in case hydrated storage already has data. The widget
|
||||
// still shows its last snapshot, so this waits until the cold-start
|
||||
// frames are done (a fresh bloc emit in the meantime supersedes it).
|
||||
final initialData = timetable.state.data;
|
||||
if (initialData is TimetableState) {
|
||||
unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
initialData,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
final epoch = AccountData().sessionEpoch;
|
||||
Debouncer.debounce(
|
||||
'widgetPublish',
|
||||
const Duration(seconds: 3),
|
||||
() => unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
initialData,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
epoch: epoch,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -208,10 +229,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
_syncChatListPolling();
|
||||
});
|
||||
|
||||
_updateTimings = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
|
||||
_reportTelemetry();
|
||||
|
||||
// A refreshed FCM token invalidates the existing push subscription — the
|
||||
@@ -250,12 +267,15 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
NotificationController.onAppOpenedByNotification(message, context);
|
||||
});
|
||||
|
||||
DataCleaner.cleanOldCache();
|
||||
// Housekeeping only; kept out of the cold-start window.
|
||||
Future<void>.delayed(
|
||||
const Duration(seconds: 20),
|
||||
DataCleaner.cleanOldCache,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_updateTimings.cancel();
|
||||
_timetableWidgetSync?.cancel();
|
||||
_onMessageSub?.cancel();
|
||||
_onMessageOpenedAppSub?.cancel();
|
||||
@@ -271,7 +291,18 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) => BlocBuilder<SettingsCubit, model.Settings>(
|
||||
) => BlocSelector<SettingsCubit, model.Settings, Object>(
|
||||
// Only the module layout shapes the shell; copied values because the
|
||||
// settings object is mutated in place.
|
||||
selector: (settings) {
|
||||
final m = settings.modulesSettings;
|
||||
return (
|
||||
m.moduleOrder.join(','),
|
||||
m.hiddenModules.join(','),
|
||||
m.autoFillBottomBar,
|
||||
m.fixedBottomBarSlots,
|
||||
);
|
||||
},
|
||||
builder: (context, _) {
|
||||
final bottomBarModules = AppModule.getBottomBarModules(context);
|
||||
final totalTabs = bottomBarModules.length + 1;
|
||||
|
||||
Reference in New Issue
Block a user