improved performance and battery usage on older devices

This commit is contained in:
2026-09-25 23:58:08 +02:00
parent ed8e52f475
commit 56a497985b
70 changed files with 1631 additions and 621 deletions
+67 -6
View File
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
@@ -20,22 +21,65 @@ class WidgetPublisher {
static DateTime widgetNow() =>
kDebugMode ? DateTime.now().add(debugTimeShift) : DateTime.now();
/// Identical snapshots are still re-written after this long, so the stored
/// `fetchedAt` (freshness for the background refresh) doesn't go stale.
static const Duration _republishAfter = Duration(minutes: 10);
static Future<void> _queue = Future.value();
static (bool, String, bool)? _lastFlags;
static String? _lastSignature;
static DateTime? _lastPublishedAt;
/// Forgets what was written last, so the next publish writes again. Called
/// whenever the stored widget data is cleared (sign-out).
static void resetDedupe() {
_lastSignature = null;
_lastPublishedAt = null;
}
/// Publishes run one at a time: they are fire-and-forget from the bloc
/// stream, and interleaved writes could leave day and week data from
/// different states behind.
/// [epoch] is the session the [state] belongs to; defaults to the current
/// one. Callers that delay the publish must capture it up front.
static Future<void> publishFromBlocState(
TimetableState state, {
Settings? settings,
bool isTeacher = false,
int? epoch,
}) {
final sessionEpoch = epoch ?? AccountData().sessionEpoch;
return _queue = _queue.then(
(_) => _publish(
state,
settings: settings,
isTeacher: isTeacher,
epoch: sessionEpoch,
),
);
}
static Future<void> _publish(
TimetableState state, {
required Settings? settings,
required bool isTeacher,
required int epoch,
}) async {
final epoch = AccountData().sessionEpoch;
if (!AccountData().isCurrentSession(epoch)) return;
try {
final connectDouble =
settings?.timetableSettings.connectDoubleLessons ?? true;
// Mirror into widget storage so the background isolate sees the same
// values the user just toggled — concurrently, they are independent.
await Future.wait([
WidgetSync.setConnectDoubleLessons(connectDouble),
WidgetSync.setThemeMode(_themeName(settings?.appTheme)),
WidgetSync.setIsTeacher(isTeacher),
]);
final flags = (connectDouble, _themeName(settings?.appTheme), isTeacher);
if (flags != _lastFlags) {
await Future.wait([
WidgetSync.setConnectDoubleLessons(flags.$1),
WidgetSync.setThemeMode(flags.$2),
WidgetSync.setIsTeacher(flags.$3),
]);
_lastFlags = flags;
}
final lessons = state.getAllKnownLessons();
final now = widgetNow();
final dayData = WidgetDataMapper.buildDayData(
@@ -63,6 +107,20 @@ class WidgetPublisher {
// A publish still running at sign-out would put the previous account's
// plan back onto the just cleared widget.
if (!AccountData().isCurrentSession(epoch)) return;
// Most bloc emits (week swipes, prefetches) don't touch the widget's
// window; skip the SharedPreferences commits and widget re-render then.
final signature = jsonEncode([
_withoutFetchedAt(dayData.toJson()),
_withoutFetchedAt(weekData.toJson()),
]);
final lastAt = _lastPublishedAt;
if (signature == _lastSignature &&
lastAt != null &&
now.difference(lastAt) < _republishAfter) {
return;
}
_lastSignature = signature;
_lastPublishedAt = now;
await WidgetSync.writeDayData(dayData);
await WidgetSync.writeWeekData(weekData);
await WidgetSync.setLoggedIn(true);
@@ -74,6 +132,9 @@ class WidgetPublisher {
}
}
static Map<String, Object?> _withoutFetchedAt(Map<String, Object?> json) =>
Map.of(json)..remove('fetchedAt');
static String _themeName(ThemeMode? mode) {
switch (mode) {
case ThemeMode.light: