perf: slim per-card build cost

Memoize the per-image SHA-1 disk-cache key (ran on the UI thread on
every image build), fade images in via paint alpha instead of an
AnimatedSwitcher saveLayer per in-flight tile, create SettingsBuilder's
merged listenable once instead of per build, and drop the pointer
chrome (InkWell ink machinery, cursor MouseRegions) from cards on TV
where no pointer exists.
This commit is contained in:
edde746
2026-07-02 11:41:25 +02:00
parent 43ab5d3246
commit 868a1469a3
4 changed files with 245 additions and 45 deletions
+25 -6
View File
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../services/settings_service.dart';
@@ -15,19 +16,37 @@ extension SettingsContextRead on BuildContext {
/// depends on multiple settings (conditional visibility, derived values).
/// Inside [builder], read with [SettingsService.read] directly — the rebuild
/// is already wired through.
class SettingsBuilder extends StatelessWidget {
///
/// Stateful so the merged listenable is created once, not per build: cards
/// rebuild this widget constantly and re-merging would unsubscribe/resubscribe
/// every pref listener each time.
class SettingsBuilder extends StatefulWidget {
final List<Pref<Object?>> prefs;
final WidgetBuilder builder;
const SettingsBuilder({super.key, required this.prefs, required this.builder});
@override
Widget build(BuildContext context) {
State<SettingsBuilder> createState() => _SettingsBuilderState();
}
class _SettingsBuilderState extends State<SettingsBuilder> {
late Listenable _merged = _merge();
Listenable _merge() {
final svc = SettingsService.instance;
return ListenableBuilder(
listenable: Listenable.merge(prefs.map(svc.listenableOf).toList(growable: false)),
builder: (context, _) => builder(context),
);
return Listenable.merge(widget.prefs.map(svc.listenableOf).toList(growable: false));
}
@override
void didUpdateWidget(covariant SettingsBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (!listEquals(widget.prefs, oldWidget.prefs)) _merged = _merge();
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(listenable: _merged, builder: (context, _) => widget.builder(context));
}
}