about.dart 21.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Ian Hickson's avatar
Ian Hickson committed
5
import 'dart:async';
6
import 'dart:developer' show Timeline, Flow;
7
import 'dart:io' show Platform;
Ian Hickson's avatar
Ian Hickson committed
8

9
import 'package:flutter/foundation.dart';
10 11
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart' hide Flow;
Ian Hickson's avatar
Ian Hickson committed
12 13 14 15 16

import 'app_bar.dart';
import 'debug.dart';
import 'dialog.dart';
import 'flat_button.dart';
17
import 'list_tile.dart';
18
import 'material_localizations.dart';
Ian Hickson's avatar
Ian Hickson committed
19
import 'page.dart';
Ian Hickson's avatar
Ian Hickson committed
20
import 'progress_indicator.dart';
Ian Hickson's avatar
Ian Hickson committed
21
import 'scaffold.dart';
22
import 'scrollbar.dart';
Ian Hickson's avatar
Ian Hickson committed
23 24
import 'theme.dart';

25
/// A [ListTile] that shows an about box.
Ian Hickson's avatar
Ian Hickson committed
26
///
27 28
/// This widget is often added to an app's [Drawer]. When tapped it shows
/// an about box dialog with [showAboutDialog].
Ian Hickson's avatar
Ian Hickson committed
29 30
///
/// The about box will include a button that shows licenses for software used by
31 32
/// the application. The licenses shown are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
Ian Hickson's avatar
Ian Hickson committed
33 34 35
///
/// If your application does not have a [Drawer], you should provide an
/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
36
/// {@tool dartpad --template=stateless_widget_material}
37 38 39 40 41 42 43
///
/// This sample shows two ways to open [AboutDialog]. The first one
/// uses an [AboutListTile], and the second uses the [showAboutDialog] function.
///
/// ```dart
///
///  Widget build(BuildContext context) {
44
///    final TextStyle textStyle = Theme.of(context).textTheme.bodyText2;
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
///    final List<Widget> aboutBoxChildren = <Widget>[
///      SizedBox(height: 24),
///      RichText(
///        text: TextSpan(
///          children: <TextSpan>[
///            TextSpan(
///              style: textStyle,
///              text: 'Flutter is Google’s UI toolkit for building beautiful, '
///              'natively compiled applications for mobile, web, and desktop '
///              'from a single codebase. Learn more about Flutter at '
///            ),
///            TextSpan(
///              style: textStyle.copyWith(color: Theme.of(context).accentColor),
///              text: 'https://flutter.dev'
///            ),
///            TextSpan(
///              style: textStyle,
///              text: '.'
///            ),
///          ],
///        ),
///      ),
///    ];
///
///    return Scaffold(
///      appBar: AppBar(
///        title: Text('Show About Example'),
///      ),
///      drawer: Drawer(
///        child: SingleChildScrollView(
///          child: SafeArea(
///            child: AboutListTile(
///              icon: Icon(Icons.info),
///              applicationIcon: FlutterLogo(),
///              applicationName: 'Show About Example',
///              applicationVersion: 'August 2019',
Ian Hickson's avatar
Ian Hickson committed
81
///              applicationLegalese: '© 2014 The Flutter Authors',
82 83 84 85 86 87 88 89 90 91 92 93 94 95
///              aboutBoxChildren: aboutBoxChildren,
///            ),
///          ),
///        ),
///      ),
///      body: Center(
///        child: RaisedButton(
///          child: Text('Show About Example'),
///          onPressed: () {
///            showAboutDialog(
///              context: context,
///              applicationIcon: FlutterLogo(),
///              applicationName: 'Show About Example',
///              applicationVersion: 'August 2019',
Ian Hickson's avatar
Ian Hickson committed
96
///              applicationLegalese: '© 2014 The Flutter Authors',
97 98 99 100 101 102 103 104 105 106
///              children: aboutBoxChildren,
///            );
///          },
///        ),
///      ),
///    );
///}
/// ```
/// {@end-tool}
///
107 108
class AboutListTile extends StatelessWidget {
  /// Creates a list tile for showing an about box.
Ian Hickson's avatar
Ian Hickson committed
109 110 111 112
  ///
  /// The arguments are all optional. The application name, if omitted, will be
  /// derived from the nearest [Title] widget. The version, icon, and legalese
  /// values default to the empty string.
113
  const AboutListTile({
Ian Hickson's avatar
Ian Hickson committed
114
    Key key,
115
    this.icon,
Ian Hickson's avatar
Ian Hickson committed
116 117 118 119 120
    this.child,
    this.applicationName,
    this.applicationVersion,
    this.applicationIcon,
    this.applicationLegalese,
121
    this.aboutBoxChildren,
122
    this.dense,
Ian Hickson's avatar
Ian Hickson committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
  }) : super(key: key);

