Files
plezy/lib/focus/focus_chrome.dart
edde746 aa9118a799 fix(tv): hold the hub focus glow while the rail scrolls vertically
Moving UP between hubs showed the focused card's glow immediately: the
glow paints in the root overlay, unclipped by the rail viewport, so it
flashed over the spotlight while the target row was still offscreen.
The rail now suppresses only the glow for the 250 ms vertical scroll
and lets it fade in once the row settles. FocusGlowOverlay defers
attached-portal shows out of build, which OverlayPortalController
asserts against.
2026-08-23 10:12:26 +02:00

63 lines
2.2 KiB
Dart

import 'package:flutter/material.dart';
import 'card_focus_scope.dart';
import 'focus_glow_overlay.dart';
import 'focus_theme.dart';
/// Builds the focus border/glow chrome shared by [FocusableWrapper] and
/// [FocusBuilders.buildLockedFocusWrapper].
///
/// A function rather than a widget so it costs no element per card in dense TV
/// grids. Scale and input handling stay with the callers: the wrapper drives a
/// paint-only scale from its own controller and owns the [Focus] node, while
/// the locked builder scales implicitly and wraps gestures itself.
///
/// Callers pass the [duration] they already resolved via
/// [FocusTheme.getAnimationDuration] so a build resolves it once.
Widget buildFocusChrome(
BuildContext context, {
required bool showFocus,
required Duration duration,
double borderRadius = FocusTheme.defaultBorderRadius,
BorderRadius? borderRadii,
Color? focusColor,
bool useBackgroundFocus = false,
bool useFocusGlow = false,
bool delegateFocusBorder = false,
bool? showGlow,
Size? glowSize,
required Widget child,
}) {
Widget card;
if (delegateFocusBorder) {
card = CardFocusScope(showFocus: showFocus, child: child);
} else {
final decoration = useBackgroundFocus
? FocusTheme.focusBackgroundDecoration(isFocused: showFocus, borderRadius: borderRadius, radii: borderRadii)
: FocusTheme.focusDecoration(
context,
isFocused: showFocus,
borderRadius: borderRadius,
radii: borderRadii,
color: focusColor,
);
card = AnimatedContainer(duration: duration, curve: Curves.easeOutCubic, decoration: decoration, child: child);
}
// Glow (full-bleed cards) renders in an overlay above siblings so it stays
// symmetric; the in-card decoration only carries the border. [showGlow] lets
// callers hold back just the glow (e.g. while a viewport scroll animates)
// without touching the border or scale chrome.
if (useFocusGlow) {
card = FocusGlowOverlay(
isFocused: showGlow ?? showFocus,
borderRadius: borderRadius,
color: focusColor ?? FocusTheme.getFocusBorderColor(context),
glowSize: glowSize,
child: card,
);
}
return card;
}