"Fossies" - the Fresh Open Source Software Archive

Member "flutter-3.7.0/packages/flutter/lib/src/material/about.dart" (24 Jan 2023, 50408 Bytes) of package /linux/misc/flutter-3.7.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "about.dart": 3.3.10_vs_3.7.0.

    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 import 'dart:developer' show Flow, Timeline;
    6 import 'dart:io' show Platform;
    7 
    8 import 'package:flutter/foundation.dart';
    9 import 'package:flutter/scheduler.dart';
   10 import 'package:flutter/widgets.dart' hide Flow;
   11 
   12 import 'app_bar.dart';
   13 import 'back_button.dart';
   14 import 'card.dart';
   15 import 'constants.dart';
   16 import 'debug.dart';
   17 import 'dialog.dart';
   18 import 'divider.dart';
   19 import 'floating_action_button_location.dart';
   20 import 'ink_decoration.dart';
   21 import 'list_tile.dart';
   22 import 'material.dart';
   23 import 'material_localizations.dart';
   24 import 'page.dart';
   25 import 'page_transitions_theme.dart';
   26 import 'progress_indicator.dart';
   27 import 'scaffold.dart';
   28 import 'scrollbar.dart';
   29 import 'text_button.dart';
   30 import 'text_theme.dart';
   31 import 'theme.dart';
   32 
   33 // Examples can assume:
   34 // BuildContext context;
   35 
   36 /// A [ListTile] that shows an about box.
   37 ///
   38 /// This widget is often added to an app's [Drawer]. When tapped it shows
   39 /// an about box dialog with [showAboutDialog].
   40 ///
   41 /// The about box will include a button that shows licenses for software used by
   42 /// the application. The licenses shown are those returned by the
   43 /// [LicenseRegistry] API, which can be used to add more licenses to the list.
   44 ///
   45 /// If your application does not have a [Drawer], you should provide an
   46 /// affordance to call [showAboutDialog] or (at least) [showLicensePage].
   47 ///
   48 /// {@tool dartpad}
   49 /// This sample shows two ways to open [AboutDialog]. The first one
   50 /// uses an [AboutListTile], and the second uses the [showAboutDialog] function.
   51 ///
   52 /// ** See code in examples/api/lib/material/about/about_list_tile.0.dart **
   53 /// {@end-tool}
   54 class AboutListTile extends StatelessWidget {
   55   /// Creates a list tile for showing an about box.
   56   ///
   57   /// The arguments are all optional. The application name, if omitted, will be
   58   /// derived from the nearest [Title] widget. The version, icon, and legalese
   59   /// values default to the empty string.
   60   const AboutListTile({
   61     super.key,
   62     this.icon,
   63     this.child,
   64     this.applicationName,
   65     this.applicationVersion,
   66     this.applicationIcon,
   67     this.applicationLegalese,
   68     this.aboutBoxChildren,
   69     this.dense,
   70   });
   71 
   72   /// The icon to show for this drawer item.
   73   ///
   74   /// By default no icon is shown.
   75   ///
   76   /// This is not necessarily the same as the image shown in the dialog box
   77   /// itself; which is controlled by the [applicationIcon] property.
   78   final Widget? icon;
   79 
   80   /// The label to show on this drawer item.
   81   ///
   82   /// Defaults to a text widget that says "About Foo" where "Foo" is the
   83   /// application name specified by [applicationName].
   84   final Widget? child;
   85 
   86   /// The name of the application.
   87   ///
   88   /// This string is used in the default label for this drawer item (see
   89   /// [child]) and as the caption of the [AboutDialog] that is shown.
   90   ///
   91   /// Defaults to the value of [Title.title], if a [Title] widget can be found.
   92   /// Otherwise, defaults to [Platform.resolvedExecutable].
   93   final String? applicationName;
   94 
   95   /// The version of this build of the application.
   96   ///
   97   /// This string is shown under the application name in the [AboutDialog].
   98   ///
   99   /// Defaults to the empty string.
  100   final String? applicationVersion;
  101 
  102   /// The icon to show next to the application name in the [AboutDialog].
  103   ///
  104   /// By default no icon is shown.
  105   ///
  106   /// Typically this will be an [ImageIcon] widget. It should honor the
  107   /// [IconTheme]'s [IconThemeData.size].
  108   ///
  109   /// This is not necessarily the same as the icon shown on the drawer item
  110   /// itself, which is controlled by the [icon] property.
  111   final Widget? applicationIcon;
  112 
  113   /// A string to show in small print in the [AboutDialog].
  114   ///
  115   /// Typically this is a copyright notice.
  116   ///
  117   /// Defaults to the empty string.
  118   final String? applicationLegalese;
  119 
  120   /// Widgets to add to the [AboutDialog] after the name, version, and legalese.
  121   ///
  122   /// This could include a link to a Web site, some descriptive text, credits,
  123   /// or other information to show in the about box.
  124   ///
  125   /// Defaults to nothing.
  126   final List<Widget>? aboutBoxChildren;
  127 
  128   /// Whether this list tile is part of a vertically dense list.
  129   ///
  130   /// If this property is null, then its value is based on [ListTileThemeData.dense].
  131   ///
  132   /// Dense list tiles default to a smaller height.
  133   final bool? dense;
  134 
  135   @override
  136   Widget build(BuildContext context) {
  137     assert(debugCheckHasMaterial(context));
  138     assert(debugCheckHasMaterialLocalizations(context));
  139     return ListTile(
  140       leading: icon,
  141       title: child ?? Text(MaterialLocalizations.of(context).aboutListTileTitle(
  142         applicationName ?? _defaultApplicationName(context),
  143       )),
  144       dense: dense,
  145       onTap: () {
  146         showAboutDialog(
  147           context: context,
  148           applicationName: applicationName,
  149           applicationVersion: applicationVersion,
  150           applicationIcon: applicationIcon,
  151           applicationLegalese: applicationLegalese,
  152           children: aboutBoxChildren,
  153         );
  154       },
  155     );
  156   }
  157 }
  158 
  159 /// Displays an [AboutDialog], which describes the application and provides a
  160 /// button to show licenses for software used by the application.
  161 ///
  162 /// The arguments correspond to the properties on [AboutDialog].
  163 ///
  164 /// If the application has a [Drawer], consider using [AboutListTile] instead
  165 /// of calling this directly.
  166 ///
  167 /// If you do not need an about box in your application, you should at least
  168 /// provide an affordance to call [showLicensePage].
  169 ///
  170 /// The licenses shown on the [LicensePage] are those returned by the
  171 /// [LicenseRegistry] API, which can be used to add more licenses to the list.
  172 ///
  173 /// The [context], [useRootNavigator], [routeSettings] and [anchorPoint]
  174 /// arguments are passed to [showDialog], the documentation for which discusses
  175 /// how it is used.
  176 void showAboutDialog({
  177   required BuildContext context,
  178   String? applicationName,
  179   String? applicationVersion,
  180   Widget? applicationIcon,
  181   String? applicationLegalese,
  182   List<Widget>? children,
  183   bool useRootNavigator = true,
  184   RouteSettings? routeSettings,
  185   Offset? anchorPoint,
  186 }) {
  187   assert(context != null);
  188   assert(useRootNavigator != null);
  189   showDialog<void>(
  190     context: context,
  191     useRootNavigator: useRootNavigator,
  192     builder: (BuildContext context) {
  193       return AboutDialog(
  194         applicationName: applicationName,
  195         applicationVersion: applicationVersion,
  196         applicationIcon: applicationIcon,
  197         applicationLegalese: applicationLegalese,
  198         children: children,
  199       );
  200     },
  201     routeSettings: routeSettings,
  202     anchorPoint: anchorPoint,
  203   );
  204 }
  205 
  206 /// Displays a [LicensePage], which shows licenses for software used by the
  207 /// application.
  208 ///
  209 /// The application arguments correspond to the properties on [LicensePage].
  210 ///
  211 /// The `context` argument is used to look up the [Navigator] for the page.
  212 ///
  213 /// The `useRootNavigator` argument is used to determine whether to push the
  214 /// page to the [Navigator] furthest from or nearest to the given `context`. It
  215 /// is `false` by default.
  216 ///
  217 /// If the application has a [Drawer], consider using [AboutListTile] instead
  218 /// of calling this directly.
  219 ///
  220 /// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
  221 /// [showLicensePage].
  222 ///
  223 /// The licenses shown on the [LicensePage] are those returned by the
  224 /// [LicenseRegistry] API, which can be used to add more licenses to the list.
  225 void showLicensePage({
  226   required BuildContext context,
  227   String? applicationName,
  228   String? applicationVersion,
  229   Widget? applicationIcon,
  230   String? applicationLegalese,
  231   bool useRootNavigator = false,
  232 }) {
  233   assert(context != null);
  234   assert(useRootNavigator != null);
  235   Navigator.of(context, rootNavigator: useRootNavigator).push(MaterialPageRoute<void>(
  236     builder: (BuildContext context) => LicensePage(
  237       applicationName: applicationName,
  238       applicationVersion: applicationVersion,
  239       applicationIcon: applicationIcon,
  240       applicationLegalese: applicationLegalese,
  241     ),
  242   ));
  243 }
  244 
  245 /// The amount of vertical space to separate chunks of text.
  246 const double _textVerticalSeparation = 18.0;
  247 
  248 /// An about box. This is a dialog box with the application's icon, name,
  249 /// version number, and copyright, plus a button to show licenses for software
  250 /// used by the application.
  251 ///
  252 /// To show an [AboutDialog], use [showAboutDialog].
  253 ///
  254 /// {@youtube 560 315 https://www.youtube.com/watch?v=YFCSODyFxbE}
  255 ///
  256 /// If the application has a [Drawer], the [AboutListTile] widget can make the
  257 /// process of showing an about dialog simpler.
  258 ///
  259 /// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
  260 /// [showLicensePage].
  261 ///
  262 /// The licenses shown on the [LicensePage] are those returned by the
  263 /// [LicenseRegistry] API, which can be used to add more licenses to the list.
  264 class AboutDialog extends StatelessWidget {
  265   /// Creates an about box.
  266   ///
  267   /// The arguments are all optional. The application name, if omitted, will be
  268   /// derived from the nearest [Title] widget. The version, icon, and legalese
  269   /// values default to the empty string.
  270   const AboutDialog({
  271     super.key,
  272     this.applicationName,
  273     this.applicationVersion,
  274     this.applicationIcon,
  275     this.applicationLegalese,
  276     this.children,
  277   });
  278 
  279   /// The name of the application.
  280   ///
  281   /// Defaults to the value of [Title.title], if a [Title] widget can be found.
  282   /// Otherwise, defaults to [Platform.resolvedExecutable].
  283   final String? applicationName;
  284 
  285   /// The version of this build of the application.
  286   ///
  287   /// This string is shown under the application name.
  288   ///
  289   /// Defaults to the empty string.
  290   final String? applicationVersion;
  291 
  292   /// The icon to show next to the application name.
  293   ///
  294   /// By default no icon is shown.
  295   ///
  296   /// Typically this will be an [ImageIcon] widget. It should honor the
  297   /// [IconTheme]'s [IconThemeData.size].
  298   final Widget? applicationIcon;
  299 
  300   /// A string to show in small print.
  301   ///
  302   /// Typically this is a copyright notice.
  303   ///
  304   /// Defaults to the empty string.
  305   final String? applicationLegalese;
  306 
  307   /// Widgets to add to the dialog box after the name, version, and legalese.
  308   ///
  309   /// This could include a link to a Web site, some descriptive text, credits,
  310   /// or other information to show in the about box.
  311   ///
  312   /// Defaults to nothing.
  313   final List<Widget>? children;
  314 
  315   @override
  316   Widget build(BuildContext context) {
  317     assert(debugCheckHasMaterialLocalizations(context));
  318     final String name = applicationName ?? _defaultApplicationName(context);
  319     final String version = applicationVersion ?? _defaultApplicationVersion(context);
  320     final Widget? icon = applicationIcon ?? _defaultApplicationIcon(context);
  321     final ThemeData themeData = Theme.of(context);
  322     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
  323     return AlertDialog(
  324       content: ListBody(
  325         children: <Widget>[
  326           Row(
  327             crossAxisAlignment: CrossAxisAlignment.start,
  328             children: <Widget>[
  329               if (icon != null) IconTheme(data: themeData.iconTheme, child: icon),
  330               Expanded(
  331                 child: Padding(
  332                   padding: const EdgeInsets.symmetric(horizontal: 24.0),
  333                   child: ListBody(
  334                     children: <Widget>[
  335                       Text(name, style: themeData.textTheme.headlineSmall),
  336                       Text(version, style: themeData.textTheme.bodyMedium),
  337                       const SizedBox(height: _textVerticalSeparation),
  338                       Text(applicationLegalese ?? '', style: themeData.textTheme.bodySmall),
  339                     ],
  340                   ),
  341                 ),
  342               ),
  343             ],
  344           ),
  345           ...?children,
  346         ],
  347       ),
  348       actions: <Widget>[
  349         TextButton(
  350           child: Text(
  351             themeData.useMaterial3
  352               ? localizations.viewLicensesButtonLabel
  353               : localizations.viewLicensesButtonLabel.toUpperCase()
  354           ),
  355           onPressed: () {
  356             showLicensePage(
  357               context: context,
  358               applicationName: applicationName,
  359               applicationVersion: applicationVersion,
  360               applicationIcon: applicationIcon,
  361               applicationLegalese: applicationLegalese,
  362             );
  363           },
  364         ),
  365         TextButton(
  366           child: Text(
  367             themeData.useMaterial3
  368               ? localizations.closeButtonLabel
  369               : localizations.closeButtonLabel.toUpperCase()
  370           ),
  371           onPressed: () {
  372             Navigator.pop(context);
  373           },
  374         ),
  375       ],
  376       scrollable: true,
  377     );
  378   }
  379 }
  380 
  381 /// A page that shows licenses for software used by the application.
  382 ///
  383 /// To show a [LicensePage], use [showLicensePage].
  384 ///
  385 /// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
  386 /// a button that calls [showLicensePage].
  387 ///
  388 /// The licenses shown on the [LicensePage] are those returned by the
  389 /// [LicenseRegistry] API, which can be used to add more licenses to the list.
  390 class LicensePage extends StatefulWidget {
  391   /// Creates a page that shows licenses for software used by the application.
  392   ///
  393   /// The arguments are all optional. The application name, if omitted, will be
  394   /// derived from the nearest [Title] widget. The version and legalese values
  395   /// default to the empty string.
  396   ///
  397   /// The licenses shown on the [LicensePage] are those returned by the
  398   /// [LicenseRegistry] API, which can be used to add more licenses to the list.
  399   const LicensePage({
  400     super.key,
  401     this.applicationName,
  402     this.applicationVersion,
  403     this.applicationIcon,
  404     this.applicationLegalese,
  405   });
  406 
  407   /// The name of the application.
  408   ///
  409   /// Defaults to the value of [Title.title], if a [Title] widget can be found.
  410   /// Otherwise, defaults to [Platform.resolvedExecutable].
  411   final String? applicationName;
  412 
  413   /// The version of this build of the application.
  414   ///
  415   /// This string is shown under the application name.
  416   ///
  417   /// Defaults to the empty string.
  418   final String? applicationVersion;
  419 
  420   /// The icon to show below the application name.
  421   ///
  422   /// By default no icon is shown.
  423   ///
  424   /// Typically this will be an [ImageIcon] widget. It should honor the
  425   /// [IconTheme]'s [IconThemeData.size].
  426   final Widget? applicationIcon;
  427 
  428   /// A string to show in small print.
  429   ///
  430   /// Typically this is a copyright notice.
  431   ///
  432   /// Defaults to the empty string.
  433   final String? applicationLegalese;
  434 
  435   @override
  436   State<LicensePage> createState() => _LicensePageState();
  437 }
  438 
  439 class _LicensePageState extends State<LicensePage> {
  440   final ValueNotifier<int?> selectedId = ValueNotifier<int?>(null);
  441 
  442   @override
  443   void dispose() {
  444     selectedId.dispose();
  445     super.dispose();
  446   }
  447 
  448   @override
  449   Widget build(BuildContext context) {
  450     return _MasterDetailFlow(
  451       detailPageFABlessGutterWidth: _getGutterSize(context),
  452       title: Text(MaterialLocalizations.of(context).licensesPageTitle),
  453       detailPageBuilder: _packageLicensePage,
  454       masterViewBuilder: _packagesView,
  455     );
  456   }
  457 
  458   Widget _packageLicensePage(BuildContext _, Object? args, ScrollController? scrollController) {
  459     assert(args is _DetailArguments);
  460     final _DetailArguments detailArguments = args! as _DetailArguments;
  461     return _PackageLicensePage(
  462       packageName: detailArguments.packageName,
  463       licenseEntries: detailArguments.licenseEntries,
  464       scrollController: scrollController,
  465     );
  466   }
  467 
  468   Widget _packagesView(final BuildContext _, final bool isLateral) {
  469     final Widget about = _AboutProgram(
  470         name: widget.applicationName ?? _defaultApplicationName(context),
  471         icon: widget.applicationIcon ?? _defaultApplicationIcon(context),
  472         version: widget.applicationVersion ?? _defaultApplicationVersion(context),
  473         legalese: widget.applicationLegalese,
  474       );
  475     return _PackagesView(
  476       about: about,
  477       isLateral: isLateral,
  478       selectedId: selectedId,
  479     );
  480   }
  481 }
  482 
  483 class _AboutProgram extends StatelessWidget {
  484   const _AboutProgram({
  485     required this.name,
  486     required this.version,
  487     this.icon,
  488     this.legalese,
  489   })  : assert(name != null),
  490         assert(version != null);
  491 
  492   final String name;
  493   final String version;
  494   final Widget? icon;
  495   final String? legalese;
  496 
  497   @override
  498   Widget build(BuildContext context) {
  499     return Padding(
  500       padding: EdgeInsets.symmetric(
  501         horizontal: _getGutterSize(context),
  502         vertical: 24.0,
  503       ),
  504       child: Column(
  505         children: <Widget>[
  506           Text(
  507             name,
  508             style: Theme.of(context).textTheme.headlineSmall,
  509             textAlign: TextAlign.center,
  510           ),
  511           if (icon != null)
  512             IconTheme(data: Theme.of(context).iconTheme, child: icon!),
  513           if (version != '')
  514             Padding(
  515               padding: const EdgeInsets.only(bottom: _textVerticalSeparation),
  516               child: Text(
  517                 version,
  518                 style: Theme.of(context).textTheme.bodyMedium,
  519                 textAlign: TextAlign.center,
  520               ),
  521             ),
  522           if (legalese != null && legalese != '')
  523             Text(
  524               legalese!,
  525               style: Theme.of(context).textTheme.bodySmall,
  526               textAlign: TextAlign.center,
  527             ),
  528           const SizedBox(height: _textVerticalSeparation),
  529           Text(
  530             'Powered by Flutter',
  531             style: Theme.of(context).textTheme.bodyMedium,
  532             textAlign: TextAlign.center,
  533           ),
  534         ],
  535       ),
  536     );
  537   }
  538 }
  539 
  540 class _PackagesView extends StatefulWidget {
  541   const _PackagesView({
  542     required this.about,
  543     required this.isLateral,
  544     required this.selectedId,
  545   })  : assert(about != null),
  546         assert(isLateral != null);
  547 
  548   final Widget about;
  549   final bool isLateral;
  550   final ValueNotifier<int?> selectedId;
  551 
  552   @override
  553   _PackagesViewState createState() => _PackagesViewState();
  554 }
  555 
  556 class _PackagesViewState extends State<_PackagesView> {
  557   final Future<_LicenseData> licenses = LicenseRegistry.licenses
  558       .fold<_LicenseData>(
  559         _LicenseData(),
  560         (_LicenseData prev, LicenseEntry license) => prev..addLicense(license),
  561       )
  562       .then((_LicenseData licenseData) => licenseData..sortPackages());
  563 
  564   @override
  565   Widget build(BuildContext context) {
  566     return FutureBuilder<_LicenseData>(
  567       future: licenses,
  568       builder: (BuildContext context, AsyncSnapshot<_LicenseData> snapshot) {
  569         return LayoutBuilder(
  570           key: ValueKey<ConnectionState>(snapshot.connectionState),
  571           builder: (BuildContext context, BoxConstraints constraints) {
  572             switch (snapshot.connectionState) {
  573               case ConnectionState.done:
  574                 _initDefaultDetailPage(snapshot.data!, context);
  575                 return ValueListenableBuilder<int?>(
  576                   valueListenable: widget.selectedId,
  577                   builder: (BuildContext context, int? selectedId, Widget? _) {
  578                     return Center(
  579                       child: Material(
  580                         color: Theme.of(context).cardColor,
  581                         elevation: 4.0,
  582                         child: Container(
  583                           constraints: BoxConstraints.loose(const Size.fromWidth(600.0)),
  584                           child: _packagesList(context, selectedId, snapshot.data!, widget.isLateral),
  585                         ),
  586                       ),
  587                     );
  588                   },
  589                 );
  590               case ConnectionState.none:
  591               case ConnectionState.active:
  592               case ConnectionState.waiting:
  593                 return Material(
  594                     color: Theme.of(context).cardColor,
  595                     child: Column(
  596                     children: <Widget>[
  597                       widget.about,
  598                       const Center(child: CircularProgressIndicator()),
  599                     ],
  600                   ),
  601                 );
  602             }
  603           },
  604         );
  605       },
  606     );
  607   }
  608 
  609   void _initDefaultDetailPage(_LicenseData data, BuildContext context) {
  610     if (data.packages.isEmpty) {
  611       return;
  612     }
  613     final String packageName = data.packages[widget.selectedId.value ?? 0];
  614     final List<int> bindings = data.packageLicenseBindings[packageName]!;
  615     _MasterDetailFlow.of(context).setInitialDetailPage(
  616       _DetailArguments(
  617         packageName,
  618         bindings.map((int i) => data.licenses[i]).toList(growable: false),
  619       ),
  620     );
  621   }
  622 
  623   Widget _packagesList(
  624     final BuildContext context,
  625     final int? selectedId,
  626     final _LicenseData data,
  627     final bool drawSelection,
  628   ) {
  629     return ListView.builder(
  630       itemCount: data.packages.length + 1,
  631       itemBuilder: (BuildContext context, int index) {
  632         if (index == 0) {
  633           return widget.about;
  634         }
  635         final int packageIndex = index - 1;
  636         final String packageName = data.packages[packageIndex];
  637         final List<int> bindings = data.packageLicenseBindings[packageName]!;
  638         return _PackageListTile(
  639           packageName: packageName,
  640           index: packageIndex,
  641           isSelected: drawSelection && packageIndex == (selectedId ?? 0),
  642           numberLicenses: bindings.length,
  643           onTap: () {
  644             widget.selectedId.value = packageIndex;
  645             _MasterDetailFlow.of(context).openDetailPage(_DetailArguments(
  646               packageName,
  647               bindings.map((int i) => data.licenses[i]).toList(growable: false),
  648             ));
  649           },
  650         );
  651       },
  652     );
  653   }
  654 }
  655 
  656 class _PackageListTile extends StatelessWidget {
  657   const _PackageListTile({
  658     required this.packageName,
  659     this.index,
  660     required this.isSelected,
  661     required this.numberLicenses,
  662     this.onTap,
  663 });
  664 
  665   final String packageName;
  666   final int? index;
  667   final bool isSelected;
  668   final int numberLicenses;
  669   final GestureTapCallback? onTap;
  670 
  671   @override
  672   Widget build(BuildContext context) {
  673     return Ink(
  674       color: isSelected ? Theme.of(context).highlightColor : Theme.of(context).cardColor,
  675       child: ListTile(
  676         title: Text(packageName),
  677         subtitle: Text(MaterialLocalizations.of(context).licensesPackageDetailText(numberLicenses)),
  678         selected: isSelected,
  679         onTap: onTap,
  680       ),
  681     );
  682   }
  683 }
  684 
  685 /// This is a collection of licenses and the packages to which they apply.
  686 /// [packageLicenseBindings] records the m+:n+ relationship between the license
  687 /// and packages as a map of package names to license indexes.
  688 class _LicenseData {
  689   final List<LicenseEntry> licenses = <LicenseEntry>[];
  690   final Map<String, List<int>> packageLicenseBindings = <String, List<int>>{};
  691   final List<String> packages = <String>[];
  692 
  693   // Special treatment for the first package since it should be the package
  694   // for delivered application.
  695   String? firstPackage;
  696 
  697   void addLicense(LicenseEntry entry) {
  698     // Before the license can be added, we must first record the packages to
  699     // which it belongs.
  700     for (final String package in entry.packages) {
  701       _addPackage(package);
  702       // Bind this license to the package using the next index value. This
  703       // creates a contract that this license must be inserted at this same
  704       // index value.
  705       packageLicenseBindings[package]!.add(licenses.length);
  706     }
  707     licenses.add(entry); // Completion of the contract above.
  708   }
  709 
  710   /// Add a package and initialize package license binding. This is a no-op if
  711   /// the package has been seen before.
  712   void _addPackage(String package) {
  713     if (!packageLicenseBindings.containsKey(package)) {
  714       packageLicenseBindings[package] = <int>[];
  715       firstPackage ??= package;
  716       packages.add(package);
  717     }
  718   }
  719 
  720   /// Sort the packages using some comparison method, or by the default manner,
  721   /// which is to put the application package first, followed by every other
  722   /// package in case-insensitive alphabetical order.
  723   void sortPackages([int Function(String a, String b)? compare]) {
  724     packages.sort(compare ?? (String a, String b) {
  725       // Based on how LicenseRegistry currently behaves, the first package
  726       // returned is the end user application license. This should be
  727       // presented first in the list. So here we make sure that first package
  728       // remains at the front regardless of alphabetical sorting.
  729       if (a == firstPackage) {
  730         return -1;
  731       }
  732       if (b == firstPackage) {
  733         return 1;
  734       }
  735       return a.toLowerCase().compareTo(b.toLowerCase());
  736     });
  737   }
  738 }
  739 
  740 @immutable
  741 class _DetailArguments {
  742   const _DetailArguments(this.packageName, this.licenseEntries);
  743 
  744   final String packageName;
  745   final List<LicenseEntry> licenseEntries;
  746 
  747   @override
  748   bool operator ==(final dynamic other) {
  749     if (other is _DetailArguments) {
  750       return other.packageName == packageName;
  751     }
  752     return other == this;
  753   }
  754 
  755   @override
  756   int get hashCode => Object.hash(packageName, Object.hashAll(licenseEntries));
  757 }
  758 
  759 class _PackageLicensePage extends StatefulWidget {
  760   const _PackageLicensePage({
  761     required this.packageName,
  762     required this.licenseEntries,
  763     required this.scrollController,
  764   });
  765 
  766   final String packageName;
  767   final List<LicenseEntry> licenseEntries;
  768   final ScrollController? scrollController;
  769 
  770   @override
  771   _PackageLicensePageState createState() => _PackageLicensePageState();
  772 }
  773 
  774 class _PackageLicensePageState extends State<_PackageLicensePage> {
  775   @override
  776   void initState() {
  777     super.initState();
  778     _initLicenses();
  779   }
  780 
  781   final List<Widget> _licenses = <Widget>[];
  782   bool _loaded = false;
  783 
  784   Future<void> _initLicenses() async {
  785     int debugFlowId = -1;
  786     assert(() {
  787       final Flow flow = Flow.begin();
  788       Timeline.timeSync('_initLicenses()', () { }, flow: flow);
  789       debugFlowId = flow.id;
  790       return true;
  791     }());
  792     for (final LicenseEntry license in widget.licenseEntries) {
  793       if (!mounted) {
  794         return;
  795       }
  796       assert(() {
  797         Timeline.timeSync('_initLicenses()', () { }, flow: Flow.step(debugFlowId));
  798         return true;
  799       }());
  800       final List<LicenseParagraph> paragraphs =
  801         await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
  802           license.paragraphs.toList,
  803           Priority.animation,
  804           debugLabel: 'License',
  805         );
  806       if (!mounted) {
  807         return;
  808       }
  809       setState(() {
  810         _licenses.add(const Padding(
  811           padding: EdgeInsets.all(18.0),
  812           child: Divider(),
  813         ));
  814         for (final LicenseParagraph paragraph in paragraphs) {
  815           if (paragraph.indent == LicenseParagraph.centeredIndent) {
  816             _licenses.add(Padding(
  817               padding: const EdgeInsets.only(top: 16.0),
  818               child: Text(
  819                 paragraph.text,
  820                 style: const TextStyle(fontWeight: FontWeight.bold),
  821                 textAlign: TextAlign.center,
  822               ),
  823             ));
  824           } else {
  825             assert(paragraph.indent >= 0);
  826             _licenses.add(Padding(
  827               padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
  828               child: Text(paragraph.text),
  829             ));
  830           }
  831         }
  832       });
  833     }
  834     setState(() {
  835       _loaded = true;
  836     });
  837     assert(() {
  838       Timeline.timeSync('Build scheduled', () { }, flow: Flow.end(debugFlowId));
  839       return true;
  840     }());
  841   }
  842 
  843   @override
  844   Widget build(BuildContext context) {
  845     assert(debugCheckHasMaterialLocalizations(context));
  846     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
  847     final ThemeData theme = Theme.of(context);
  848     final String title = widget.packageName;
  849     final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length);
  850     final double pad = _getGutterSize(context);
  851     final EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad);
  852     final List<Widget> listWidgets = <Widget>[
  853       ..._licenses,
  854       if (!_loaded)
  855         const Padding(
  856           padding: EdgeInsets.symmetric(vertical: 24.0),
  857           child: Center(
  858             child: CircularProgressIndicator(),
  859           ),
  860         ),
  861     ];
  862 
  863     final Widget page;
  864     if (widget.scrollController == null) {
  865       page = Scaffold(
  866         appBar: AppBar(
  867           title: _PackageLicensePageTitle(
  868             title,
  869             subtitle,
  870             theme.appBarTheme.textTheme ?? theme.primaryTextTheme,
  871           ),
  872         ),
  873         body: Center(
  874           child: Material(
  875             color: theme.cardColor,
  876             elevation: 4.0,
  877             child: Container(
  878               constraints: BoxConstraints.loose(const Size.fromWidth(600.0)),
  879               child: Localizations.override(
  880                 locale: const Locale('en', 'US'),
  881                 context: context,
  882                 child: ScrollConfiguration(
  883                   // A Scrollbar is built-in below.
  884                   behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
  885                   child: Scrollbar(
  886                     child: ListView(padding: padding, children: listWidgets),
  887                   ),
  888                 ),
  889               ),
  890             ),
  891           ),
  892         ),
  893       );
  894     } else {
  895       page = CustomScrollView(
  896         controller: widget.scrollController,
  897         slivers: <Widget>[
  898           SliverAppBar(
  899             automaticallyImplyLeading: false,
  900             pinned: true,
  901             backgroundColor: theme.cardColor,
  902             title: _PackageLicensePageTitle(title, subtitle, theme.textTheme),
  903           ),
  904           SliverPadding(
  905             padding: padding,
  906             sliver: SliverList(
  907               delegate: SliverChildBuilderDelegate(
  908                 (BuildContext context, int index) => Localizations.override(
  909                   locale: const Locale('en', 'US'),
  910                   context: context,
  911                   child: listWidgets[index],
  912                 ),
  913                 childCount: listWidgets.length,
  914               ),
  915             ),
  916           ),
  917         ],
  918       );
  919     }
  920     return DefaultTextStyle(
  921       style: theme.textTheme.bodySmall!,
  922       child: page,
  923     );
  924   }
  925 }
  926 
  927 class _PackageLicensePageTitle extends StatelessWidget {
  928   const _PackageLicensePageTitle(
  929     this.title,
  930     this.subtitle,
  931     this.theme,
  932   );
  933 
  934   final String title;
  935   final String subtitle;
  936   final TextTheme theme;
  937 
  938   @override
  939   Widget build(BuildContext context) {
  940     final Color? color = Theme.of(context).appBarTheme.foregroundColor;
  941 
  942     return Column(
  943       mainAxisAlignment: MainAxisAlignment.center,
  944       crossAxisAlignment: CrossAxisAlignment.start,
  945       children: <Widget>[
  946         Text(title, style: theme.titleLarge?.copyWith(color: color)),
  947         Text(subtitle, style: theme.titleSmall?.copyWith(color: color)),
  948       ],
  949     );
  950   }
  951 }
  952 
  953 String _defaultApplicationName(BuildContext context) {
  954   // This doesn't handle the case of the application's title dynamically
  955   // changing. In theory, we should make Title expose the current application
  956   // title using an InheritedWidget, and so forth. However, in practice, if
  957   // someone really wants their application title to change dynamically, they
  958   // can provide an explicit applicationName to the widgets defined in this
  959   // file, instead of relying on the default.
  960   final Title? ancestorTitle = context.findAncestorWidgetOfExactType<Title>();
  961   return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
  962 }
  963 
  964 String _defaultApplicationVersion(BuildContext context) {
  965   // TODO(ianh): Get this from the embedder somehow.
  966   return '';
  967 }
  968 
  969 Widget? _defaultApplicationIcon(BuildContext context) {
  970   // TODO(ianh): Get this from the embedder somehow.
  971   return null;
  972 }
  973 
  974 const int _materialGutterThreshold = 720;
  975 const double _wideGutterSize = 24.0;
  976 const double _narrowGutterSize = 12.0;
  977 
  978 double _getGutterSize(BuildContext context) =>
  979     MediaQuery.of(context).size.width >= _materialGutterThreshold ? _wideGutterSize : _narrowGutterSize;
  980 
  981 /// Signature for the builder callback used by [_MasterDetailFlow].
  982 typedef _MasterViewBuilder = Widget Function(BuildContext context, bool isLateralUI);
  983 
  984 /// Signature for the builder callback used by [_MasterDetailFlow.detailPageBuilder].
  985 ///
  986 /// scrollController is provided when the page destination is the draggable
  987 /// sheet in the lateral UI. Otherwise, it is null.
  988 typedef _DetailPageBuilder = Widget Function(BuildContext context, Object? arguments, ScrollController? scrollController);
  989 
  990 /// Signature for the builder callback used by [_MasterDetailFlow.actionBuilder].
  991 ///
  992 /// Builds the actions that go in the app bars constructed for the master and
  993 /// lateral UI pages. actionLevel indicates the intended destination of the
  994 /// return actions.
  995 typedef _ActionBuilder = List<Widget> Function(BuildContext context, _ActionLevel actionLevel);
  996 
  997 /// Describes which type of app bar the actions are intended for.
  998 enum _ActionLevel {
  999   /// Indicates the top app bar in the lateral UI.
 1000   top,
 1001 
 1002   /// Indicates the master view app bar in the lateral UI.
 1003   view,
 1004 }
 1005 
 1006 /// Describes which layout will be used by [_MasterDetailFlow].
 1007 enum _LayoutMode {
 1008   /// Use a nested or lateral layout depending on available screen width.
 1009   auto,
 1010 
 1011   /// Always use a lateral layout.
 1012   lateral,
 1013 
 1014   /// Always use a nested layout.
 1015   nested,
 1016 }
 1017 
 1018 const String _navMaster = 'master';
 1019 const String _navDetail = 'detail';
 1020 enum _Focus { master, detail }
 1021 
 1022 /// A Master Detail Flow widget. Depending on screen width it builds either a
 1023 /// lateral or nested navigation flow between a master view and a detail page.
 1024 /// bloc pattern.
 1025 ///
 1026 /// If focus is on detail view, then switching to nested navigation will
 1027 /// populate the navigation history with the master page and the detail page on
 1028 /// top. Otherwise the focus is on the master view and just the master page
 1029 /// is shown.
 1030 class _MasterDetailFlow extends StatefulWidget {
 1031   /// Creates a master detail navigation flow which is either nested or
 1032   /// lateral depending on screen width.
 1033   const _MasterDetailFlow({
 1034     required this.detailPageBuilder,
 1035     required this.masterViewBuilder,
 1036     this.automaticallyImplyLeading = true,
 1037     this.detailPageFABlessGutterWidth,
 1038     this.displayMode = _LayoutMode.auto,
 1039     this.title,
 1040   })  : assert(masterViewBuilder != null),
 1041         assert(automaticallyImplyLeading != null),
 1042         assert(detailPageBuilder != null),
 1043         assert(displayMode != null);
 1044 
 1045   /// Builder for the master view for lateral navigation.
 1046   ///
 1047   /// If [masterPageBuilder] is not supplied the master page required for nested navigation, also
 1048   /// builds the master view inside a [Scaffold] with an [AppBar].
 1049   final _MasterViewBuilder masterViewBuilder;
 1050 
 1051   /// Builder for the detail page.
 1052   ///
 1053   /// If scrollController == null, the page is intended for nested navigation. The lateral detail
 1054   /// page is inside a [DraggableScrollableSheet] and should have a scrollable element that uses
 1055   /// the [ScrollController] provided. In fact, it is strongly recommended the entire lateral
 1056   /// page is scrollable.
 1057   final _DetailPageBuilder detailPageBuilder;
 1058 
 1059   /// Override the width of the gutter when there is no floating action button.
 1060   final double? detailPageFABlessGutterWidth;
 1061 
 1062   /// The title for the lateral UI [AppBar].
 1063   ///
 1064   /// See [AppBar.title].
 1065   final Widget? title;
 1066 
 1067   /// Override the framework from determining whether to show a leading widget or not.
 1068   ///
 1069   /// See [AppBar.automaticallyImplyLeading].
 1070   final bool automaticallyImplyLeading;
 1071 
 1072   /// Forces display mode and style.
 1073   final _LayoutMode displayMode;
 1074 
 1075   @override
 1076   _MasterDetailFlowState createState() => _MasterDetailFlowState();
 1077 
 1078   // The master detail flow proxy from the closest instance of this class that encloses the given
 1079   // context.
 1080   //
 1081   // Typical usage is as follows:
 1082   //
 1083   // ```dart
 1084   // _MasterDetailFlow.of(context).openDetailPage(arguments);
 1085   // ```
 1086   static _MasterDetailFlowProxy of(BuildContext context) {
 1087     _PageOpener? pageOpener = context.findAncestorStateOfType<_MasterDetailScaffoldState>();
 1088     pageOpener ??= context.findAncestorStateOfType<_MasterDetailFlowState>();
 1089     assert(() {
 1090       if (pageOpener == null) {
 1091         throw FlutterError(
 1092           'Master Detail operation requested with a context that does not include a Master Detail '
 1093           'Flow.\nThe context used to open a detail page from the Master Detail Flow must be '
 1094           'that of a widget that is a descendant of a Master Detail Flow widget.',
 1095         );
 1096       }
 1097       return true;
 1098     }());
 1099     return _MasterDetailFlowProxy._(pageOpener!);
 1100   }
 1101 }
 1102 
 1103 /// Interface for interacting with the [_MasterDetailFlow].
 1104 class _MasterDetailFlowProxy implements _PageOpener {
 1105   _MasterDetailFlowProxy._(this._pageOpener);
 1106 
 1107   final _PageOpener _pageOpener;
 1108 
 1109   /// Open detail page with arguments.
 1110   @override
 1111   void openDetailPage(Object arguments) =>
 1112       _pageOpener.openDetailPage(arguments);
 1113 
 1114   /// Set the initial page to be open for the lateral layout. This can be set at any time, but
 1115   /// will have no effect after any calls to openDetailPage.
 1116   @override
 1117   void setInitialDetailPage(Object arguments) =>
 1118       _pageOpener.setInitialDetailPage(arguments);
 1119 }
 1120 
 1121 abstract class _PageOpener {
 1122   void openDetailPage(Object arguments);
 1123 
 1124   void setInitialDetailPage(Object arguments);
 1125 }
 1126 
 1127 const int _materialWideDisplayThreshold = 840;
 1128 
 1129 class _MasterDetailFlowState extends State<_MasterDetailFlow> implements _PageOpener {
 1130   /// Tracks whether focus is on the detail or master views. Determines behavior when switching
 1131   /// from lateral to nested navigation.
 1132   _Focus focus = _Focus.master;
 1133 
 1134   /// Cache of arguments passed when opening a detail page. Used when rebuilding.
 1135   Object? _cachedDetailArguments;
 1136 
 1137   /// Record of the layout that was built.
 1138   _LayoutMode? _builtLayout;
 1139 
 1140   /// Key to access navigator in the nested layout.
 1141   final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
 1142 
 1143   @override
 1144   void openDetailPage(Object arguments) {
 1145     _cachedDetailArguments = arguments;
 1146     if (_builtLayout == _LayoutMode.nested) {
 1147       _navigatorKey.currentState!.pushNamed(_navDetail, arguments: arguments);
 1148     } else {
 1149       focus = _Focus.detail;
 1150     }
 1151   }
 1152 
 1153   @override
 1154   void setInitialDetailPage(Object arguments) {
 1155     _cachedDetailArguments = arguments;
 1156   }
 1157 
 1158   @override
 1159   Widget build(BuildContext context) {
 1160     switch (widget.displayMode) {
 1161       case _LayoutMode.nested:
 1162         return _nestedUI(context);
 1163       case _LayoutMode.lateral:
 1164         return _lateralUI(context);
 1165       case _LayoutMode.auto:
 1166         return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
 1167           final double availableWidth = constraints.maxWidth;
 1168           if (availableWidth >= _materialWideDisplayThreshold) {
 1169             return _lateralUI(context);
 1170           } else {
 1171             return _nestedUI(context);
 1172           }
 1173         });
 1174     }
 1175   }
 1176 
 1177   Widget _nestedUI(BuildContext context) {
 1178     _builtLayout = _LayoutMode.nested;
 1179     final MaterialPageRoute<void> masterPageRoute = _masterPageRoute(context);
 1180 
 1181     return WillPopScope(
 1182       // Push pop check into nested navigator.
 1183       onWillPop: () async => !(await _navigatorKey.currentState!.maybePop()),
 1184       child: Navigator(
 1185         key: _navigatorKey,
 1186         initialRoute: 'initial',
 1187         onGenerateInitialRoutes: (NavigatorState navigator, String initialRoute) {
 1188           switch (focus) {
 1189             case _Focus.master:
 1190               return <Route<void>>[masterPageRoute];
 1191             case _Focus.detail:
 1192               return <Route<void>>[
 1193                 masterPageRoute,
 1194                 _detailPageRoute(_cachedDetailArguments),
 1195               ];
 1196           }
 1197         },
 1198         onGenerateRoute: (RouteSettings settings) {
 1199           switch (settings.name) {
 1200             case _navMaster:
 1201               // Matching state to navigation event.
 1202               focus = _Focus.master;
 1203               return masterPageRoute;
 1204             case _navDetail:
 1205               // Matching state to navigation event.
 1206               focus = _Focus.detail;
 1207               // Cache detail page settings.
 1208               _cachedDetailArguments = settings.arguments;
 1209               return _detailPageRoute(_cachedDetailArguments);
 1210             default:
 1211               throw Exception('Unknown route ${settings.name}');
 1212           }
 1213         },
 1214       ),
 1215     );
 1216   }
 1217 
 1218   MaterialPageRoute<void> _masterPageRoute(BuildContext context) {
 1219     return MaterialPageRoute<dynamic>(
 1220       builder: (BuildContext c) => BlockSemantics(
 1221         child: _MasterPage(
 1222                 leading: widget.automaticallyImplyLeading && Navigator.of(context).canPop()
 1223                         ? BackButton(onPressed: () => Navigator.of(context).pop())
 1224                         : null,
 1225                 title: widget.title,
 1226                 automaticallyImplyLeading: widget.automaticallyImplyLeading,
 1227                 masterViewBuilder: widget.masterViewBuilder,
 1228               ),
 1229       ),
 1230     );
 1231   }
 1232 
 1233   MaterialPageRoute<void> _detailPageRoute(Object? arguments) {
 1234     return MaterialPageRoute<dynamic>(builder: (BuildContext context) {
 1235       return WillPopScope(
 1236         onWillPop: () async {
 1237           // No need for setState() as rebuild happens on navigation pop.
 1238           focus = _Focus.master;
 1239           Navigator.of(context).pop();
 1240           return false;
 1241         },
 1242         child: BlockSemantics(child: widget.detailPageBuilder(context, arguments, null)),
 1243       );
 1244     });
 1245   }
 1246 
 1247   Widget _lateralUI(BuildContext context) {
 1248     _builtLayout = _LayoutMode.lateral;
 1249     return _MasterDetailScaffold(
 1250       actionBuilder: (_, __) => const<Widget>[],
 1251       automaticallyImplyLeading: widget.automaticallyImplyLeading,
 1252       detailPageBuilder: (BuildContext context, Object? args, ScrollController? scrollController) =>
 1253           widget.detailPageBuilder(context, args ?? _cachedDetailArguments, scrollController),
 1254       detailPageFABlessGutterWidth: widget.detailPageFABlessGutterWidth,
 1255       initialArguments: _cachedDetailArguments,
 1256       masterViewBuilder: (BuildContext context, bool isLateral) => widget.masterViewBuilder(context, isLateral),
 1257       title: widget.title,
 1258     );
 1259   }
 1260 }
 1261 
 1262 class _MasterPage extends StatelessWidget {
 1263   const _MasterPage({
 1264     this.leading,
 1265     this.title,
 1266     this.masterViewBuilder,
 1267     required this.automaticallyImplyLeading,
 1268   });
 1269 
 1270   final _MasterViewBuilder? masterViewBuilder;
 1271   final Widget? title;
 1272   final Widget? leading;
 1273   final bool automaticallyImplyLeading;
 1274 
 1275   @override
 1276   Widget build(BuildContext context) {
 1277     return Scaffold(
 1278         appBar: AppBar(
 1279           title: title,
 1280           leading: leading,
 1281           actions: const <Widget>[],
 1282           automaticallyImplyLeading: automaticallyImplyLeading,
 1283         ),
 1284         body: masterViewBuilder!(context, false),
 1285       );
 1286   }
 1287 
 1288 }
 1289 
 1290 const double _kCardElevation = 4.0;
 1291 const double _kMasterViewWidth = 320.0;
 1292 const double _kDetailPageFABlessGutterWidth = 40.0;
 1293 const double _kDetailPageFABGutterWidth = 84.0;
 1294 
 1295 class _MasterDetailScaffold extends StatefulWidget {
 1296   const _MasterDetailScaffold({
 1297     required this.detailPageBuilder,
 1298     required this.masterViewBuilder,
 1299     this.actionBuilder,
 1300     this.initialArguments,
 1301     this.title,
 1302     required this.automaticallyImplyLeading,
 1303     this.detailPageFABlessGutterWidth,
 1304   })  : assert(detailPageBuilder != null),
 1305         assert(masterViewBuilder != null);
 1306 
 1307   final _MasterViewBuilder masterViewBuilder;
 1308 
 1309   /// Builder for the detail page.
 1310   ///
 1311   /// The detail page is inside a [DraggableScrollableSheet] and should have a scrollable element
 1312   /// that uses the [ScrollController] provided. In fact, it is strongly recommended the entire
 1313   /// lateral page is scrollable.
 1314   final _DetailPageBuilder detailPageBuilder;
 1315   final _ActionBuilder? actionBuilder;
 1316   final Object? initialArguments;
 1317   final Widget? title;
 1318   final bool automaticallyImplyLeading;
 1319   final double? detailPageFABlessGutterWidth;
 1320 
 1321   @override
 1322   _MasterDetailScaffoldState createState() => _MasterDetailScaffoldState();
 1323 }
 1324 
 1325 class _MasterDetailScaffoldState extends State<_MasterDetailScaffold>
 1326     implements _PageOpener {
 1327   late FloatingActionButtonLocation floatingActionButtonLocation;
 1328   late double detailPageFABGutterWidth;
 1329   late double detailPageFABlessGutterWidth;
 1330   late double masterViewWidth;
 1331 
 1332   final ValueNotifier<Object?> _detailArguments = ValueNotifier<Object?>(null);
 1333 
 1334   @override
 1335   void initState() {
 1336     super.initState();
 1337     detailPageFABlessGutterWidth = widget.detailPageFABlessGutterWidth ?? _kDetailPageFABlessGutterWidth;
 1338     detailPageFABGutterWidth = _kDetailPageFABGutterWidth;
 1339     masterViewWidth = _kMasterViewWidth;
 1340     floatingActionButtonLocation = FloatingActionButtonLocation.endTop;
 1341   }
 1342 
 1343   @override
 1344   void dispose() {
 1345     _detailArguments.dispose();
 1346     super.dispose();
 1347   }
 1348 
 1349   @override
 1350   void openDetailPage(Object arguments) {
 1351     SchedulerBinding.instance.addPostFrameCallback((_) => _detailArguments.value = arguments);
 1352     _MasterDetailFlow.of(context).openDetailPage(arguments);
 1353   }
 1354 
 1355   @override
 1356   void setInitialDetailPage(Object arguments) {
 1357     SchedulerBinding.instance.addPostFrameCallback((_) => _detailArguments.value = arguments);
 1358     _MasterDetailFlow.of(context).setInitialDetailPage(arguments);
 1359   }
 1360 
 1361   @override
 1362   Widget build(BuildContext context) {
 1363     return Stack(
 1364       children: <Widget>[
 1365         Scaffold(
 1366           floatingActionButtonLocation: floatingActionButtonLocation,
 1367           appBar: AppBar(
 1368             title: widget.title,
 1369             actions: widget.actionBuilder!(context, _ActionLevel.top),
 1370             automaticallyImplyLeading: widget.automaticallyImplyLeading,
 1371             bottom: PreferredSize(
 1372               preferredSize: const Size.fromHeight(kToolbarHeight),
 1373               child: Row(
 1374                 children: <Widget>[
 1375                   ConstrainedBox(
 1376                     constraints: BoxConstraints.tightFor(width: masterViewWidth),
 1377                     child: IconTheme(
 1378                       data: Theme.of(context).primaryIconTheme,
 1379                       child: Container(
 1380                         alignment: AlignmentDirectional.centerEnd,
 1381                         padding: const EdgeInsets.all(8),
 1382                         child: OverflowBar(
 1383                           spacing: 8,
 1384                           overflowAlignment: OverflowBarAlignment.end,
 1385                           children: widget.actionBuilder!(context, _ActionLevel.view),
 1386                         ),
 1387                       ),
 1388                     ),
 1389                   ),
 1390                 ],
 1391               ),
 1392             ),
 1393           ),
 1394           body: _masterPanel(context),
 1395         ),
 1396         // Detail view stacked above main scaffold and master view.
 1397         SafeArea(
 1398           child: Padding(
 1399             padding: EdgeInsetsDirectional.only(
 1400               start: masterViewWidth - _kCardElevation,
 1401               end: detailPageFABlessGutterWidth,
 1402             ),
 1403             child: ValueListenableBuilder<Object?>(
 1404               valueListenable: _detailArguments,
 1405               builder: (BuildContext context, Object? value, Widget? child) {
 1406                 return AnimatedSwitcher(
 1407                   transitionBuilder: (Widget child, Animation<double> animation) =>
 1408                     const FadeUpwardsPageTransitionsBuilder().buildTransitions<void>(
 1409                       null,
 1410                       null,
 1411                       animation,
 1412                       null,
 1413                       child,
 1414                     ),
 1415                   duration: const Duration(milliseconds: 500),
 1416                   child: Container(
 1417                     key: ValueKey<Object?>(value ?? widget.initialArguments),
 1418                     constraints: const BoxConstraints.expand(),
 1419                     child: _DetailView(
 1420                       builder: widget.detailPageBuilder,
 1421                       arguments: value ?? widget.initialArguments,
 1422                     ),
 1423                   ),
 1424                 );
 1425               },
 1426             ),
 1427           ),
 1428         ),
 1429       ],
 1430     );
 1431   }
 1432 
 1433   ConstrainedBox _masterPanel(BuildContext context, {bool needsScaffold = false}) {
 1434     return ConstrainedBox(
 1435       constraints: BoxConstraints(maxWidth: masterViewWidth),
 1436       child: needsScaffold
 1437           ? Scaffold(
 1438               appBar: AppBar(
 1439                 title: widget.title,
 1440                 actions: widget.actionBuilder!(context, _ActionLevel.top),
 1441                 automaticallyImplyLeading: widget.automaticallyImplyLeading,
 1442               ),
 1443               body: widget.masterViewBuilder(context, true),
 1444             )
 1445           : widget.masterViewBuilder(context, true),
 1446     );
 1447   }
 1448 }
 1449 
 1450 class _DetailView extends StatelessWidget {
 1451   const _DetailView({
 1452     required _DetailPageBuilder builder,
 1453     Object? arguments,
 1454   })  : assert(builder != null),
 1455         _builder = builder,
 1456         _arguments = arguments;
 1457 
 1458   final _DetailPageBuilder _builder;
 1459   final Object? _arguments;
 1460 
 1461   @override
 1462   Widget build(BuildContext context) {
 1463     if (_arguments == null) {
 1464       return const SizedBox.shrink();
 1465     }
 1466     final double screenHeight = MediaQuery.of(context).size.height;
 1467     final double minHeight = (screenHeight - kToolbarHeight) / screenHeight;
 1468 
 1469     return DraggableScrollableSheet(
 1470       initialChildSize: minHeight,
 1471       minChildSize: minHeight,
 1472       expand: false,
 1473       builder: (BuildContext context, ScrollController controller) {
 1474         return MouseRegion(
 1475           // TODO(TonicArtos): Remove MouseRegion workaround for pointer hover events passing through DraggableScrollableSheet once https://github.com/flutter/flutter/issues/59741 is resolved.
 1476           child: Card(
 1477             color: Theme.of(context).cardColor,
 1478             elevation: _kCardElevation,
 1479             clipBehavior: Clip.antiAlias,
 1480             margin: const EdgeInsets.fromLTRB(_kCardElevation, 0.0, _kCardElevation, 0.0),
 1481             shape: const RoundedRectangleBorder(
 1482               borderRadius: BorderRadius.vertical(top: Radius.circular(3.0)),
 1483             ),
 1484             child: _builder(
 1485               context,
 1486               _arguments,
 1487               controller,
 1488             ),
 1489           ),
 1490         );
 1491       },
 1492     );
 1493   }
 1494 }