  /// The icon to show for this drawer item.
  ///
  /// By default no icon is shown.
  ///
  /// This is not necessarily the same as the image shown in the dialog box
  /// itself; which is controlled by the [applicationIcon] property.
  final Widget icon;

  /// The label to show on this drawer item.
  ///
  /// Defaults to a text widget that says "About Foo" where "Foo" is the
  /// application name specified by [applicationName].
  final Widget child;

  /// The name of the application.
  ///
  /// This string is used in the default label for this drawer item (see
  /// [child]) and as the caption of the [AboutDialog] that is shown.
  ///
  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
145
  /// Otherwise, defaults to [Platform.resolvedExecutable].
Ian Hickson's avatar
Ian Hickson committed
146 147 148 149 150 151 152 153 154 155 156 157 158
  final String applicationName;

  /// The version of this build of the application.
  ///
  /// This string is shown under the application name in the [AboutDialog].
  ///
  /// Defaults to the empty string.
  final String applicationVersion;

  /// The icon to show next to the application name in the [AboutDialog].
  ///
  /// By default no icon is shown.
  ///
159 160 161
  /// Typically this will be an [ImageIcon] widget. It should honor the
  /// [IconTheme]'s [IconThemeData.size].
  ///
Ian Hickson's avatar
Ian Hickson committed
162 163
  /// This is not necessarily the same as the icon shown on the drawer item
  /// itself, which is controlled by the [icon] property.
164
  final Widget applicationIcon;
Ian Hickson's avatar
Ian Hickson committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

  /// A string to show in small print in the [AboutDialog].
  ///
  /// Typically this is a copyright notice.
  ///
  /// Defaults to the empty string.
  final String applicationLegalese;

  /// Widgets to add to the [AboutDialog] after the name, version, and legalese.
  ///
  /// This could include a link to a Web site, some descriptive text, credits,
  /// or other information to show in the about box.
  ///
  /// Defaults to nothing.
  final List<Widget> aboutBoxChildren;

181 182 183 184 185 186 187
  /// Whether this list tile is part of a vertically dense list.
  ///
  /// If this property is null, then its value is based on [ListTileTheme.dense].
  ///
  /// Dense list tiles default to a smaller height.
  final bool dense;

Ian Hickson's avatar
Ian Hickson committed
188 189 190
  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterial(context));
191
    assert(debugCheckHasMaterialLocalizations(context));
192
    return ListTile(
193
      leading: icon,
194 195 196 197
      title: child ?? Text(MaterialLocalizations.of(context).aboutListTileTitle(
        applicationName ?? _defaultApplicationName(context),
      )),
      dense: dense,
198
      onTap: () {
Ian Hickson's avatar
Ian Hickson committed
199 200 201 202 203 204
        showAboutDialog(
          context: context,
          applicationName: applicationName,
          applicationVersion: applicationVersion,
          applicationIcon: applicationIcon,
          applicationLegalese: applicationLegalese,
205
          children: aboutBoxChildren,
Ian Hickson's avatar
Ian Hickson committed
206
        );
207
      },
Ian Hickson's avatar
Ian Hickson committed
208 209 210 211 212 213 214 215 216
    );
  }
}

