Files
plezy/lib/widgets/focusable_popup_menu_button.dart
T
edde746 057ff98ec1 refactor(ui): delete never-passed widget parameters and unreachable surfaces
Grep-verified dead surface across shared widgets: the app-menu system's minWidth/maxWidth/childPadding/alignmentOffset knobs threaded through four layers with no caller ever passing them; FocusableListTile's suppressInitialSelect/hoverColor/textColor/iconColor machinery (~55 lines) reachable by no call site; OptimizedMediaImage.playlist with zero constructions; TvSpotlightBackground's primary-action button that neither caller can render; MediaGridDelegate.createDelegate whose only caller was a test (MediaGridGeometry.resolve is the single delegate composition now); and the CustomAppBar->Config->DesktopTopBar chain collapsed onto DesktopSliverAppBar with its ~11 never-passed passthrough params (snap kept — real callers pass it).
2026-08-17 19:02:22 +02:00

93 lines
3.0 KiB
Dart

import 'package:flutter/material.dart';
import '../focus/focusable_wrapper.dart';
import 'app_menu.dart';
/// An [AppMenuButton] that can be focused and opened with D-pad select.
class FocusablePopupMenuButton<T> extends StatefulWidget {
final Widget? icon;
final Widget? child;
final String? tooltip;
final bool enabled;
final AppMenuEntryBuilder<T> itemBuilder;
final ValueChanged<T>? onSelected;
final GlobalKey<AppMenuButtonState<T>>? menuKey;
final AppMenuAnchorAlignment anchorAlignment;
final FocusNode? focusNode;
final VoidCallback? onNavigateUp;
final VoidCallback? onNavigateDown;
final VoidCallback? onNavigateLeft;
final VoidCallback? onNavigateRight;
final String? semanticLabel;
/// Optional current value announced after the effective semantic label.
final String? semanticValue;
final double borderRadius;
final bool useBackgroundFocus;
final bool enableLongPress;
const FocusablePopupMenuButton({
super.key,
this.icon,
this.child,
this.tooltip,
this.enabled = true,
required this.itemBuilder,
this.onSelected,
this.menuKey,
this.anchorAlignment = AppMenuAnchorAlignment.start,
this.focusNode,
this.onNavigateUp,
this.onNavigateDown,
this.onNavigateLeft,
this.onNavigateRight,
this.semanticLabel,
this.semanticValue,
this.borderRadius = 100,
this.useBackgroundFocus = true,
this.enableLongPress = true,
}) : assert(icon != null || child != null, 'FocusablePopupMenuButton requires icon or child');
@override
State<FocusablePopupMenuButton<T>> createState() => _FocusablePopupMenuButtonState<T>();
}
class _FocusablePopupMenuButtonState<T> extends State<FocusablePopupMenuButton<T>> {
final _ownedMenuKey = GlobalKey<AppMenuButtonState<T>>();
GlobalKey<AppMenuButtonState<T>> get _menuKey => widget.menuKey ?? _ownedMenuKey;
void _showMenu() => _menuKey.currentState?.showButtonMenu(focusFirstItem: true);
@override
Widget build(BuildContext context) {
return FocusableWrapper(
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
disableScale: true,
borderRadius: widget.borderRadius,
useBackgroundFocus: widget.useBackgroundFocus,
descendantsAreFocusable: false,
semanticLabel: widget.semanticLabel ?? widget.tooltip,
semanticValue: widget.semanticValue,
enableLongPress: widget.enableLongPress,
onNavigateUp: widget.onNavigateUp,
onNavigateDown: widget.onNavigateDown,
onNavigateLeft: widget.onNavigateLeft,
onNavigateRight: widget.onNavigateRight,
onSelect: widget.enabled ? _showMenu : null,
onLongPress: widget.enabled && widget.enableLongPress ? _showMenu : null,
child: AppMenuButton<T>(
key: _menuKey,
icon: widget.icon,
tooltip: widget.tooltip,
enabled: widget.enabled,
onSelected: widget.onSelected,
entriesBuilder: widget.itemBuilder,
anchorAlignment: widget.anchorAlignment,
child: widget.child,
),
);
}
}