LCOV - code coverage report
Current view: top level - form_widget - grid_row_dropdown.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 471 0.0 %
Date: 2021-07-13 15:03:27 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2014 The Flutter Authors. All rights reserved.
       2             : // Use of this source code is governed by a BSD-style license that can be
       3             : // found in the LICENSE file.
       4             : 
       5             : /// Copy of material dropdown.dart file
       6             : /// Changes are removing horizontal item Padding (kMenuItemPadding) and Min Height (kMenuItemHeight)
       7             : 
       8             : // coverage: ignore-file
       9             : 
      10             : import 'dart:math' as math;
      11             : import 'dart:ui' show window;
      12             : 
      13             : import 'package:flutter/foundation.dart';
      14             : import 'package:flutter/material.dart';
      15             : import 'package:flutter/rendering.dart';
      16             : import 'package:flutter/services.dart';
      17             : import 'package:flutter/widgets.dart';
      18             : 
      19             : const Duration _kGridRowDropdownMenuDuration = Duration(milliseconds: 300);
      20             : const double _kMenuItemHeight = 0;
      21             : const double _kDenseButtonHeight = 24.0;
      22             : const EdgeInsets _kMenuItemPadding = EdgeInsets.zero;
      23             : const EdgeInsetsGeometry _kAlignedButtonPadding = EdgeInsetsDirectional.only(start: 16.0, end: 4.0);
      24             : const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero;
      25             : const EdgeInsets _kAlignedMenuMargin = EdgeInsets.zero;
      26             : const EdgeInsetsGeometry _kUnalignedMenuMargin = EdgeInsetsDirectional.only(start: 16.0, end: 24.0);
      27             : 
      28             : /// A builder to customize dropdown buttons.
      29             : ///
      30             : /// Used by [GridRowDropdownButton.selectedItemBuilder].
      31             : typedef GridRowDropdownButtonBuilder = List<Widget> Function(BuildContext context);
      32             : 
      33             : class _GridRowDropdownMenuPainter extends CustomPainter {
      34           0 :   _GridRowDropdownMenuPainter({
      35             :     this.color,
      36             :     this.elevation,
      37             :     this.selectedIndex,
      38             :     required this.resize,
      39             :     required this.getSelectedItemOffset,
      40           0 :   }) : _painter = BoxDecoration(
      41             :     // If you add an image here, you must provide a real
      42             :     // configuration in the paint() function and you must provide some sort
      43             :     // of onChanged callback here.
      44             :     color: color,
      45           0 :     borderRadius: BorderRadius.circular(2.0),
      46           0 :     boxShadow: kElevationToShadow[elevation],
      47           0 :   ).createBoxPainter(),
      48           0 :         super(repaint: resize);
      49             : 
      50             :   final Color? color;
      51             :   final int? elevation;
      52             :   final int? selectedIndex;
      53             :   final Animation<double> resize;
      54             :   final ValueGetter<double> getSelectedItemOffset;
      55             :   final BoxPainter _painter;
      56             : 
      57           0 :   @override
      58             :   void paint(Canvas canvas, Size size) {
      59           0 :     final double selectedItemOffset = getSelectedItemOffset();
      60           0 :     final Tween<double> top = Tween<double>(
      61           0 :       begin: selectedItemOffset.clamp(0.0, math.max(size.height - _kMenuItemHeight, 0.0)),
      62             :       end: 0.0,
      63             :     );
      64             : 
      65           0 :     final Tween<double> bottom = Tween<double>(
      66           0 :       begin: (top.begin! + _kMenuItemHeight).clamp(math.min(_kMenuItemHeight, size.height), size.height),
      67           0 :       end: size.height,
      68             :     );
      69             : 
      70           0 :     final Rect rect = Rect.fromLTRB(0.0, top.evaluate(resize), size.width, bottom.evaluate(resize));
      71             : 
      72           0 :     _painter.paint(canvas, rect.topLeft, ImageConfiguration(size: rect.size));
      73             :   }
      74             : 
      75           0 :   @override
      76             :   bool shouldRepaint(_GridRowDropdownMenuPainter oldPainter) {
      77           0 :     return oldPainter.color != color
      78           0 :         || oldPainter.elevation != elevation
      79           0 :         || oldPainter.selectedIndex != selectedIndex
      80           0 :         || oldPainter.resize != resize;
      81             :   }
      82             : }
      83             : 
      84             : // The widget that is the button wrapping the menu items.
      85             : class _GridRowDropdownMenuItemButton<T> extends StatefulWidget {
      86           0 :   const _GridRowDropdownMenuItemButton({
      87             :     Key? key,
      88             :     this.padding,
      89             :     required this.route,
      90             :     required this.buttonRect,
      91             :     required this.constraints,
      92             :     required this.itemIndex,
      93             :     required this.enableFeedback,
      94           0 :   }) : super(key: key);
      95             : 
      96             :   final _GridRowDropdownRoute<T> route;
      97             :   final EdgeInsets? padding;
      98             :   final Rect buttonRect;
      99             :   final BoxConstraints constraints;
     100             :   final int itemIndex;
     101             :   final bool enableFeedback;
     102             : 
     103           0 :   @override
     104           0 :   _GridRowDropdownMenuItemButtonState<T> createState() => _GridRowDropdownMenuItemButtonState<T>();
     105             : }
     106             : 
     107             : class _GridRowDropdownMenuItemButtonState<T> extends State<_GridRowDropdownMenuItemButton<T>> {
     108           0 :   void _handleFocusChange(bool focused) {
     109             :     final bool inTraditionalMode;
     110           0 :     switch (FocusManager.instance.highlightMode) {
     111           0 :       case FocusHighlightMode.touch:
     112             :         inTraditionalMode = false;
     113             :         break;
     114           0 :       case FocusHighlightMode.traditional:
     115             :         inTraditionalMode = true;
     116             :         break;
     117             :     }
     118             : 
     119             :     if (focused && inTraditionalMode) {
     120           0 :       final _MenuLimits menuLimits = widget.route.getMenuLimits(
     121           0 :         widget.buttonRect,
     122           0 :         widget.constraints.maxHeight,
     123           0 :         widget.itemIndex,
     124             :       );
     125           0 :       widget.route.scrollController!.animateTo(
     126           0 :         menuLimits.scrollOffset,
     127             :         curve: Curves.easeInOut,
     128             :         duration: const Duration(milliseconds: 100),
     129             :       );
     130             :     }
     131             :   }
     132             : 
     133           0 :   void _handleOnTap() {
     134           0 :     final GridRowDropdownMenuItem<T> dropdownMenuItem = widget.route.items[widget.itemIndex].item!;
     135             : 
     136           0 :     dropdownMenuItem.onTap?.call();
     137             : 
     138           0 :     Navigator.pop(
     139           0 :       context,
     140           0 :       _GridRowDropdownRouteResult<T>(dropdownMenuItem.value),
     141             :     );
     142             :   }
     143             : 
     144             :   static const Map<ShortcutActivator, Intent> _webShortcuts = <ShortcutActivator, Intent>{
     145             :     // On the web, up/down don't change focus, *except* in a <select>
     146             :     // element, which is what a dropdown emulates.
     147             :     SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down),
     148             :     SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up),
     149             :   };
     150             : 
     151           0 :   @override
     152             :   Widget build(BuildContext context) {
     153           0 :     final GridRowDropdownMenuItem<T> dropdownMenuItem = widget.route.items[widget.itemIndex].item!;
     154             :     final CurvedAnimation opacity;
     155           0 :     final double unit = 0.5 / (widget.route.items.length + 1.5);
     156           0 :     if (widget.itemIndex == widget.route.selectedIndex) {
     157           0 :       opacity = CurvedAnimation(parent: widget.route.animation!, curve: const Threshold(0.0));
     158             :     } else {
     159           0 :       final double start = (0.5 + (widget.itemIndex + 1) * unit).clamp(0.0, 1.0);
     160           0 :       final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
     161           0 :       opacity = CurvedAnimation(parent: widget.route.animation!, curve: Interval(start, end));
     162             :     }
     163           0 :     Widget child = Container(
     164           0 :       padding: widget.padding,
     165           0 :       child: widget.route.items[widget.itemIndex],
     166             :     );
     167             :     // An [InkWell] is added to the item only if it is enabled
     168           0 :     if (dropdownMenuItem.enabled) {
     169           0 :       child = InkWell(
     170           0 :         autofocus: widget.itemIndex == widget.route.selectedIndex,
     171           0 :         enableFeedback: widget.enableFeedback,
     172           0 :         onTap: _handleOnTap,
     173           0 :         onFocusChange: _handleFocusChange,
     174             :         child: child,
     175             :       );
     176             :     }
     177           0 :     child = FadeTransition(opacity: opacity, child: child);
     178           0 :     if (kIsWeb && dropdownMenuItem.enabled) {
     179           0 :       child = Shortcuts(
     180             :         shortcuts: _webShortcuts,
     181             :         child: child,
     182             :       );
     183             :     }
     184             :     return child;
     185             :   }
     186             : }
     187             : 
     188             : class _GridRowDropdownMenu<T> extends StatefulWidget {
     189           0 :   const _GridRowDropdownMenu({
     190             :     Key? key,
     191             :     this.padding,
     192             :     required this.route,
     193             :     required this.buttonRect,
     194             :     required this.constraints,
     195             :     this.dropdownColor,
     196             :     required this.enableFeedback,
     197           0 :   }) : super(key: key);
     198             : 
     199             :   final _GridRowDropdownRoute<T> route;
     200             :   final EdgeInsets? padding;
     201             :   final Rect buttonRect;
     202             :   final BoxConstraints constraints;
     203             :   final Color? dropdownColor;
     204             :   final bool enableFeedback;
     205             : 
     206           0 :   @override
     207           0 :   _GridRowDropdownMenuState<T> createState() => _GridRowDropdownMenuState<T>();
     208             : }
     209             : 
     210             : class _GridRowDropdownMenuState<T> extends State<_GridRowDropdownMenu<T>> {
     211             :   late CurvedAnimation _fadeOpacity;
     212             :   late CurvedAnimation _resize;
     213             : 
     214           0 :   @override
     215             :   void initState() {
     216           0 :     super.initState();
     217             :     // We need to hold these animations as state because of their curve
     218             :     // direction. When the route's animation reverses, if we were to recreate
     219             :     // the CurvedAnimation objects in build, we'd lose
     220             :     // CurvedAnimation._curveDirection.
     221           0 :     _fadeOpacity = CurvedAnimation(
     222           0 :       parent: widget.route.animation!,
     223             :       curve: const Interval(0.0, 0.25),
     224             :       reverseCurve: const Interval(0.75, 1.0),
     225             :     );
     226           0 :     _resize = CurvedAnimation(
     227           0 :       parent: widget.route.animation!,
     228             :       curve: const Interval(0.25, 0.5),
     229             :       reverseCurve: const Threshold(0.0),
     230             :     );
     231             :   }
     232             : 
     233           0 :   @override
     234             :   Widget build(BuildContext context) {
     235             :     // The menu is shown in three stages (unit timing in brackets):
     236             :     // [0s - 0.25s] - Fade in a rect-sized menu container with the selected item.
     237             :     // [0.25s - 0.5s] - Grow the otherwise empty menu container from the center
     238             :     //   until it's big enough for as many items as we're going to show.
     239             :     // [0.5s - 1.0s] Fade in the remaining visible items from top to bottom.
     240             :     //
     241             :     // When the menu is dismissed we just fade the entire thing out
     242             :     // in the first 0.25s.
     243           0 :     assert(debugCheckHasMaterialLocalizations(context));
     244           0 :     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
     245           0 :     final _GridRowDropdownRoute<T> route = widget.route;
     246           0 :     final List<Widget> children = <Widget>[
     247           0 :       for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex)
     248           0 :         _GridRowDropdownMenuItemButton<T>(
     249           0 :           route: widget.route,
     250           0 :           padding: widget.padding,
     251           0 :           buttonRect: widget.buttonRect,
     252           0 :           constraints: widget.constraints,
     253             :           itemIndex: itemIndex,
     254           0 :           enableFeedback: widget.enableFeedback,
     255             :         ),
     256             :     ];
     257             : 
     258           0 :     return FadeTransition(
     259           0 :       opacity: _fadeOpacity,
     260           0 :       child: CustomPaint(
     261           0 :         painter: _GridRowDropdownMenuPainter(
     262           0 :           color: widget.dropdownColor ?? Theme.of(context).canvasColor,
     263           0 :           elevation: route.elevation,
     264           0 :           selectedIndex: route.selectedIndex,
     265           0 :           resize: _resize,
     266             :           // This offset is passed as a callback, not a value, because it must
     267             :           // be retrieved at paint time (after layout), not at build time.
     268           0 :           getSelectedItemOffset: () => route.getItemOffset(route.selectedIndex),
     269             :         ),
     270           0 :         child: Semantics(
     271             :           scopesRoute: true,
     272             :           namesRoute: true,
     273             :           explicitChildNodes: true,
     274           0 :           label: localizations.popupMenuLabel,
     275           0 :           child: Material(
     276             :             type: MaterialType.transparency,
     277           0 :             textStyle: route.style,
     278           0 :             child: ScrollConfiguration(
     279             :               // GridRowDropdown menus should never overscroll or display an overscroll indicator.
     280             :               // Scrollbars are built-in below.
     281             :               // Platform must use Theme and ScrollPhysics must be Clamping.
     282           0 :               behavior: ScrollConfiguration.of(context).copyWith(
     283             :                 scrollbars: false,
     284             :                 overscroll: false,
     285             :                 physics: const ClampingScrollPhysics(),
     286           0 :                 platform: Theme.of(context).platform,
     287             :               ),
     288           0 :               child: PrimaryScrollController(
     289           0 :                 controller: widget.route.scrollController!,
     290           0 :                 child: Scrollbar(
     291             :                   isAlwaysShown: true,
     292           0 :                   child: ListView(
     293             :                     padding: kMaterialListPadding,
     294             :                     shrinkWrap: true,
     295             :                     children: children,
     296             :                   ),
     297             :                 ),
     298             :               ),
     299             :             ),
     300             :           ),
     301             :         ),
     302             :       ),
     303             :     );
     304             :   }
     305             : }
     306             : 
     307             : class _GridRowDropdownMenuRouteLayout<T> extends SingleChildLayoutDelegate {
     308           0 :   _GridRowDropdownMenuRouteLayout({
     309             :     required this.buttonRect,
     310             :     required this.route,
     311             :     required this.textDirection,
     312             :   });
     313             : 
     314             :   final Rect buttonRect;
     315             :   final _GridRowDropdownRoute<T> route;
     316             :   final TextDirection? textDirection;
     317             : 
     318           0 :   @override
     319             :   BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
     320             :     // The maximum height of a simple menu should be one or more rows less than
     321             :     // the view height. This ensures a tappable area outside of the simple menu
     322             :     // with which to dismiss the menu.
     323             :     //   -- https://material.io/design/components/menus.html#usage
     324           0 :     double maxHeight = math.max(0.0, constraints.maxHeight - 2 * _kMenuItemHeight);
     325           0 :     if (route.menuMaxHeight != null && route.menuMaxHeight! <= maxHeight) {
     326           0 :       maxHeight = route.menuMaxHeight!;
     327             :     }
     328             :     // The width of a menu should be at most the view width. This ensures that
     329             :     // the menu does not extend past the left and right edges of the screen.
     330           0 :     final double width = math.min(constraints.maxWidth, buttonRect.width);
     331           0 :     return BoxConstraints(
     332             :       minWidth: width,
     333             :       maxWidth: width,
     334             :       minHeight: 0.0,
     335             :       maxHeight: maxHeight,
     336             :     );
     337             :   }
     338             : 
     339           0 :   @override
     340             :   Offset getPositionForChild(Size size, Size childSize) {
     341           0 :     final _MenuLimits menuLimits = route.getMenuLimits(buttonRect, size.height, route.selectedIndex);
     342             : 
     343           0 :     assert(() {
     344           0 :       final Rect container = Offset.zero & size;
     345           0 :       if (container.intersect(buttonRect) == buttonRect) {
     346             :         // If the button was entirely on-screen, then verify
     347             :         // that the menu is also on-screen.
     348             :         // If the button was a bit off-screen, then, oh well.
     349           0 :         assert(menuLimits.top >= 0.0);
     350           0 :         assert(menuLimits.top + menuLimits.height <= size.height);
     351             :       }
     352             :       return true;
     353           0 :     }());
     354           0 :     assert(textDirection != null);
     355             :     final double left;
     356           0 :     switch (textDirection!) {
     357           0 :       case TextDirection.rtl:
     358           0 :         left = buttonRect.right.clamp(0.0, size.width) - childSize.width;
     359             :         break;
     360           0 :       case TextDirection.ltr:
     361           0 :         left = buttonRect.left.clamp(0.0, size.width - childSize.width);
     362             :         break;
     363             :     }
     364             : 
     365           0 :     return Offset(left, menuLimits.top);
     366             :   }
     367             : 
     368           0 :   @override
     369             :   bool shouldRelayout(_GridRowDropdownMenuRouteLayout<T> oldDelegate) {
     370           0 :     return buttonRect != oldDelegate.buttonRect || textDirection != oldDelegate.textDirection;
     371             :   }
     372             : }
     373             : 
     374             : // We box the return value so that the return value can be null. Otherwise,
     375             : // canceling the route (which returns null) would get confused with actually
     376             : // returning a real null value.
     377             : @immutable
     378             : class _GridRowDropdownRouteResult<T> {
     379           0 :   const _GridRowDropdownRouteResult(this.result);
     380             : 
     381             :   final T? result;
     382             : 
     383           0 :   @override
     384             :   bool operator ==(Object other) {
     385           0 :     return other is _GridRowDropdownRouteResult<T>
     386           0 :         && other.result == result;
     387             :   }
     388             : 
     389           0 :   @override
     390           0 :   int get hashCode => result.hashCode;
     391             : }
     392             : 
     393             : class _MenuLimits {
     394           0 :   const _MenuLimits(this.top, this.bottom, this.height, this.scrollOffset);
     395             :   final double top;
     396             :   final double bottom;
     397             :   final double height;
     398             :   final double scrollOffset;
     399             : }
     400             : 
     401             : class _GridRowDropdownRoute<T> extends PopupRoute<_GridRowDropdownRouteResult<T>> {
     402           0 :   _GridRowDropdownRoute({
     403             :     required this.items,
     404             :     required this.padding,
     405             :     required this.buttonRect,
     406             :     required this.selectedIndex,
     407             :     this.elevation = 8,
     408             :     required this.capturedThemes,
     409             :     required this.style,
     410             :     this.barrierLabel,
     411             :     this.itemHeight,
     412             :     this.dropdownColor,
     413             :     this.menuMaxHeight,
     414             :     required this.enableFeedback,
     415           0 :   }) : assert(style != null),
     416           0 :         itemHeights = List<double>.filled(items.length, itemHeight ?? kMinInteractiveDimension);
     417             : 
     418             :   final List<_MenuItem<T>> items;
     419             :   final EdgeInsetsGeometry padding;
     420             :   final Rect buttonRect;
     421             :   final int selectedIndex;
     422             :   final int elevation;
     423             :   final CapturedThemes capturedThemes;
     424             :   final TextStyle style;
     425             :   final double? itemHeight;
     426             :   final Color? dropdownColor;
     427             :   final double? menuMaxHeight;
     428             :   final bool enableFeedback;
     429             : 
     430             :   final List<double> itemHeights;
     431             :   ScrollController? scrollController;
     432             : 
     433           0 :   @override
     434             :   Duration get transitionDuration => _kGridRowDropdownMenuDuration;
     435             : 
     436           0 :   @override
     437             :   bool get barrierDismissible => true;
     438             : 
     439           0 :   @override
     440             :   Color? get barrierColor => null;
     441             : 
     442             :   @override
     443             :   final String? barrierLabel;
     444             : 
     445           0 :   @override
     446             :   Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
     447           0 :     return LayoutBuilder(
     448           0 :       builder: (BuildContext context, BoxConstraints constraints) {
     449           0 :         return _GridRowDropdownRoutePage<T>(
     450             :           route: this,
     451             :           constraints: constraints,
     452           0 :           items: items,
     453           0 :           padding: padding,
     454           0 :           buttonRect: buttonRect,
     455           0 :           selectedIndex: selectedIndex,
     456           0 :           elevation: elevation,
     457           0 :           capturedThemes: capturedThemes,
     458           0 :           style: style,
     459           0 :           dropdownColor: dropdownColor,
     460           0 :           enableFeedback: enableFeedback,
     461             :         );
     462             :       },
     463             :     );
     464             :   }
     465             : 
     466           0 :   void _dismiss() {
     467           0 :     if (isActive) {
     468           0 :       navigator?.removeRoute(this);
     469             :     }
     470             :   }
     471             : 
     472           0 :   double getItemOffset(int index) {
     473           0 :     double offset = kMaterialListPadding.top;
     474           0 :     if (items.isNotEmpty && index > 0) {
     475           0 :       assert(items.length == itemHeights.length);
     476           0 :       offset += itemHeights
     477           0 :           .sublist(0, index)
     478           0 :           .reduce((double total, double height) => total + height);
     479             :     }
     480             :     return offset;
     481             :   }
     482             : 
     483             :   // Returns the vertical extent of the menu and the initial scrollOffset
     484             :   // for the ListView that contains the menu items. The vertical center of the
     485             :   // selected item is aligned with the button's vertical center, as far as
     486             :   // that's possible given availableHeight.
     487           0 :   _MenuLimits getMenuLimits(Rect buttonRect, double availableHeight, int index) {
     488           0 :     final double maxMenuHeight = availableHeight - 2.0 * _kMenuItemHeight;
     489           0 :     final double buttonTop = buttonRect.top;
     490           0 :     final double buttonBottom = math.min(buttonRect.bottom, availableHeight);
     491           0 :     final double selectedItemOffset = getItemOffset(index);
     492             : 
     493             :     // If the button is placed on the bottom or top of the screen, its top or
     494             :     // bottom may be less than [_kMenuItemHeight] from the edge of the screen.
     495             :     // In this case, we want to change the menu limits to align with the top
     496             :     // or bottom edge of the button.
     497           0 :     final double topLimit = math.min(_kMenuItemHeight, buttonTop);
     498           0 :     final double bottomLimit = math.max(availableHeight - _kMenuItemHeight, buttonBottom);
     499             : 
     500           0 :     double menuTop = (buttonTop - selectedItemOffset) - (itemHeights[selectedIndex] - buttonRect.height) / 2.0;
     501           0 :     double preferredMenuHeight = kMaterialListPadding.vertical;
     502           0 :     if (items.isNotEmpty)
     503           0 :       preferredMenuHeight += itemHeights.reduce((double total, double height) => total + height);
     504             : 
     505             :     // If there are too many elements in the menu, we need to shrink it down
     506             :     // so it is at most the maxMenuHeight.
     507           0 :     final double menuHeight = math.min(maxMenuHeight, preferredMenuHeight);
     508           0 :     double menuBottom = menuTop + menuHeight;
     509             : 
     510             :     // If the computed top or bottom of the menu are outside of the range
     511             :     // specified, we need to bring them into range. If the item height is larger
     512             :     // than the button height and the button is at the very bottom or top of the
     513             :     // screen, the menu will be aligned with the bottom or top of the button
     514             :     // respectively.
     515           0 :     if (menuTop < topLimit)
     516           0 :       menuTop = math.min(buttonTop, topLimit);
     517             : 
     518           0 :     if (menuBottom > bottomLimit) {
     519           0 :       menuBottom = math.max(buttonBottom, bottomLimit);
     520           0 :       menuTop = menuBottom - menuHeight;
     521             :     }
     522             : 
     523             :     double scrollOffset = 0;
     524             :     // If all of the menu items will not fit within availableHeight then
     525             :     // compute the scroll offset that will line the selected menu item up
     526             :     // with the select item. This is only done when the menu is first
     527             :     // shown - subsequently we leave the scroll offset where the user left
     528             :     // it. This scroll offset is only accurate for fixed height menu items
     529             :     // (the default).
     530           0 :     if (preferredMenuHeight > maxMenuHeight) {
     531             :       // The offset should be zero if the selected item is in view at the beginning
     532             :       // of the menu. Otherwise, the scroll offset should center the item if possible.
     533           0 :       scrollOffset = math.max(0.0, selectedItemOffset - (buttonTop - menuTop));
     534             :       // If the selected item's scroll offset is greater than the maximum scroll offset,
     535             :       // set it instead to the maximum allowed scroll offset.
     536           0 :       scrollOffset = math.min(scrollOffset, preferredMenuHeight - menuHeight);
     537             :     }
     538             : 
     539           0 :     return _MenuLimits(menuTop, menuBottom, menuHeight, scrollOffset);
     540             :   }
     541             : }
     542             : 
     543             : class _GridRowDropdownRoutePage<T> extends StatelessWidget {
     544           0 :   const _GridRowDropdownRoutePage({
     545             :     Key? key,
     546             :     required this.route,
     547             :     required this.constraints,
     548             :     this.items,
     549             :     required this.padding,
     550             :     required this.buttonRect,
     551             :     required this.selectedIndex,
     552             :     this.elevation = 8,
     553             :     required this.capturedThemes,
     554             :     this.style,
     555             :     required this.dropdownColor,
     556             :     required this.enableFeedback,
     557           0 :   }) : super(key: key);
     558             : 
     559             :   final _GridRowDropdownRoute<T> route;
     560             :   final BoxConstraints constraints;
     561             :   final List<_MenuItem<T>>? items;
     562             :   final EdgeInsetsGeometry padding;
     563             :   final Rect buttonRect;
     564             :   final int selectedIndex;
     565             :   final int elevation;
     566             :   final CapturedThemes capturedThemes;
     567             :   final TextStyle? style;
     568             :   final Color? dropdownColor;
     569             :   final bool enableFeedback;
     570             : 
     571           0 :   @override
     572             :   Widget build(BuildContext context) {
     573           0 :     assert(debugCheckHasDirectionality(context));
     574             : 
     575             :     // Computing the initialScrollOffset now, before the items have been laid
     576             :     // out. This only works if the item heights are effectively fixed, i.e. either
     577             :     // GridRowDropdownButton.itemHeight is specified or GridRowDropdownButton.itemHeight is null
     578             :     // and all of the items' intrinsic heights are less than kMinInteractiveDimension.
     579             :     // Otherwise the initialScrollOffset is just a rough approximation based on
     580             :     // treating the items as if their heights were all equal to kMinInteractiveDimension.
     581           0 :     if (route.scrollController == null) {
     582           0 :       final _MenuLimits menuLimits = route.getMenuLimits(buttonRect, constraints.maxHeight, selectedIndex);
     583           0 :       route.scrollController = ScrollController(initialScrollOffset: menuLimits.scrollOffset);
     584             :     }
     585             : 
     586           0 :     final TextDirection? textDirection = Directionality.maybeOf(context);
     587           0 :     final Widget menu = _GridRowDropdownMenu<T>(
     588           0 :       route: route,
     589           0 :       padding: padding.resolve(textDirection),
     590           0 :       buttonRect: buttonRect,
     591           0 :       constraints: constraints,
     592           0 :       dropdownColor: dropdownColor,
     593           0 :       enableFeedback: enableFeedback,
     594             :     );
     595             : 
     596           0 :     return MediaQuery.removePadding(
     597             :       context: context,
     598             :       removeTop: true,
     599             :       removeBottom: true,
     600             :       removeLeft: true,
     601             :       removeRight: true,
     602           0 :       child: Builder(
     603           0 :         builder: (BuildContext context) {
     604           0 :           return CustomSingleChildLayout(
     605           0 :             delegate: _GridRowDropdownMenuRouteLayout<T>(
     606           0 :               buttonRect: buttonRect,
     607           0 :               route: route,
     608             :               textDirection: textDirection,
     609             :             ),
     610           0 :             child: capturedThemes.wrap(menu),
     611             :           );
     612             :         },
     613             :       ),
     614             :     );
     615             :   }
     616             : }
     617             : 
     618             : // This widget enables _GridRowDropdownRoute to look up the sizes of
     619             : // each menu item. These sizes are used to compute the offset of the selected
     620             : // item so that _GridRowDropdownRoutePage can align the vertical center of the
     621             : // selected item lines up with the vertical center of the dropdown button,
     622             : // as closely as possible.
     623             : class _MenuItem<T> extends SingleChildRenderObjectWidget {
     624           0 :   const _MenuItem({
     625             :     Key? key,
     626             :     required this.onLayout,
     627             :     required this.item,
     628           0 :   }) : assert(onLayout != null), super(key: key, child: item);
     629             : 
     630             :   final ValueChanged<Size> onLayout;
     631             :   final GridRowDropdownMenuItem<T>? item;
     632             : 
     633           0 :   @override
     634             :   RenderObject createRenderObject(BuildContext context) {
     635           0 :     return _RenderMenuItem(onLayout);
     636             :   }
     637             : 
     638           0 :   @override
     639             :   void updateRenderObject(BuildContext context, covariant _RenderMenuItem renderObject) {
     640           0 :     renderObject.onLayout = onLayout;
     641             :   }
     642             : }
     643             : 
     644             : class _RenderMenuItem extends RenderProxyBox {
     645           0 :   _RenderMenuItem(this.onLayout, [RenderBox? child]) : assert(onLayout != null), super(child);
     646             : 
     647             :   ValueChanged<Size> onLayout;
     648             : 
     649           0 :   @override
     650             :   void performLayout() {
     651           0 :     super.performLayout();
     652           0 :     onLayout(size);
     653             :   }
     654             : }
     655             : 
     656             : // The container widget for a menu item created by a [GridRowDropdownButton]. It
     657             : // provides the default configuration for [GridRowDropdownMenuItem]s, as well as a
     658             : // [GridRowDropdownButton]'s hint and disabledHint widgets.
     659             : class _GridRowDropdownMenuItemContainer extends StatelessWidget {
     660             :   /// Creates an item for a dropdown menu.
     661             :   ///
     662             :   /// The [child] argument is required.
     663           0 :   const _GridRowDropdownMenuItemContainer({
     664             :     Key? key,
     665             :     this.alignment = AlignmentDirectional.centerStart,
     666             :     required this.child,
     667           0 :   }) : assert(child != null),
     668           0 :         super(key: key);
     669             : 
     670             :   /// The widget below this widget in the tree.
     671             :   ///
     672             :   /// Typically a [Text] widget.
     673             :   final Widget child;
     674             : 
     675             :   /// Defines how the item is positioned within the container.
     676             :   ///
     677             :   /// This property must not be null. It defaults to [AlignmentDirectional.centerStart].
     678             :   ///
     679             :   /// See also:
     680             :   ///
     681             :   ///  * [Alignment], a class with convenient constants typically used to
     682             :   ///    specify an [AlignmentGeometry].
     683             :   ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
     684             :   ///    relative to text direction.
     685             :   final AlignmentGeometry alignment;
     686             : 
     687           0 :   @override
     688             :   Widget build(BuildContext context) {
     689           0 :     return Container(
     690             :       constraints: const BoxConstraints(minHeight: _kMenuItemHeight),
     691           0 :       alignment: alignment,
     692           0 :       child: child,
     693             :     );
     694             :   }
     695             : }
     696             : 
     697             : /// An item in a menu created by a [GridRowDropdownButton].
     698             : ///
     699             : /// The type `T` is the type of the value the entry represents. All the entries
     700             : /// in a given menu must represent values with consistent types.
     701             : class GridRowDropdownMenuItem<T> extends _GridRowDropdownMenuItemContainer {
     702             :   /// Creates an item for a dropdown menu.
     703             :   ///
     704             :   /// The [child] argument is required.
     705           0 :   const GridRowDropdownMenuItem({
     706             :     Key? key,
     707             :     this.onTap,
     708             :     this.value,
     709             :     this.enabled = true,
     710             :     AlignmentGeometry alignment = AlignmentDirectional.centerStart,
     711             :     required Widget child,
     712           0 :   }) : assert(child != null),
     713           0 :         super(key: key, alignment:alignment, child: child);
     714             : 
     715             :   /// Called when the dropdown menu item is tapped.
     716             :   final VoidCallback? onTap;
     717             : 
     718             :   /// The value to return if the user selects this menu item.
     719             :   ///
     720             :   /// Eventually returned in a call to [GridRowDropdownButton.onChanged].
     721             :   final T? value;
     722             : 
     723             :   /// Whether or not a user can select this menu item.
     724             :   ///
     725             :   /// Defaults to `true`.
     726             :   final bool enabled;
     727             : }
     728             : 
     729             : /// An inherited widget that causes any descendant [GridRowDropdownButton]
     730             : /// widgets to not include their regular underline.
     731             : ///
     732             : /// This is used by [DataTable] to remove the underline from any
     733             : /// [GridRowDropdownButton] widgets placed within material data tables, as
     734             : /// required by the material design specification.
     735             : class GridRowDropdownButtonHideUnderline extends InheritedWidget {
     736             :   /// Creates a [GridRowDropdownButtonHideUnderline]. A non-null [child] must
     737             :   /// be given.
     738           0 :   const GridRowDropdownButtonHideUnderline({
     739             :     Key? key,
     740             :     required Widget child,
     741           0 :   }) : assert(child != null),
     742           0 :         super(key: key, child: child);
     743             : 
     744             :   /// Returns whether the underline of [GridRowDropdownButton] widgets should
     745             :   /// be hidden.
     746           0 :   static bool at(BuildContext context) {
     747           0 :     return context.dependOnInheritedWidgetOfExactType<GridRowDropdownButtonHideUnderline>() != null;
     748             :   }
     749             : 
     750           0 :   @override
     751             :   bool updateShouldNotify(GridRowDropdownButtonHideUnderline oldWidget) => false;
     752             : }
     753             : 
     754             : /// A material design button for selecting from a list of items.
     755             : ///
     756             : /// A dropdown button lets the user select from a number of items. The button
     757             : /// shows the currently selected item as well as an arrow that opens a menu for
     758             : /// selecting another item.
     759             : ///
     760             : /// The type `T` is the type of the [value] that each dropdown item represents.
     761             : /// All the entries in a given menu must represent values with consistent types.
     762             : /// Typically, an enum is used. Each [GridRowDropdownMenuItem] in [items] must be
     763             : /// specialized with that same type argument.
     764             : ///
     765             : /// The [onChanged] callback should update a state variable that defines the
     766             : /// dropdown's value. It should also call [State.setState] to rebuild the
     767             : /// dropdown with the new value.
     768             : ///
     769             : /// {@tool dartpad --template=stateful_widget_scaffold_center}
     770             : ///
     771             : /// This sample shows a `GridRowDropdownButton` with a large arrow icon,
     772             : /// purple text style, and bold purple underline, whose value is one of "One",
     773             : /// "Two", "Free", or "Four".
     774             : ///
     775             : /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/dropdown_button.png)
     776             : ///
     777             : /// ```dart
     778             : /// String dropdownValue = 'One';
     779             : ///
     780             : /// @override
     781             : /// Widget build(BuildContext context) {
     782             : ///   return GridRowDropdownButton<String>(
     783             : ///     value: dropdownValue,
     784             : ///     icon: const Icon(Icons.arrow_downward),
     785             : ///     iconSize: 24,
     786             : ///     elevation: 16,
     787             : ///     style: const TextStyle(
     788             : ///       color: Colors.deepPurple
     789             : ///     ),
     790             : ///     underline: Container(
     791             : ///       height: 2,
     792             : ///       color: Colors.deepPurpleAccent,
     793             : ///     ),
     794             : ///     onChanged: (String? newValue) {
     795             : ///       setState(() {
     796             : ///         dropdownValue = newValue!;
     797             : ///       });
     798             : ///     },
     799             : ///     items: <String>['One', 'Two', 'Free', 'Four']
     800             : ///       .map<GridRowDropdownMenuItem<String>>((String value) {
     801             : ///         return GridRowDropdownMenuItem<String>(
     802             : ///           value: value,
     803             : ///           child: Text(value),
     804             : ///         );
     805             : ///       })
     806             : ///       .toList(),
     807             : ///   );
     808             : /// }
     809             : /// ```
     810             : /// {@end-tool}
     811             : ///
     812             : /// If the [onChanged] callback is null or the list of [items] is null
     813             : /// then the dropdown button will be disabled, i.e. its arrow will be
     814             : /// displayed in grey and it will not respond to input. A disabled button
     815             : /// will display the [disabledHint] widget if it is non-null. However, if
     816             : /// [disabledHint] is null and [hint] is non-null, the [hint] widget will
     817             : /// instead be displayed.
     818             : ///
     819             : /// Requires one of its ancestors to be a [Material] widget.
     820             : ///
     821             : /// See also:
     822             : ///
     823             : ///  * [GridRowDropdownMenuItem], the class used to represent the [items].
     824             : ///  * [GridRowDropdownButtonHideUnderline], which prevents its descendant dropdown buttons
     825             : ///    from displaying their underlines.
     826             : ///  * [ElevatedButton], [TextButton], ordinary buttons that trigger a single action.
     827             : ///  * <https://material.io/design/components/menus.html#dropdown-menu>
     828             : class GridRowDropdownButton<T> extends StatefulWidget {
     829             :   /// Creates a dropdown button.
     830             :   ///
     831             :   /// The [items] must have distinct values. If [value] isn't null then it
     832             :   /// must be equal to one of the [GridRowDropdownMenuItem] values. If [items] or
     833             :   /// [onChanged] is null, the button will be disabled, the down arrow
     834             :   /// will be greyed out.
     835             :   ///
     836             :   /// If [value] is null and the button is enabled, [hint] will be displayed
     837             :   /// if it is non-null.
     838             :   ///
     839             :   /// If [value] is null and the button is disabled, [disabledHint] will be displayed
     840             :   /// if it is non-null. If [disabledHint] is null, then [hint] will be displayed
     841             :   /// if it is non-null.
     842             :   ///
     843             :   /// The [elevation] and [iconSize] arguments must not be null (they both have
     844             :   /// defaults, so do not need to be specified). The boolean [isDense] and
     845             :   /// [isExpanded] arguments must not be null.
     846             :   ///
     847             :   /// The [autofocus] argument must not be null.
     848             :   ///
     849             :   /// The [dropdownColor] argument specifies the background color of the
     850             :   /// dropdown when it is open. If it is null, the current theme's
     851             :   /// [ThemeData.canvasColor] will be used instead.
     852           0 :   GridRowDropdownButton({
     853             :     Key? key,
     854             :     required this.items,
     855             :     this.selectedItemBuilder,
     856             :     this.value,
     857             :     this.hint,
     858             :     this.disabledHint,
     859             :     this.onChanged,
     860             :     this.onTap,
     861             :     this.elevation = 8,
     862             :     this.style,
     863             :     this.underline,
     864             :     this.icon,
     865             :     this.iconDisabledColor,
     866             :     this.iconEnabledColor,
     867             :     this.iconSize = 24.0,
     868             :     this.isDense = false,
     869             :     this.isExpanded = false,
     870             :     this.itemHeight = kMinInteractiveDimension,
     871             :     this.focusColor,
     872             :     this.focusNode,
     873             :     this.autofocus = false,
     874             :     this.dropdownColor,
     875             :     this.menuMaxHeight,
     876             :     this.enableFeedback,
     877             :     this.alignment = AlignmentDirectional.centerStart,
     878             :     // When adding new arguments, consider adding similar arguments to
     879             :     // GridRowDropdownButtonFormField.
     880           0 :   }) : assert(items == null || items.isEmpty || value == null ||
     881           0 :       items.where((GridRowDropdownMenuItem<T> item) {
     882           0 :         return item.value == value;
     883           0 :       }).length == 1,
     884             :   "There should be exactly one item with [GridRowDropdownButton]'s value: "
     885             :       '$value. \n'
     886             :       'Either zero or 2 or more [GridRowDropdownMenuItem]s were detected '
     887             :       'with the same value',
     888             :   ),
     889           0 :         assert(elevation != null),
     890           0 :         assert(iconSize != null),
     891           0 :         assert(isDense != null),
     892           0 :         assert(isExpanded != null),
     893           0 :         assert(autofocus != null),
     894           0 :         assert(itemHeight == null || itemHeight >=  kMinInteractiveDimension),
     895           0 :         super(key: key);
     896             : 
     897             :   /// The list of items the user can select.
     898             :   ///
     899             :   /// If the [onChanged] callback is null or the list of items is null
     900             :   /// then the dropdown button will be disabled, i.e. its arrow will be
     901             :   /// displayed in grey and it will not respond to input.
     902             :   final List<GridRowDropdownMenuItem<T>>? items;
     903             : 
     904             :   /// The value of the currently selected [GridRowDropdownMenuItem].
     905             :   ///
     906             :   /// If [value] is null and the button is enabled, [hint] will be displayed
     907             :   /// if it is non-null.
     908             :   ///
     909             :   /// If [value] is null and the button is disabled, [disabledHint] will be displayed
     910             :   /// if it is non-null. If [disabledHint] is null, then [hint] will be displayed
     911             :   /// if it is non-null.
     912             :   final T? value;
     913             : 
     914             :   /// A placeholder widget that is displayed by the dropdown button.
     915             :   ///
     916             :   /// If [value] is null and the dropdown is enabled ([items] and [onChanged] are non-null),
     917             :   /// this widget is displayed as a placeholder for the dropdown button's value.
     918             :   ///
     919             :   /// If [value] is null and the dropdown is disabled and [disabledHint] is null,
     920             :   /// this widget is used as the placeholder.
     921             :   final Widget? hint;
     922             : 
     923             :   /// A preferred placeholder widget that is displayed when the dropdown is disabled.
     924             :   ///
     925             :   /// If [value] is null, the dropdown is disabled ([items] or [onChanged] is null),
     926             :   /// this widget is displayed as a placeholder for the dropdown button's value.
     927             :   final Widget? disabledHint;
     928             : 
     929             :   /// {@template flutter.material.dropdownButton.onChanged}
     930             :   /// Called when the user selects an item.
     931             :   ///
     932             :   /// If the [onChanged] callback is null or the list of [GridRowDropdownButton.items]
     933             :   /// is null then the dropdown button will be disabled, i.e. its arrow will be
     934             :   /// displayed in grey and it will not respond to input. A disabled button
     935             :   /// will display the [GridRowDropdownButton.disabledHint] widget if it is non-null.
     936             :   /// If [GridRowDropdownButton.disabledHint] is also null but [GridRowDropdownButton.hint] is
     937             :   /// non-null, [GridRowDropdownButton.hint] will instead be displayed.
     938             :   /// {@endtemplate}
     939             :   final ValueChanged<T?>? onChanged;
     940             : 
     941             :   /// Called when the dropdown button is tapped.
     942             :   ///
     943             :   /// This is distinct from [onChanged], which is called when the user
     944             :   /// selects an item from the dropdown.
     945             :   ///
     946             :   /// The callback will not be invoked if the dropdown button is disabled.
     947             :   final VoidCallback? onTap;
     948             : 
     949             :   /// A builder to customize the dropdown buttons corresponding to the
     950             :   /// [GridRowDropdownMenuItem]s in [items].
     951             :   ///
     952             :   /// When a [GridRowDropdownMenuItem] is selected, the widget that will be displayed
     953             :   /// from the list corresponds to the [GridRowDropdownMenuItem] of the same index
     954             :   /// in [items].
     955             :   ///
     956             :   /// {@tool dartpad --template=stateful_widget_scaffold}
     957             :   ///
     958             :   /// This sample shows a `GridRowDropdownButton` with a button with [Text] that
     959             :   /// corresponds to but is unique from [GridRowDropdownMenuItem].
     960             :   ///
     961             :   /// ```dart
     962             :   /// final List<String> items = <String>['1','2','3'];
     963             :   /// String selectedItem = '1';
     964             :   ///
     965             :   /// @override
     966             :   /// Widget build(BuildContext context) {
     967             :   ///   return Padding(
     968             :   ///     padding: const EdgeInsets.symmetric(horizontal: 12.0),
     969             :   ///     child: GridRowDropdownButton<String>(
     970             :   ///       value: selectedItem,
     971             :   ///       onChanged: (String? string) => setState(() => selectedItem = string!),
     972             :   ///       selectedItemBuilder: (BuildContext context) {
     973             :   ///         return items.map<Widget>((String item) {
     974             :   ///           return Text(item);
     975             :   ///         }).toList();
     976             :   ///       },
     977             :   ///       items: items.map((String item) {
     978             :   ///         return GridRowDropdownMenuItem<String>(
     979             :   ///           child: Text('Log $item'),
     980             :   ///           value: item,
     981             :   ///         );
     982             :   ///       }).toList(),
     983             :   ///     ),
     984             :   ///   );
     985             :   /// }
     986             :   /// ```
     987             :   /// {@end-tool}
     988             :   ///
     989             :   /// If this callback is null, the [GridRowDropdownMenuItem] from [items]
     990             :   /// that matches [value] will be displayed.
     991             :   final GridRowDropdownButtonBuilder? selectedItemBuilder;
     992             : 
     993             :   /// The z-coordinate at which to place the menu when open.
     994             :   ///
     995             :   /// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12,
     996             :   /// 16, and 24. See [kElevationToShadow].
     997             :   ///
     998             :   /// Defaults to 8, the appropriate elevation for dropdown buttons.
     999             :   final int elevation;
    1000             : 
    1001             :   /// The text style to use for text in the dropdown button and the dropdown
    1002             :   /// menu that appears when you tap the button.
    1003             :   ///
    1004             :   /// To use a separate text style for selected item when it's displayed within
    1005             :   /// the dropdown button, consider using [selectedItemBuilder].
    1006             :   ///
    1007             :   /// {@tool dartpad --template=stateful_widget_scaffold}
    1008             :   ///
    1009             :   /// This sample shows a `GridRowDropdownButton` with a dropdown button text style
    1010             :   /// that is different than its menu items.
    1011             :   ///
    1012             :   /// ```dart
    1013             :   /// List<String> options = <String>['One', 'Two', 'Free', 'Four'];
    1014             :   /// String dropdownValue = 'One';
    1015             :   ///
    1016             :   /// @override
    1017             :   /// Widget build(BuildContext context) {
    1018             :   ///   return Container(
    1019             :   ///     alignment: Alignment.center,
    1020             :   ///     color: Colors.blue,
    1021             :   ///     child: GridRowDropdownButton<String>(
    1022             :   ///       value: dropdownValue,
    1023             :   ///       onChanged: (String? newValue) {
    1024             :   ///         setState(() {
    1025             :   ///           dropdownValue = newValue!;
    1026             :   ///         });
    1027             :   ///       },
    1028             :   ///       style: const TextStyle(color: Colors.blue),
    1029             :   ///       selectedItemBuilder: (BuildContext context) {
    1030             :   ///         return options.map((String value) {
    1031             :   ///           return Text(
    1032             :   ///             dropdownValue,
    1033             :   ///             style: const TextStyle(color: Colors.white),
    1034             :   ///           );
    1035             :   ///         }).toList();
    1036             :   ///       },
    1037             :   ///       items: options.map<GridRowDropdownMenuItem<String>>((String value) {
    1038             :   ///         return GridRowDropdownMenuItem<String>(
    1039             :   ///           value: value,
    1040             :   ///           child: Text(value),
    1041             :   ///         );
    1042             :   ///       }).toList(),
    1043             :   ///     ),
    1044             :   ///   );
    1045             :   /// }
    1046             :   /// ```
    1047             :   /// {@end-tool}
    1048             :   ///
    1049             :   /// Defaults to the [TextTheme.subtitle1] value of the current
    1050             :   /// [ThemeData.textTheme] of the current [Theme].
    1051             :   final TextStyle? style;
    1052             : 
    1053             :   /// The widget to use for drawing the drop-down button's underline.
    1054             :   ///
    1055             :   /// Defaults to a 0.0 width bottom border with color 0xFFBDBDBD.
    1056             :   final Widget? underline;
    1057             : 
    1058             :   /// The widget to use for the drop-down button's icon.
    1059             :   ///
    1060             :   /// Defaults to an [Icon] with the [Icons.arrow_drop_down] glyph.
    1061             :   final Widget? icon;
    1062             : 
    1063             :   /// The color of any [Icon] descendant of [icon] if this button is disabled,
    1064             :   /// i.e. if [onChanged] is null.
    1065             :   ///
    1066             :   /// Defaults to [MaterialColor.shade400] of [Colors.grey] when the theme's
    1067             :   /// [ThemeData.brightness] is [Brightness.light] and to
    1068             :   /// [Colors.white10] when it is [Brightness.dark]
    1069             :   final Color? iconDisabledColor;
    1070             : 
    1071             :   /// The color of any [Icon] descendant of [icon] if this button is enabled,
    1072             :   /// i.e. if [onChanged] is defined.
    1073             :   ///
    1074             :   /// Defaults to [MaterialColor.shade700] of [Colors.grey] when the theme's
    1075             :   /// [ThemeData.brightness] is [Brightness.light] and to
    1076             :   /// [Colors.white70] when it is [Brightness.dark]
    1077             :   final Color? iconEnabledColor;
    1078             : 
    1079             :   /// The size to use for the drop-down button's down arrow icon button.
    1080             :   ///
    1081             :   /// Defaults to 24.0.
    1082             :   final double iconSize;
    1083             : 
    1084             :   /// Reduce the button's height.
    1085             :   ///
    1086             :   /// By default this button's height is the same as its menu items' heights.
    1087             :   /// If isDense is true, the button's height is reduced by about half. This
    1088             :   /// can be useful when the button is embedded in a container that adds
    1089             :   /// its own decorations, like [InputDecorator].
    1090             :   final bool isDense;
    1091             : 
    1092             :   /// Set the dropdown's inner contents to horizontally fill its parent.
    1093             :   ///
    1094             :   /// By default this button's inner width is the minimum size of its contents.
    1095             :   /// If [isExpanded] is true, the inner width is expanded to fill its
    1096             :   /// surrounding container.
    1097             :   final bool isExpanded;
    1098             : 
    1099             :   /// If null, then the menu item heights will vary according to each menu item's
    1100             :   /// intrinsic height.
    1101             :   ///
    1102             :   /// The default value is [kMinInteractiveDimension], which is also the minimum
    1103             :   /// height for menu items.
    1104             :   ///
    1105             :   /// If this value is null and there isn't enough vertical room for the menu,
    1106             :   /// then the menu's initial scroll offset may not align the selected item with
    1107             :   /// the dropdown button. That's because, in this case, the initial scroll
    1108             :   /// offset is computed as if all of the menu item heights were
    1109             :   /// [kMinInteractiveDimension].
    1110             :   final double? itemHeight;
    1111             : 
    1112             :   /// The color for the button's [Material] when it has the input focus.
    1113             :   final Color? focusColor;
    1114             : 
    1115             :   /// {@macro flutter.widgets.Focus.focusNode}
    1116             :   final FocusNode? focusNode;
    1117             : 
    1118             :   /// {@macro flutter.widgets.Focus.autofocus}
    1119             :   final bool autofocus;
    1120             : 
    1121             :   /// The background color of the dropdown.
    1122             :   ///
    1123             :   /// If it is not provided, the theme's [ThemeData.canvasColor] will be used
    1124             :   /// instead.
    1125             :   final Color? dropdownColor;
    1126             : 
    1127             :   /// The maximum height of the menu.
    1128             :   ///
    1129             :   /// The maximum height of the menu must be at least one row shorter than
    1130             :   /// the height of the app's view. This ensures that a tappable area
    1131             :   /// outside of the simple menu is present so the user can dismiss the menu.
    1132             :   ///
    1133             :   /// If this property is set above the maximum allowable height threshold
    1134             :   /// mentioned above, then the menu defaults to being padded at the top
    1135             :   /// and bottom of the menu by at one menu item's height.
    1136             :   final double? menuMaxHeight;
    1137             : 
    1138             :   /// Whether detected gestures should provide acoustic and/or haptic feedback.
    1139             :   ///
    1140             :   /// For example, on Android a tap will produce a clicking sound and a
    1141             :   /// long-press will produce a short vibration, when feedback is enabled.
    1142             :   ///
    1143             :   /// By default, platform-specific feedback is enabled.
    1144             :   ///
    1145             :   /// See also:
    1146             :   ///
    1147             :   ///  * [Feedback] for providing platform-specific feedback to certain actions.
    1148             :   final bool? enableFeedback;
    1149             : 
    1150             :   /// Defines how the hint or the selected item is positioned within the button.
    1151             :   ///
    1152             :   /// This property must not be null. It defaults to [AlignmentDirectional.centerStart].
    1153             :   ///
    1154             :   /// See also:
    1155             :   ///
    1156             :   ///  * [Alignment], a class with convenient constants typically used to
    1157             :   ///    specify an [AlignmentGeometry].
    1158             :   ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
    1159             :   ///    relative to text direction.
    1160             :   final AlignmentGeometry alignment;
    1161             : 
    1162           0 :   @override
    1163           0 :   State<GridRowDropdownButton<T>> createState() => _GridRowDropdownButtonState<T>();
    1164             : }
    1165             : 
    1166             : class _GridRowDropdownButtonState<T> extends State<GridRowDropdownButton<T>> with WidgetsBindingObserver {
    1167             :   int? _selectedIndex;
    1168             :   _GridRowDropdownRoute<T>? _dropdownRoute;
    1169             :   Orientation? _lastOrientation;
    1170             :   FocusNode? _internalNode;
    1171           0 :   FocusNode? get focusNode => widget.focusNode ?? _internalNode;
    1172             :   bool _hasPrimaryFocus = false;
    1173             :   late Map<Type, Action<Intent>> _actionMap;
    1174             :   late FocusHighlightMode _focusHighlightMode;
    1175             : 
    1176             :   // Only used if needed to create _internalNode.
    1177           0 :   FocusNode _createFocusNode() {
    1178           0 :     return FocusNode(debugLabel: '${widget.runtimeType}');
    1179             :   }
    1180             : 
    1181           0 :   @override
    1182             :   void initState() {
    1183           0 :     super.initState();
    1184           0 :     _updateSelectedIndex();
    1185           0 :     if (widget.focusNode == null) {
    1186           0 :       _internalNode ??= _createFocusNode();
    1187             :     }
    1188           0 :     _actionMap = <Type, Action<Intent>>{
    1189           0 :       ActivateIntent: CallbackAction<ActivateIntent>(
    1190           0 :         onInvoke: (ActivateIntent intent) => _handleTap(),
    1191             :       ),
    1192           0 :       ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(
    1193           0 :         onInvoke: (ButtonActivateIntent intent) => _handleTap(),
    1194             :       ),
    1195             :     };
    1196           0 :     focusNode!.addListener(_handleFocusChanged);
    1197           0 :     final FocusManager focusManager = WidgetsBinding.instance!.focusManager;
    1198           0 :     _focusHighlightMode = focusManager.highlightMode;
    1199           0 :     focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
    1200             :   }
    1201             : 
    1202           0 :   @override
    1203             :   void dispose() {
    1204           0 :     WidgetsBinding.instance!.removeObserver(this);
    1205           0 :     _removeGridRowDropdownRoute();
    1206           0 :     WidgetsBinding.instance!.focusManager.removeHighlightModeListener(_handleFocusHighlightModeChange);
    1207           0 :     focusNode!.removeListener(_handleFocusChanged);
    1208           0 :     _internalNode?.dispose();
    1209           0 :     super.dispose();
    1210             :   }
    1211             : 
    1212           0 :   void _removeGridRowDropdownRoute() {
    1213           0 :     _dropdownRoute?._dismiss();
    1214           0 :     _dropdownRoute = null;
    1215           0 :     _lastOrientation = null;
    1216             :   }
    1217             : 
    1218           0 :   void _handleFocusChanged() {
    1219           0 :     if (_hasPrimaryFocus != focusNode!.hasPrimaryFocus) {
    1220           0 :       setState(() {
    1221           0 :         _hasPrimaryFocus = focusNode!.hasPrimaryFocus;
    1222             :       });
    1223             :     }
    1224             :   }
    1225             : 
    1226           0 :   void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
    1227           0 :     if (!mounted) {
    1228             :       return;
    1229             :     }
    1230           0 :     setState(() {
    1231           0 :       _focusHighlightMode = mode;
    1232             :     });
    1233             :   }
    1234             : 
    1235           0 :   @override
    1236             :   void didUpdateWidget(GridRowDropdownButton<T> oldWidget) {
    1237           0 :     super.didUpdateWidget(oldWidget);
    1238           0 :     if (widget.focusNode != oldWidget.focusNode) {
    1239           0 :       oldWidget.focusNode?.removeListener(_handleFocusChanged);
    1240           0 :       if (widget.focusNode == null) {
    1241           0 :         _internalNode ??= _createFocusNode();
    1242             :       }
    1243           0 :       _hasPrimaryFocus = focusNode!.hasPrimaryFocus;
    1244           0 :       focusNode!.addListener(_handleFocusChanged);
    1245             :     }
    1246           0 :     _updateSelectedIndex();
    1247             :   }
    1248             : 
    1249           0 :   void _updateSelectedIndex() {
    1250           0 :     if (widget.items == null
    1251           0 :         || widget.items!.isEmpty
    1252           0 :         || (widget.value == null &&
    1253           0 :             widget.items!
    1254           0 :                 .where((GridRowDropdownMenuItem<T> item) => item.enabled && item.value == widget.value)
    1255           0 :                 .isEmpty)) {
    1256           0 :       _selectedIndex = null;
    1257             :       return;
    1258             :     }
    1259             : 
    1260           0 :     assert(widget.items!.where((GridRowDropdownMenuItem<T> item) => item.value == widget.value).length == 1);
    1261           0 :     for (int itemIndex = 0; itemIndex < widget.items!.length; itemIndex++) {
    1262           0 :       if (widget.items![itemIndex].value == widget.value) {
    1263           0 :         _selectedIndex = itemIndex;
    1264             :         return;
    1265             :       }
    1266             :     }
    1267             :   }
    1268             : 
    1269           0 :   TextStyle? get _textStyle => widget.style ?? Theme.of(context).textTheme.subtitle1;
    1270             : 
    1271           0 :   void _handleTap() {
    1272           0 :     final TextDirection? textDirection = Directionality.maybeOf(context);
    1273           0 :     final EdgeInsetsGeometry menuMargin = ButtonTheme.of(context).alignedDropdown
    1274             :         ? _kAlignedMenuMargin
    1275             :         : _kUnalignedMenuMargin;
    1276             : 
    1277           0 :     final List<_MenuItem<T>> menuItems = <_MenuItem<T>>[
    1278           0 :       for (int index = 0; index < widget.items!.length; index += 1)
    1279           0 :         _MenuItem<T>(
    1280           0 :           item: widget.items![index],
    1281           0 :           onLayout: (Size size) {
    1282             :             // If [_dropdownRoute] is null and onLayout is called, this means
    1283             :             // that performLayout was called on a _GridRowDropdownRoute that has not
    1284             :             // left the widget tree but is already on its way out.
    1285             :             //
    1286             :             // Since onLayout is used primarily to collect the desired heights
    1287             :             // of each menu item before laying them out, not having the _GridRowDropdownRoute
    1288             :             // collect each item's height to lay out is fine since the route is
    1289             :             // already on its way out.
    1290           0 :             if (_dropdownRoute == null)
    1291             :               return;
    1292             : 
    1293           0 :             _dropdownRoute!.itemHeights[index] = size.height;
    1294             :           },
    1295             :         ),
    1296             :     ];
    1297             : 
    1298           0 :     final NavigatorState navigator = Navigator.of(context);
    1299           0 :     assert(_dropdownRoute == null);
    1300           0 :     final RenderBox itemBox = context.findRenderObject()! as RenderBox;
    1301           0 :     final Rect itemRect = itemBox.localToGlobal(Offset.zero, ancestor: navigator.context.findRenderObject()) & itemBox.size;
    1302           0 :     _dropdownRoute = _GridRowDropdownRoute<T>(
    1303             :       items: menuItems,
    1304           0 :       buttonRect: menuMargin.resolve(textDirection).inflateRect(itemRect),
    1305           0 :       padding: _kMenuItemPadding.resolve(textDirection),
    1306           0 :       selectedIndex: _selectedIndex ?? 0,
    1307           0 :       elevation: widget.elevation,
    1308           0 :       capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
    1309           0 :       style: _textStyle!,
    1310           0 :       barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
    1311           0 :       itemHeight: widget.itemHeight,
    1312           0 :       dropdownColor: widget.dropdownColor,
    1313           0 :       menuMaxHeight: widget.menuMaxHeight,
    1314           0 :       enableFeedback: widget.enableFeedback ?? true,
    1315             :     );
    1316             : 
    1317           0 :     navigator.push(_dropdownRoute!).then<void>((_GridRowDropdownRouteResult<T>? newValue) {
    1318           0 :       _removeGridRowDropdownRoute();
    1319           0 :       if (!mounted || newValue == null)
    1320             :         return;
    1321           0 :       widget.onChanged?.call(newValue.result);
    1322             :     });
    1323             : 
    1324           0 :     widget.onTap?.call();
    1325             :   }
    1326             : 
    1327             :   // When isDense is true, reduce the height of this button from _kMenuItemHeight to
    1328             :   // _kDenseButtonHeight, but don't make it smaller than the text that it contains.
    1329             :   // Similarly, we don't reduce the height of the button so much that its icon
    1330             :   // would be clipped.
    1331           0 :   double get _denseButtonHeight {
    1332           0 :     final double fontSize = _textStyle!.fontSize ?? Theme.of(context).textTheme.subtitle1!.fontSize!;
    1333           0 :     return math.max(fontSize, math.max(widget.iconSize, _kDenseButtonHeight));
    1334             :   }
    1335             : 
    1336           0 :   Color get _iconColor {
    1337             :     // These colors are not defined in the Material Design spec.
    1338           0 :     if (_enabled) {
    1339           0 :       if (widget.iconEnabledColor != null)
    1340           0 :         return widget.iconEnabledColor!;
    1341             : 
    1342           0 :       switch (Theme.of(context).brightness) {
    1343           0 :         case Brightness.light:
    1344           0 :           return Colors.grey.shade700;
    1345           0 :         case Brightness.dark:
    1346             :           return Colors.white70;
    1347             :       }
    1348             :     } else {
    1349           0 :       if (widget.iconDisabledColor != null)
    1350           0 :         return widget.iconDisabledColor!;
    1351             : 
    1352           0 :       switch (Theme.of(context).brightness) {
    1353           0 :         case Brightness.light:
    1354           0 :           return Colors.grey.shade400;
    1355           0 :         case Brightness.dark:
    1356             :           return Colors.white10;
    1357             :       }
    1358             :     }
    1359             :   }
    1360             : 
    1361           0 :   bool get _enabled => widget.items != null && widget.items!.isNotEmpty && widget.onChanged != null;
    1362             : 
    1363           0 :   Orientation _getOrientation(BuildContext context) {
    1364           0 :     Orientation? result = MediaQuery.maybeOf(context)?.orientation;
    1365             :     if (result == null) {
    1366             :       // If there's no MediaQuery, then use the window aspect to determine
    1367             :       // orientation.
    1368           0 :       final Size size = window.physicalSize;
    1369           0 :       result = size.width > size.height ? Orientation.landscape : Orientation.portrait;
    1370             :     }
    1371             :     return result;
    1372             :   }
    1373             : 
    1374           0 :   bool get _showHighlight {
    1375           0 :     switch (_focusHighlightMode) {
    1376           0 :       case FocusHighlightMode.touch:
    1377             :         return false;
    1378           0 :       case FocusHighlightMode.traditional:
    1379           0 :         return _hasPrimaryFocus;
    1380             :     }
    1381             :   }
    1382             : 
    1383           0 :   @override
    1384             :   Widget build(BuildContext context) {
    1385           0 :     assert(debugCheckHasMaterial(context));
    1386           0 :     assert(debugCheckHasMaterialLocalizations(context));
    1387           0 :     final Orientation newOrientation = _getOrientation(context);
    1388           0 :     _lastOrientation ??= newOrientation;
    1389           0 :     if (newOrientation != _lastOrientation) {
    1390           0 :       _removeGridRowDropdownRoute();
    1391           0 :       _lastOrientation = newOrientation;
    1392             :     }
    1393             : 
    1394             :     // The width of the button and the menu are defined by the widest
    1395             :     // item and the width of the hint.
    1396             :     // We should explicitly type the items list to be a list of <Widget>,
    1397             :     // otherwise, no explicit type adding items maybe trigger a crash/failure
    1398             :     // when hint and selectedItemBuilder are provided.
    1399           0 :     final List<Widget> items = widget.selectedItemBuilder == null
    1400           0 :         ? (widget.items != null ? List<Widget>.from(widget.items!) : <Widget>[])
    1401           0 :         : List<Widget>.from(widget.selectedItemBuilder!(context));
    1402             : 
    1403             :     int? hintIndex;
    1404           0 :     if (widget.hint != null || (!_enabled && widget.disabledHint != null)) {
    1405           0 :       Widget displayedHint = _enabled ? widget.hint! : widget.disabledHint ?? widget.hint!;
    1406           0 :       if (widget.selectedItemBuilder == null)
    1407           0 :         displayedHint = _GridRowDropdownMenuItemContainer(child: displayedHint);
    1408             : 
    1409           0 :       hintIndex = items.length;
    1410           0 :       items.add(DefaultTextStyle(
    1411           0 :         style: _textStyle!.copyWith(color: Theme.of(context).hintColor),
    1412           0 :         child: IgnorePointer(
    1413             :           ignoringSemantics: false,
    1414             :           child: displayedHint,
    1415             :         ),
    1416             :       ));
    1417             :     }
    1418             : 
    1419           0 :     final EdgeInsetsGeometry padding = ButtonTheme.of(context).alignedDropdown
    1420             :         ? _kAlignedButtonPadding
    1421             :         : _kUnalignedButtonPadding;
    1422             : 
    1423             :     // If value is null (then _selectedIndex is null) then we
    1424             :     // display the hint or nothing at all.
    1425             :     final Widget innerItemsWidget;
    1426           0 :     if (items.isEmpty) {
    1427           0 :       innerItemsWidget = Container();
    1428             :     } else {
    1429           0 :       innerItemsWidget = IndexedStack(
    1430           0 :         index: _selectedIndex ?? hintIndex,
    1431           0 :         alignment: widget.alignment,
    1432           0 :         children: widget.isDense ? items : items.map((Widget item) {
    1433           0 :           return widget.itemHeight != null
    1434           0 :               ? SizedBox(height: widget.itemHeight, child: item)
    1435           0 :               : Column(mainAxisSize: MainAxisSize.min, children: <Widget>[item]);
    1436           0 :         }).toList(),
    1437             :       );
    1438             :     }
    1439             : 
    1440             :     const Icon defaultIcon = Icon(Icons.arrow_drop_down);
    1441             : 
    1442           0 :     Widget result = DefaultTextStyle(
    1443           0 :       style: _enabled ? _textStyle! : _textStyle!.copyWith(color: Theme.of(context).disabledColor),
    1444           0 :       child: Container(
    1445           0 :         decoration: _showHighlight
    1446           0 :             ? BoxDecoration(
    1447           0 :           color: widget.focusColor ?? Theme.of(context).focusColor,
    1448             :           borderRadius: const BorderRadius.all(Radius.circular(4.0)),
    1449             :         )
    1450             :             : null,
    1451           0 :         padding: padding.resolve(Directionality.of(context)),
    1452           0 :         height: widget.isDense ? _denseButtonHeight : null,
    1453           0 :         child: Row(
    1454             :           mainAxisAlignment: MainAxisAlignment.spaceBetween,
    1455             :           mainAxisSize: MainAxisSize.min,
    1456           0 :           children: <Widget>[
    1457           0 :             if (widget.isExpanded)
    1458           0 :               Expanded(child: innerItemsWidget)
    1459             :             else
    1460           0 :               innerItemsWidget,
    1461           0 :             IconTheme(
    1462           0 :               data: IconThemeData(
    1463           0 :                 color: _iconColor,
    1464           0 :                 size: widget.iconSize,
    1465             :               ),
    1466           0 :               child: widget.icon ?? defaultIcon,
    1467             :             ),
    1468             :           ],
    1469             :         ),
    1470             :       ),
    1471             :     );
    1472             : 
    1473           0 :     if (!GridRowDropdownButtonHideUnderline.at(context)) {
    1474           0 :       final double bottom = (widget.isDense || widget.itemHeight == null) ? 0.0 : 8.0;
    1475           0 :       result = Stack(
    1476           0 :         children: <Widget>[
    1477             :           result,
    1478           0 :           Positioned(
    1479             :             left: 0.0,
    1480             :             right: 0.0,
    1481             :             bottom: bottom,
    1482           0 :             child: widget.underline ?? Container(
    1483             :               height: 1.0,
    1484             :               decoration: const BoxDecoration(
    1485             :                 border: Border(
    1486             :                   bottom: BorderSide(
    1487             :                     color: Color(0xFFBDBDBD),
    1488             :                     width: 0.0,
    1489             :                   ),
    1490             :                 ),
    1491             :               ),
    1492             :             ),
    1493             :           ),
    1494             :         ],
    1495             :       );
    1496             :     }
    1497             : 
    1498           0 :     final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
    1499             :       MaterialStateMouseCursor.clickable,
    1500             :       <MaterialState>{
    1501           0 :         if (!_enabled) MaterialState.disabled,
    1502             :       },
    1503             :     );
    1504             : 
    1505           0 :     return Semantics(
    1506             :       button: true,
    1507           0 :       child: Actions(
    1508           0 :         actions: _actionMap,
    1509           0 :         child: Focus(
    1510           0 :           canRequestFocus: _enabled,
    1511           0 :           focusNode: focusNode,
    1512           0 :           autofocus: widget.autofocus,
    1513           0 :           child: MouseRegion(
    1514             :             cursor: effectiveMouseCursor,
    1515           0 :             child: GestureDetector(
    1516           0 :               onTap: _enabled ? _handleTap : null,
    1517             :               behavior: HitTestBehavior.opaque,
    1518             :               child: result,
    1519             :             ),
    1520             :           ),
    1521             :         ),
    1522             :       ),
    1523             :     );
    1524             :   }
    1525             : }
    1526             : 
    1527             : /// A convenience widget that makes a [GridRowDropdownButton] into a [FormField].
    1528             : class GridRowDropdownButtonFormField<T> extends FormField<T> {
    1529             :   /// Creates a [GridRowDropdownButton] widget that is a [FormField], wrapped in an
    1530             :   /// [InputDecorator].
    1531             :   ///
    1532             :   /// For a description of the `onSaved`, `validator`, or `autovalidateMode`
    1533             :   /// parameters, see [FormField]. For the rest (other than [decoration]), see
    1534             :   /// [GridRowDropdownButton].
    1535             :   ///
    1536             :   /// The `items`, `elevation`, `iconSize`, `isDense`, `isExpanded`,
    1537             :   /// `autofocus`, and `decoration`  parameters must not be null.
    1538           0 :   GridRowDropdownButtonFormField({
    1539             :     Key? key,
    1540             :     required List<GridRowDropdownMenuItem<T>>? items,
    1541             :     GridRowDropdownButtonBuilder? selectedItemBuilder,
    1542             :     T? value,
    1543             :     Widget? hint,
    1544             :     Widget? disabledHint,
    1545             :     this.onChanged,
    1546             :     VoidCallback? onTap,
    1547             :     int elevation = 8,
    1548             :     TextStyle? style,
    1549             :     Widget? icon,
    1550             :     Color? iconDisabledColor,
    1551             :     Color? iconEnabledColor,
    1552             :     double iconSize = 24.0,
    1553             :     bool isDense = true,
    1554             :     bool isExpanded = false,
    1555             :     double? itemHeight,
    1556             :     Color? focusColor,
    1557             :     FocusNode? focusNode,
    1558             :     bool autofocus = false,
    1559             :     Color? dropdownColor,
    1560             :     InputDecoration? decoration,
    1561             :     FormFieldSetter<T>? onSaved,
    1562             :     FormFieldValidator<T>? validator,
    1563             :     @Deprecated(
    1564             :       'Use autovalidateMode parameter which provide more specific '
    1565             :           'behaviour related to auto validation. '
    1566             :           'This feature was deprecated after v1.19.0.',
    1567             :     )
    1568             :     bool autovalidate = false,
    1569             :     AutovalidateMode? autovalidateMode,
    1570             :     double? menuMaxHeight,
    1571             :     bool? enableFeedback,
    1572             :     AlignmentGeometry alignment = AlignmentDirectional.centerStart,
    1573           0 :   }) : assert(items == null || items.isEmpty || value == null ||
    1574           0 :       items.where((GridRowDropdownMenuItem<T> item) {
    1575           0 :         return item.value == value;
    1576           0 :       }).length == 1,
    1577             :   "There should be exactly one item with [GridRowDropdownButton]'s value: "
    1578             :       '$value. \n'
    1579             :       'Either zero or 2 or more [GridRowDropdownMenuItem]s were detected '
    1580             :       'with the same value',
    1581             :   ),
    1582           0 :         assert(elevation != null),
    1583           0 :         assert(iconSize != null),
    1584           0 :         assert(isDense != null),
    1585           0 :         assert(isExpanded != null),
    1586           0 :         assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
    1587           0 :         assert(autofocus != null),
    1588           0 :         assert(autovalidate != null),
    1589             :         assert(
    1590           0 :         autovalidate == false ||
    1591           0 :             autovalidate == true && autovalidateMode == null,
    1592             :         'autovalidate and autovalidateMode should not be used together.',
    1593             :         ),
    1594           0 :         decoration = decoration ?? InputDecoration(focusColor: focusColor),
    1595           0 :         super(
    1596             :         key: key,
    1597             :         onSaved: onSaved,
    1598             :         initialValue: value,
    1599             :         validator: validator,
    1600             :         autovalidateMode: autovalidate
    1601             :             ? AutovalidateMode.always
    1602             :             : (autovalidateMode ?? AutovalidateMode.disabled),
    1603           0 :         builder: (FormFieldState<T> field) {
    1604             :           final _GridRowDropdownButtonFormFieldState<T> state = field as _GridRowDropdownButtonFormFieldState<T>;
    1605           0 :           final InputDecoration decorationArg =  decoration ?? InputDecoration(focusColor: focusColor);
    1606           0 :           final InputDecoration effectiveDecoration = decorationArg.applyDefaults(
    1607           0 :             Theme.of(field.context).inputDecorationTheme,
    1608             :           );
    1609             :           // An unfocusable Focus widget so that this widget can detect if its
    1610             :           // descendants have focus or not.
    1611           0 :           return Focus(
    1612             :             canRequestFocus: false,
    1613             :             skipTraversal: true,
    1614           0 :             child: Builder(builder: (BuildContext context) {
    1615           0 :               return InputDecorator(
    1616           0 :                 decoration: effectiveDecoration.copyWith(errorText: field.errorText),
    1617           0 :                 isEmpty: state.value == null,
    1618           0 :                 isFocused: Focus.of(context).hasFocus,
    1619           0 :                 child: GridRowDropdownButtonHideUnderline(
    1620           0 :                   child: GridRowDropdownButton<T>(
    1621             :                     items: items,
    1622             :                     selectedItemBuilder: selectedItemBuilder,
    1623           0 :                     value: state.value,
    1624             :                     hint: hint,
    1625             :                     disabledHint: disabledHint,
    1626           0 :                     onChanged: onChanged == null ? null : state.didChange,
    1627             :                     onTap: onTap,
    1628             :                     elevation: elevation,
    1629             :                     style: style,
    1630             :                     icon: icon,
    1631             :                     iconDisabledColor: iconDisabledColor,
    1632             :                     iconEnabledColor: iconEnabledColor,
    1633             :                     iconSize: iconSize,
    1634             :                     isDense: isDense,
    1635             :                     isExpanded: isExpanded,
    1636             :                     itemHeight: itemHeight,
    1637             :                     focusColor: focusColor,
    1638             :                     focusNode: focusNode,
    1639             :                     autofocus: autofocus,
    1640             :                     dropdownColor: dropdownColor,
    1641             :                     menuMaxHeight: menuMaxHeight,
    1642             :                     enableFeedback: enableFeedback,
    1643             :                     alignment: alignment,
    1644             :                   ),
    1645             :                 ),
    1646             :               );
    1647             :             }),
    1648             :           );
    1649             :         },
    1650             :       );
    1651             : 
    1652             :   /// {@macro flutter.material.dropdownButton.onChanged}
    1653             :   final ValueChanged<T?>? onChanged;
    1654             : 
    1655             :   /// The decoration to show around the dropdown button form field.
    1656             :   ///
    1657             :   /// By default, draws a horizontal line under the dropdown button field but
    1658             :   /// can be configured to show an icon, label, hint text, and error text.
    1659             :   ///
    1660             :   /// If not specified, an [InputDecorator] with the `focusColor` set to the
    1661             :   /// supplied `focusColor` (if any) will be used.
    1662             :   final InputDecoration decoration;
    1663             : 
    1664           0 :   @override
    1665           0 :   FormFieldState<T> createState() => _GridRowDropdownButtonFormFieldState<T>();
    1666             : }
    1667             : 
    1668             : class _GridRowDropdownButtonFormFieldState<T> extends FormFieldState<T> {
    1669           0 :   @override
    1670           0 :   GridRowDropdownButtonFormField<T> get widget => super.widget as GridRowDropdownButtonFormField<T>;
    1671             : 
    1672           0 :   @override
    1673             :   void didChange(T? value) {
    1674           0 :     super.didChange(value);
    1675           0 :     assert(widget.onChanged != null);
    1676           0 :     widget.onChanged!(value);
    1677             :   }
    1678             : 
    1679           0 :   @override
    1680             :   void didUpdateWidget(GridRowDropdownButtonFormField<T> oldWidget) {
    1681           0 :     super.didUpdateWidget(oldWidget);
    1682           0 :     if (oldWidget.initialValue != widget.initialValue) {
    1683           0 :       setValue(widget.initialValue);
    1684             :     }
    1685             :   }
    1686             : }

Generated by: LCOV version 1.15