/// Displays an [AboutDialog], which describes the application and provides a
/// button to show licenses for software used by the application.
///
/// The arguments correspond to the properties on [AboutDialog].
///
217
/// If the application has a [Drawer], consider using [AboutListTile] instead
Ian Hickson's avatar
Ian Hickson committed
218 219 220 221
/// of calling this directly.
///
/// If you do not need an about box in your application, you should at least
/// provide an affordance to call [showLicensePage].
222 223 224
///
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
Ian Hickson's avatar
Ian Hickson committed
225
///
226 227
/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
/// [showDialog], the documentation for which discusses how it is used.
Ian Hickson's avatar
Ian Hickson committed
228 229 230 231
void showAboutDialog({
  @required BuildContext context,
  String applicationName,
  String applicationVersion,
232
  Widget applicationIcon,
Ian Hickson's avatar
Ian Hickson committed
233
  String applicationLegalese,
234
  List<Widget> children,
235
  bool useRootNavigator = true,
236
  RouteSettings routeSettings,
Ian Hickson's avatar
Ian Hickson committed
237
}) {
238
  assert(context != null);
239
  assert(useRootNavigator != null);
240
  showDialog<void>(
Ian Hickson's avatar
Ian Hickson committed
241
    context: context,
242
    useRootNavigator: useRootNavigator,
243
    builder: (BuildContext context) {
244
      return AboutDialog(
245 246 247 248 249 250
        applicationName: applicationName,
        applicationVersion: applicationVersion,
        applicationIcon: applicationIcon,
        applicationLegalese: applicationLegalese,
        children: children,
      );
251
    },
252
    routeSettings: routeSettings,
Ian Hickson's avatar
Ian Hickson committed
253 254 255 256 257 258
  );
}

/// Displays a [LicensePage], which shows licenses for software used by the
/// application.
///
259 260 261 262 263 264 265
/// The application arguments correspond to the properties on [LicensePage].
///
/// The `context` argument is used to look up the [Navigator] for the page.
///
/// The `useRootNavigator` argument is used to determine whether to push the
/// page to the [Navigator] furthest from or nearest to the given `context`. It
/// is `false` by default.
Ian Hickson's avatar
Ian Hickson committed
266
///
267
/// If the application has a [Drawer], consider using [AboutListTile] instead
Ian Hickson's avatar
Ian Hickson committed
268 269 270 271
/// of calling this directly.
///
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
/// [showLicensePage].
272 273 274
///
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
Ian Hickson's avatar
Ian Hickson committed
275 276 277 278
void showLicensePage({
  @required BuildContext context,
  String applicationName,
  String applicationVersion,
279
  Widget applicationIcon,
280
  String applicationLegalese,
281
  bool useRootNavigator = false,
Ian Hickson's avatar
Ian Hickson committed
282
}) {
283
  assert(context != null);
284 285
  assert(useRootNavigator != null);
  Navigator.of(context, rootNavigator: useRootNavigator).push(MaterialPageRoute<void>(
286
    builder: (BuildContext context) => LicensePage(
287 288
      applicationName: applicationName,
      applicationVersion: applicationVersion,
289
      applicationIcon: applicationIcon,
290
      applicationLegalese: applicationLegalese,
291
    ),
292
  ));
Ian Hickson's avatar
Ian Hickson committed
293 294 295 296 297 298 299
}

/// An about box. This is a dialog box with the application's icon, name,
/// version number, and copyright, plus a button to show licenses for software
/// used by the application.
///
/// To show an [AboutDialog], use [showAboutDialog].
300
///
301
/// If the application has a [Drawer], the [AboutListTile] widget can make the
302 303 304 305 306 307 308
/// process of showing an about dialog simpler.
///
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
/// [showLicensePage].
///
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
Ian Hickson's avatar
Ian Hickson committed
309 310 311 312 313 314
class AboutDialog extends StatelessWidget {
  /// Creates an about box.
  ///
  /// The arguments are all optional. The application name, if omitted, will be
  /// derived from the nearest [Title] widget. The version, icon, and legalese
  /// values default to the empty string.
315
  const AboutDialog({
Ian Hickson's avatar
Ian Hickson committed
316 317 318 319 320
    Key key,
    this.applicationName,
    this.applicationVersion,
    this.applicationIcon,
    this.applicationLegalese,
321
    this.children,
Ian Hickson's avatar
Ian Hickson committed
322 323 324 325 326
  }) : super(key: key);

