Files
plezy/lib/focus/card_focus_scope.dart
edde746 4eaf4423a1 refactor: share focus chrome and simplify the TV picker and browse paths
Focus chrome was implemented twice, once in the focusable wrapper and once
in the focus builders; both now go through FocusChrome. TvColorPicker's
channel row was a copy of TvNumberSpinner and is now that widget in compact
density.

Also trims unused helpers and fields and simplifies the Jellyfin browse
paths.
2026-07-26 06:09:49 +02:00

73 lines
2.6 KiB
Dart

import 'package:flutter/material.dart';
import 'focus_theme.dart';
/// Exposes the focus state of an enclosing focus wrapper to a descendant
/// [CardFocusBorder] that draws the focus border itself.
///
/// The shared focus chrome ([buildFocusChrome]) inserts this instead of painting
/// a border when `delegateFocusBorder` is set, so cards can
/// put the border on the exact rect the design highlights (the poster image,
/// not the card-plus-captions rect — issue #1278). Only the [CardFocusBorder]
/// element registers a dependency, so a focus flip rebuilds just that border
/// box: a stable `child:` card subtree (e.g. the TV rail's MediaCard) is not
/// rebuilt.
class CardFocusScope extends InheritedWidget {
const CardFocusScope({super.key, required this.showFocus, required super.child});
/// Whether the enclosing wrapper currently shows focus visuals
/// (focused while in keyboard/d-pad input mode).
final bool showFocus;
/// Null when no delegating wrapper is above (touch mode skips the focus
/// wrappers entirely) — [CardFocusBorder] then renders its child bare.
static bool? maybeOf(BuildContext context) => context.dependOnInheritedWidgetOfExactType<CardFocusScope>()?.showFocus;
@override
bool updateShouldNotify(CardFocusScope oldWidget) => showFocus != oldWidget.showFocus;
}
/// Draws the focus border around its child based on the enclosing
/// [CardFocusScope], letting the card decide which rect gets highlighted.
///
/// Defaults to an outside stroke so the border hugs the child exactly like the
/// full-bleed card treatment (the child's own corner radius nests inside it).
/// Decoration only — never affects layout. Renders the child unchanged when no
/// scope is present.
class CardFocusBorder extends StatelessWidget {
const CardFocusBorder({
super.key,
this.borderRadius = FocusTheme.defaultBorderRadius,
this.borderRadii,
this.strokeAlign = BorderSide.strokeAlignOutside,
required this.child,
});
final double borderRadius;
/// Per-corner radii; overrides [borderRadius] when set (M3E grouped cards).
final BorderRadius? borderRadii;
final double strokeAlign;
final Widget child;
@override
Widget build(BuildContext context) {
final showFocus = CardFocusScope.maybeOf(context);
if (showFocus == null) return child;
return AnimatedContainer(
duration: FocusTheme.getAnimationDuration(context),
curve: Curves.easeOutCubic,
foregroundDecoration: FocusTheme.focusDecoration(
context,
isFocused: showFocus,
borderRadius: borderRadius,
radii: borderRadii,
borderStrokeAlign: strokeAlign,
),
child: child,
);
}
}