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:
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user