  /// The name of the application.
  ///
  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
327
  /// Otherwise, defaults to [Platform.resolvedExecutable].
Ian Hickson's avatar
Ian Hickson committed
328 329 330 331 332 333 334 335 336 337 338 339
  final String applicationName;

  /// The version of this build of the application.
  ///
  /// This string is shown under the application name.
  ///
  /// Defaults to the empty string.
  final String applicationVersion;

  /// The icon to show next to the application name.
  ///
  /// By default no icon is shown.
340 341 342 343
  ///
  /// Typically this will be an [ImageIcon] widget. It should honor the
  /// [IconTheme]'s [IconThemeData.size].
  final Widget applicationIcon;
Ian Hickson's avatar
Ian Hickson committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361

  /// A string to show in small print.
  ///
  /// Typically this is a copyright notice.
  ///
  /// Defaults to the empty string.
  final String applicationLegalese;

  /// Widgets to add to the dialog box after the name, version, and legalese.
  ///
  /// This could include a link to a Web site, some descriptive text, credits,
  /// or other information to show in the about box.
  ///
  /// Defaults to nothing.
  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
362
    assert(debugCheckHasMaterialLocalizations(context));
Ian Hickson's avatar
Ian Hickson committed
363 364
    final String name = applicationName ?? _defaultApplicationName(context);
    final String version = applicationVersion ?? _defaultApplicationVersion(context);
365
    final Widget icon = applicationIcon ?? _defaultApplicationIcon(context);
366
    return AlertDialog(
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
      content: ListBody(
        children: <Widget>[
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              if (icon != null) IconTheme(data: Theme.of(context).iconTheme, child: icon),
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24.0),
                  child: ListBody(
                    children: <Widget>[
                      Text(name, style: Theme.of(context).textTheme.headline5),
                      Text(version, style: Theme.of(context).textTheme.bodyText2),
                      Container(height: 18.0),
                      Text(applicationLegalese ?? '', style: Theme.of(context).textTheme.caption),
                    ],
383 384
                  ),
                ),
385 386 387 388 389
              ),
            ],
          ),
          ...?children,
        ],
390
      ),
Ian Hickson's avatar
Ian Hickson committed
391
      actions: <Widget>[
392 393
        FlatButton(
          child: Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
Ian Hickson's avatar
Ian Hickson committed
394 395 396 397 398 399
          onPressed: () {
            showLicensePage(
              context: context,
              applicationName: applicationName,
              applicationVersion: applicationVersion,
              applicationIcon: applicationIcon,
400
              applicationLegalese: applicationLegalese,
Ian Hickson's avatar
Ian Hickson committed
401
            );
402
          },
Ian Hickson's avatar
Ian Hickson committed
403
        ),
404 405
        FlatButton(
          child: Text(MaterialLocalizations.of(context).closeButtonLabel),
Ian Hickson's avatar
Ian Hickson committed
406 407
          onPressed: () {
            Navigator.pop(context);
408
          },
Ian Hickson's avatar
Ian Hickson committed
409
        ),
410
      ],
411
      scrollable: true,
Ian Hickson's avatar
Ian Hickson committed
412 413 414 415 416 417 418
    );
  }
}

/// A page that shows licenses for software used by the application.
///
/// To show a [LicensePage], use [showLicensePage].
419
///
420
/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
421 422 423 424
/// a button that calls [showLicensePage].
///
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
425
class LicensePage extends StatefulWidget {
Ian Hickson's avatar
Ian Hickson committed
426 427 428 429 430
  /// Creates a page that shows licenses for software used by the application.
  ///
  /// The arguments are all optional. The application name, if omitted, will be
  /// derived from the nearest [Title] widget. The version and legalese values
  /// default to the empty string.
431 432 433
  ///
  /// The licenses shown on the [LicensePage] are those returned by the
  /// [LicenseRegistry] API, which can be used to add more licenses to the list.
Ian Hickson's avatar
Ian Hickson committed
434 435 436 437
  const LicensePage({
    Key key,
    this.applicationName,
    this.applicationVersion,
438
    this.applicationIcon,
439
    this.applicationLegalese,
Ian Hickson's avatar
Ian Hickson committed
440 441 442 443 444
  }) : super(key: key);

