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
+55 -9
View File
@@ -19,6 +19,7 @@ import '../services/settings_service.dart';
import 'settings_builder.dart';
import 'watched_indicator.dart';
import '../utils/content_utils.dart';
import '../utils/platform_detector.dart';
import '../utils/provider_extensions.dart';
import '../utils/formatters.dart';
import '../utils/media_navigation_helper.dart';
@@ -293,9 +294,7 @@ class MediaCardState extends State<MediaCard> with ContextMenuTapMixin<MediaCard
return SizedBox(
width: width,
height: height,
child: InkWell(
mouseCursor: SystemMouseCursors.click,
canRequestFocus: false,
child: _CardTapRegion(
onTap: () => _handleTap(context, item),
onTapDown: storeTapPosition,
onLongPress: showContextMenuFromTap,
@@ -359,9 +358,7 @@ class MediaCardState extends State<MediaCard> with ContextMenuTapMixin<MediaCard
return SizedBox(
width: widget.width,
child: InkWell(
mouseCursor: SystemMouseCursors.click,
canRequestFocus: false,
child: _CardTapRegion(
onTap: () => _handleTap(context, item),
onTapDown: storeTapPosition,
onLongPress: showContextMenuFromTap,
@@ -589,9 +586,7 @@ class _MediaCardList extends StatelessWidget {
return CardFocusBorder(
borderRadius: tokens(context).radiusSm,
strokeAlign: BorderSide.strokeAlignInside,
child: InkWell(
mouseCursor: SystemMouseCursors.click,
canRequestFocus: false, // Keyboard handled by FocusableMediaCard
child: _CardTapRegion(
onTap: onTap,
onTapDown: onTapDown,
onLongPress: onLongPress,
@@ -980,3 +975,54 @@ class SkeletonLoader extends StatelessWidget {
);
}
}
/// Tap surface for a card: a full [InkWell] (ripple, hover, cursor) where a
/// pointer exists, a bare [GestureDetector] on TV where the d-pad drives
/// activation and the per-card ink/hover/focus machinery is dead weight.
/// Keyboard focus is handled by the focus wrappers either way
/// (canRequestFocus stays false on the InkWell).
class _CardTapRegion extends StatelessWidget {
const _CardTapRegion({
required this.onTap,
this.onTapDown,
this.onLongPress,
this.onSecondaryTap,
this.onSecondaryTapDown,
this.borderRadius,
required this.child,
});
final VoidCallback onTap;
final GestureTapDownCallback? onTapDown;
final VoidCallback? onLongPress;
final VoidCallback? onSecondaryTap;
final GestureTapDownCallback? onSecondaryTapDown;
final BorderRadius? borderRadius;
final Widget child;
@override
Widget build(BuildContext context) {
if (PlatformDetector.isTV()) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
onTapDown: onTapDown,
onLongPress: onLongPress,
onSecondaryTap: onSecondaryTap,
onSecondaryTapDown: onSecondaryTapDown,
child: child,
);
}
return InkWell(
mouseCursor: SystemMouseCursors.click,
canRequestFocus: false,
onTap: onTap,
onTapDown: onTapDown,
onLongPress: onLongPress,
onSecondaryTapDown: onSecondaryTapDown,
onSecondaryTap: onSecondaryTap,
borderRadius: borderRadius,
child: child,
);
}
}