"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/about.dart" (13 Nov 2020, 56936 Bytes) of package /linux/misc/flutter-1.22.4.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.

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