  /// The name of the application.
  ///
  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
445
  /// Otherwise, defaults to [Platform.resolvedExecutable].
Ian Hickson's avatar
Ian Hickson committed
446 447 448 449 450 451 452 453 454
  final String applicationName;

  /// The version of this build of the application.
  ///
  /// This string is shown under the application name.
  ///
  /// Defaults to the empty string.
  final String applicationVersion;

455 456 457 458 459 460 461 462
  /// The icon to show below the application name.
  ///
  /// By default no icon is shown.
  ///
  /// Typically this will be an [ImageIcon] widget. It should honor the
  /// [IconTheme]'s [IconThemeData.size].
  final Widget applicationIcon;

Ian Hickson's avatar
Ian Hickson committed
463 464 465 466 467 468 469
  /// A string to show in small print.
  ///
  /// Typically this is a copyright notice.
  ///
  /// Defaults to the empty string.
  final String applicationLegalese;

470
  @override
471
  _LicensePageState createState() => _LicensePageState();
472 473 474
}

class _LicensePageState extends State<LicensePage> {
Ian Hickson's avatar
Ian Hickson committed
475 476 477 478 479 480
  @override
  void initState() {
    super.initState();
    _initLicenses();
  }

481
  final List<Widget> _licenses = <Widget>[];
Ian Hickson's avatar
Ian Hickson committed
482 483
  bool _loaded = false;

484
  Future<void> _initLicenses() async {
485 486 487 488 489 490 491
    int debugFlowId = -1;
    assert(() {
      final Flow flow = Flow.begin();
      Timeline.timeSync('_initLicenses()', () { }, flow: flow);
      debugFlowId = flow.id;
      return true;
    }());
492
    await for (final LicenseEntry license in LicenseRegistry.licenses) {
493
      if (!mounted) {
494
        return;
495 496 497 498 499
      }
      assert(() {
        Timeline.timeSync('_initLicenses()', () { }, flow: Flow.step(debugFlowId));
        return true;
      }());
500 501
      final List<LicenseParagraph> paragraphs =
        await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
502
          license.paragraphs.toList,
503 504 505
          Priority.animation,
          debugLabel: 'License',
        );
506 507 508
      if (!mounted) {
        return;
      }
Ian Hickson's avatar
Ian Hickson committed
509
      setState(() {
510
        _licenses.add(const Padding(
511 512
          padding: EdgeInsets.symmetric(vertical: 18.0),
          child: Text(
Ian Hickson's avatar
Ian Hickson committed
513
            '🍀‬', // That's U+1F340. Could also use U+2766 (❦) if U+1F340 doesn't work everywhere.
514 515
            textAlign: TextAlign.center,
          ),
Ian Hickson's avatar
Ian Hickson committed
516
        ));
517
        _licenses.add(Container(
518
          decoration: const BoxDecoration(
519
            border: Border(bottom: BorderSide(width: 0.0))
520
          ),
521
          child: Text(
522
            license.packages.join(', '),
523
            style: const TextStyle(fontWeight: FontWeight.bold),
524 525
            textAlign: TextAlign.center,
          ),
526
        ));
527
        for (final LicenseParagraph paragraph in paragraphs) {
Ian Hickson's avatar
Ian Hickson committed
528
          if (paragraph.indent == LicenseParagraph.centeredIndent) {
529
            _licenses.add(Padding(
530
              padding: const EdgeInsets.only(top: 16.0),
531
              child: Text(
Ian Hickson's avatar
Ian Hickson committed
532
                paragraph.text,
533
                style: const TextStyle(fontWeight: FontWeight.bold),
534 535
                textAlign: TextAlign.center,
              ),
Ian Hickson's avatar
Ian Hickson committed
536 537 538
            ));
          } else {
            assert(paragraph.indent >= 0);
539 540
            _licenses.add(Padding(
              padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
541
              child: Text(paragraph.text),
Ian Hickson's avatar
Ian Hickson committed
542 543
            ));
          }
544
        }
Ian Hickson's avatar
Ian Hickson committed
545
      });
546
    }
Ian Hickson's avatar
Ian Hickson committed
547 548 549
    setState(() {
      _loaded = true;
    });
550 551 552 553
    assert(() {
      Timeline.timeSync('Build scheduled', () { }, flow: Flow.end(debugFlowId));
      return true;
    }());
554 555
  }

