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
@@ -7,6 +7,7 @@ import '../../../api/marianumconnect/queries/timetable_get_element_week/timetabl
import '../../../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms.dart';
import '../../../api/marianumconnect/queries/timetable_get_students/timetable_get_students.dart';
import '../../../api/marianumconnect/queries/timetable_get_teachers/timetable_get_teachers.dart';
import '../../../model/account_data.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../storage/timetable_favorites_settings.dart';
import '../../../utils/haptics.dart';
@@ -31,7 +32,14 @@ class _ElementPickerPageState extends State<ElementPickerPage> {
// One in-flight/resolved future per type so switching tabs (or rebuilds)
// never re-fetches a list that's already loaded.
final Map<TimetableElementType, Future<List<_PickerItem>>> _futures = {};
// Session-wide (per account): the student list alone has 1000+ entries and
// used to be downloaded again every time the picker opened.
static final Map<
TimetableElementType,
({int epoch, DateTime at, Future<List<_PickerItem>> items})
>
_futures = {};
static const Duration _listMaxAge = Duration(minutes: 15);
// Memoised combined future for the "Alle" tab; rebuilt on retry.
Future<List<_PickerItem>>? _allFuture;
@@ -47,8 +55,25 @@ class _ElementPickerPageState extends State<ElementPickerPage> {
return _allFuture ??= _loadAll();
}
Future<List<_PickerItem>> _loadFor(TimetableElementType type) =>
_futures.putIfAbsent(type, () => _fetch(type));
Future<List<_PickerItem>> _loadFor(TimetableElementType type) {
final epoch = AccountData().sessionEpoch;
final cached = _futures[type];
if (cached != null &&
cached.epoch == epoch &&
DateTime.now().difference(cached.at) < _listMaxAge) {
return cached.items;
}
final items = _fetch(type);
_futures[type] = (epoch: epoch, at: DateTime.now(), items: items);
// A failed load must not stick for the rest of the session.
items.then(
(_) {},
onError: (Object _) {
if (identical(_futures[type]?.items, items)) _futures.remove(type);
},
);
return items;
}
Future<List<_PickerItem>> _loadAll() async {
final lists = await Future.wait(TimetableElementType.values.map(_loadFor));
@@ -117,20 +142,16 @@ class _ElementPickerPageState extends State<ElementPickerPage> {
Haptics.selection();
// Hand the selection back to the timetable view, which renders the foreign
// plan inline. We do not navigate to a new page.
Navigator.of(context).pop((
type: item.type,
id: item.id,
label: item.primary,
));
Navigator.of(
context,
).pop((type: item.type, id: item.id, label: item.primary));
}
void _openFavorite(FavoriteTimetableElement favorite) {
Haptics.selection();
Navigator.of(context).pop((
type: favorite.type,
id: favorite.id,
label: favorite.label,
));
Navigator.of(
context,
).pop((type: favorite.type, id: favorite.id, label: favorite.label));
}
void _toggleFavorite(TimetableElementType type, int id, String label) {
@@ -325,9 +346,7 @@ class _ElementPickerPageState extends State<ElementPickerPage> {
return ListTile(
leading: Icon(_iconFor(item.type)),
title: Text(item.primary),
subtitle: subtitleParts.isEmpty
? null
: Text(subtitleParts.join(' · ')),
subtitle: subtitleParts.isEmpty ? null : Text(subtitleParts.join(' · ')),
trailing: IconButton(
icon: Icon(isFavorite ? Icons.star : Icons.star_border),
tooltip: isFavorite ? 'Favorit entfernen' : 'Als Favorit markieren',