Ian Hickson's avatar
Ian Hickson committed
556 557
  @override
  Widget build(BuildContext context) {
558
    assert(debugCheckHasMaterialLocalizations(context));
559 560
    final String name = widget.applicationName ?? _defaultApplicationName(context);
    final String version = widget.applicationVersion ?? _defaultApplicationVersion(context);
561
    final Widget icon = widget.applicationIcon ?? _defaultApplicationIcon(context);
Hans Muller's avatar
Hans Muller committed
562
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
563 564 565
    return Scaffold(
      appBar: AppBar(
        title: Text(localizations.licensesPageTitle),
Ian Hickson's avatar
Ian Hickson committed
566
      ),
Hans Muller's avatar
Hans Muller committed
567 568
      // All of the licenses page text is English. We don't want localized text
      // or text direction.
569
      body: Localizations.override(
Hans Muller's avatar
Hans Muller committed
570 571
        locale: const Locale('en', 'US'),
        context: context,
572
        child: DefaultTextStyle(
Hans Muller's avatar
Hans Muller committed
573
          style: Theme.of(context).textTheme.caption,
574 575 576 577 578
          child: SafeArea(
            bottom: false,
            child: Scrollbar(
              child: ListView(
                padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
579
                children: <Widget>[
580
                  Text(name, style: Theme.of(context).textTheme.headline5, textAlign: TextAlign.center),
581
                  if (icon != null) IconTheme(data: Theme.of(context).iconTheme, child: icon),
582
                  Text(version, style: Theme.of(context).textTheme.bodyText2, textAlign: TextAlign.center),
583 584 585
                  Container(height: 18.0),
                  Text(widget.applicationLegalese ?? '', style: Theme.of(context).textTheme.caption, textAlign: TextAlign.center),
                  Container(height: 18.0),
586
                  Text('Powered by Flutter', style: Theme.of(context).textTheme.bodyText2, textAlign: TextAlign.center),
587 588 589 590 591 592 593 594
                  Container(height: 24.0),
                  ..._licenses,
                  if (!_loaded)
                    const Padding(
                      padding: EdgeInsets.symmetric(vertical: 24.0),
                      child: Center(
                        child: CircularProgressIndicator(),
                      ),
595
                    ),
596
                ],
597
              ),
Hans Muller's avatar
Hans Muller committed
598
            ),
599 600 601
          ),
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
602 603 604 605 606
    );
  }
}

String _defaultApplicationName(BuildContext context) {
607 608 609 610 611 612
  // This doesn't handle the case of the application's title dynamically
  // changing. In theory, we should make Title expose the current application
  // title using an InheritedWidget, and so forth. However, in practice, if
  // someone really wants their application title to change dynamically, they
  // can provide an explicit applicationName to the widgets defined in this
  // file, instead of relying on the default.
613
  final Title ancestorTitle = context.findAncestorWidgetOfExactType<Title>();
614
  return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
Ian Hickson's avatar
Ian Hickson committed
615 616 617 618 619 620 621
}

String _defaultApplicationVersion(BuildContext context) {
  // TODO(ianh): Get this from the embedder somehow.
  return '';
}

622
Widget _defaultApplicationIcon(BuildContext context) {
Ian Hickson's avatar
Ian Hickson committed
623 624 625
  // TODO(ianh): Get this from the embedder somehow.
  return null;
}