about.dart 16.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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:io' show Platform;
Ian Hickson's avatar
Ian Hickson committed
7

Ian Hickson's avatar
Ian Hickson committed
8
import 'package:flutter/widgets.dart';
9
import 'package:flutter/foundation.dart';
Ian Hickson's avatar
Ian Hickson committed
10 11 12 13 14

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

23
/// A [ListTile] that shows an about box.
Ian Hickson's avatar
Ian Hickson committed
24
///
25 26
/// 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
27 28
///
/// The about box will include a button that shows licenses for software used by
29 30
/// 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
31 32 33
///
/// If your application does not have a [Drawer], you should provide an
/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
34 35
class AboutListTile extends StatelessWidget {
  /// Creates a list tile for showing an about box.
Ian Hickson's avatar
Ian Hickson committed
36 37 38 39
  ///
  /// 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.
40
  const AboutListTile({
Ian Hickson's avatar
Ian Hickson committed
41 42 43 44 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
    Key key,
    this.icon: const Icon(null),
    this.child,
    this.applicationName,
    this.applicationVersion,
    this.applicationIcon,
    this.applicationLegalese,
    this.aboutBoxChildren
  }) : 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.
71
  /// Otherwise, defaults to [Platform.resolvedExecutable].
Ian Hickson's avatar
Ian Hickson committed
72 73 74 75 76 77 78 79 80 81 82 83 84
  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.
  ///
85 86 87
  /// Typically this will be an [ImageIcon] widget. It should honor the
  /// [IconTheme]'s [IconThemeData.size].
  ///
Ian Hickson's avatar
Ian Hickson committed
88 89
  /// This is not necessarily the same as the icon shown on the drawer item
  /// itself, which is controlled by the [icon] property.
90
  final Widget applicationIcon;
Ian Hickson's avatar
Ian Hickson committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

  /// 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;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterial(context));
110 111
    return new ListTile(
      leading: icon,
Hans Muller's avatar
Hans Muller committed
112 113
      title: child ??
        new Text(MaterialLocalizations.of(context).aboutListTileTitle(applicationName ?? _defaultApplicationName(context))),
114
      onTap: () {
Ian Hickson's avatar
Ian Hickson committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
        showAboutDialog(
          context: context,
          applicationName: applicationName,
          applicationVersion: applicationVersion,
          applicationIcon: applicationIcon,
          applicationLegalese: applicationLegalese,
          children: aboutBoxChildren
        );
      }
    );
  }
}

/// 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].
///
133
/// If the application has a [Drawer], consider using [AboutListTile] instead
Ian Hickson's avatar
Ian Hickson committed
134 135 136 137
/// 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].
138 139 140
///
/// 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
141 142 143
///
/// The `context` argument is passed to [showDialog], the documentation for
/// which discusses how it is used.
Ian Hickson's avatar
Ian Hickson committed
144 145 146 147
void showAboutDialog({
  @required BuildContext context,
  String applicationName,
  String applicationVersion,
148
  Widget applicationIcon,
Ian Hickson's avatar
Ian Hickson committed
149 150 151
  String applicationLegalese,
  List<Widget> children
}) {
152
  showDialog<Null>(
Ian Hickson's avatar
Ian Hickson committed
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    context: context,
    child: new AboutDialog(
      applicationName: applicationName,
      applicationVersion: applicationVersion,
      applicationIcon: applicationIcon,
      applicationLegalese: applicationLegalese,
      children: children
    )
  );
}

/// Displays a [LicensePage], which shows licenses for software used by the
/// application.
///
/// The arguments correspond to the properties on [LicensePage].
///
169
/// If the application has a [Drawer], consider using [AboutListTile] instead
Ian Hickson's avatar
Ian Hickson committed
170 171 172 173
/// of calling this directly.
///
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
/// [showLicensePage].
174 175 176
///
/// 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
177 178 179 180
void showLicensePage({
  @required BuildContext context,
  String applicationName,
  String applicationVersion,
181
  Widget applicationIcon,
Ian Hickson's avatar
Ian Hickson committed
182 183
  String applicationLegalese
}) {
184 185 186 187 188 189 190 191 192
  // TODO(ianh): remove pop once https://github.com/flutter/flutter/issues/4667 is fixed
  Navigator.pop(context);
  Navigator.push(context, new MaterialPageRoute<Null>(
    builder: (BuildContext context) => new LicensePage(
      applicationName: applicationName,
      applicationVersion: applicationVersion,
      applicationLegalese: applicationLegalese
    )
  ));
Ian Hickson's avatar
Ian Hickson committed
193 194 195 196 197 198 199
}

/// 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].
200
///
201
/// If the application has a [Drawer], the [AboutListTile] widget can make the
202 203 204 205 206 207 208
/// 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
209 210 211 212 213 214
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.
215
  const AboutDialog({
Ian Hickson's avatar
Ian Hickson committed
216 217 218 219 220
    Key key,
    this.applicationName,
    this.applicationVersion,
    this.applicationIcon,
    this.applicationLegalese,
221
    this.children,
Ian Hickson's avatar
Ian Hickson committed
222 223 224 225 226
  }) : super(key: key);

  /// The name of the application.
  ///
  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
227
  /// Otherwise, defaults to [Platform.resolvedExecutable].
Ian Hickson's avatar
Ian Hickson committed
228 229 230 231 232 233 234 235 236 237 238 239
  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.
240 241 242 243
  ///
  /// 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
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

  /// 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) {
    final String name = applicationName ?? _defaultApplicationName(context);
    final String version = applicationVersion ?? _defaultApplicationVersion(context);
264
    final Widget icon = applicationIcon ?? _defaultApplicationIcon(context);
Ian Hickson's avatar
Ian Hickson committed
265
    List<Widget> body = <Widget>[];
266
    if (icon != null)
267
      body.add(new IconTheme(data: const IconThemeData(size: 48.0), child: icon));
268
    body.add(new Expanded(
Ian Hickson's avatar
Ian Hickson committed
269
      child: new Padding(
270
        padding: const EdgeInsets.symmetric(horizontal: 24.0),
271
        child: new ListBody(
Ian Hickson's avatar
Ian Hickson committed
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
          children: <Widget>[
            new Text(name, style: Theme.of(context).textTheme.headline),
            new Text(version, style: Theme.of(context).textTheme.body1),
            new Container(height: 18.0),
            new Text(applicationLegalese ?? '', style: Theme.of(context).textTheme.caption)
          ]
        )
      )
    ));
    body = <Widget>[
      new Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: body
      ),
    ];
    if (children != null)
      body.addAll(children);
289
    return new AlertDialog(
290
      content: new SingleChildScrollView(
291
        child: new ListBody(children: body),
Ian Hickson's avatar
Ian Hickson committed
292 293 294
      ),
      actions: <Widget>[
        new FlatButton(
295
          child: new Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
Ian Hickson's avatar
Ian Hickson committed
296 297 298 299 300 301 302 303 304 305 306
          onPressed: () {
            showLicensePage(
              context: context,
              applicationName: applicationName,
              applicationVersion: applicationVersion,
              applicationIcon: applicationIcon,
              applicationLegalese: applicationLegalese
            );
          }
        ),
        new FlatButton(
307
          child: new Text(MaterialLocalizations.of(context).closeButtonLabel),
Ian Hickson's avatar
Ian Hickson committed
308 309 310 311 312 313 314 315 316 317 318 319
          onPressed: () {
            Navigator.pop(context);
          }
        ),
      ]
    );
  }
}

/// A page that shows licenses for software used by the application.
///
/// To show a [LicensePage], use [showLicensePage].
320
///
321
/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
322 323 324 325
/// 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.
326
class LicensePage extends StatefulWidget {
Ian Hickson's avatar
Ian Hickson committed
327 328 329 330 331
  /// 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.
332 333 334
  ///
  /// 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
335 336 337 338 339 340 341 342 343 344
  const LicensePage({
    Key key,
    this.applicationName,
    this.applicationVersion,
    this.applicationLegalese
  }) : super(key: key);

  /// The name of the application.
  ///
  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
345
  /// Otherwise, defaults to [Platform.resolvedExecutable].
Ian Hickson's avatar
Ian Hickson committed
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  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;

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

362 363 364 365 366 367
  @override
  _LicensePageState createState() => new _LicensePageState();
}

class _LicensePageState extends State<LicensePage> {

Ian Hickson's avatar
Ian Hickson committed
368 369 370 371 372 373
  @override
  void initState() {
    super.initState();
    _initLicenses();
  }

374
  final List<Widget> _licenses = <Widget>[];
Ian Hickson's avatar
Ian Hickson committed
375 376 377 378
  bool _loaded = false;

  Future<Null> _initLicenses() async {
    await for (LicenseEntry license in LicenseRegistry.licenses) {
379 380
      if (!mounted)
        return;
Ian Hickson's avatar
Ian Hickson committed
381
      setState(() {
382
        _licenses.add(const Padding(
383
          padding: const EdgeInsets.symmetric(vertical: 18.0),
384
          child: const Text(
Ian Hickson's avatar
Ian Hickson committed
385 386 387 388
            '🍀‬', // That's U+1F340. Could also use U+2766 (❦) if U+1F340 doesn't work everywhere.
            textAlign: TextAlign.center
          )
        ));
389
        _licenses.add(new Container(
390 391
          decoration: const BoxDecoration(
            border: const Border(bottom: const BorderSide(width: 0.0))
392 393 394
          ),
          child: new Text(
            license.packages.join(', '),
395
            style: const TextStyle(fontWeight: FontWeight.bold),
396 397 398
            textAlign: TextAlign.center
          )
        ));
Ian Hickson's avatar
Ian Hickson committed
399 400 401
        for (LicenseParagraph paragraph in license.paragraphs) {
          if (paragraph.indent == LicenseParagraph.centeredIndent) {
            _licenses.add(new Padding(
402
              padding: const EdgeInsets.only(top: 16.0),
Ian Hickson's avatar
Ian Hickson committed
403 404
              child: new Text(
                paragraph.text,
405
                style: const TextStyle(fontWeight: FontWeight.bold),
Ian Hickson's avatar
Ian Hickson committed
406 407 408 409 410 411
                textAlign: TextAlign.center
              )
            ));
          } else {
            assert(paragraph.indent >= 0);
            _licenses.add(new Padding(
412
              padding: new EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
Ian Hickson's avatar
Ian Hickson committed
413 414 415
              child: new Text(paragraph.text)
            ));
          }
416
        }
Ian Hickson's avatar
Ian Hickson committed
417
      });
418
    }
Ian Hickson's avatar
Ian Hickson committed
419 420 421
    setState(() {
      _loaded = true;
    });
422 423
  }

Ian Hickson's avatar
Ian Hickson committed
424 425
  @override
  Widget build(BuildContext context) {
426 427
    final String name = widget.applicationName ?? _defaultApplicationName(context);
    final String version = widget.applicationVersion ?? _defaultApplicationVersion(context);
Hans Muller's avatar
Hans Muller committed
428
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
429 430 431 432
    final List<Widget> contents = <Widget>[
      new Text(name, style: Theme.of(context).textTheme.headline, textAlign: TextAlign.center),
      new Text(version, style: Theme.of(context).textTheme.body1, textAlign: TextAlign.center),
      new Container(height: 18.0),
433
      new Text(widget.applicationLegalese ?? '', style: Theme.of(context).textTheme.caption, textAlign: TextAlign.center),
434 435 436 437 438
      new Container(height: 18.0),
      new Text('Powered by Flutter', style: Theme.of(context).textTheme.body1, textAlign: TextAlign.center),
      new Container(height: 24.0),
    ];
    contents.addAll(_licenses);
Ian Hickson's avatar
Ian Hickson committed
439
    if (!_loaded) {
440
      contents.add(const Padding(
441
        padding: const EdgeInsets.symmetric(vertical: 24.0),
442 443
        child: const Center(
          child: const CircularProgressIndicator()
Ian Hickson's avatar
Ian Hickson committed
444 445 446
        )
      ));
    }
Ian Hickson's avatar
Ian Hickson committed
447 448
    return new Scaffold(
      appBar: new AppBar(
Hans Muller's avatar
Hans Muller committed
449
        title: new Text(localizations.licensesPageTitle),
Ian Hickson's avatar
Ian Hickson committed
450
      ),
Hans Muller's avatar
Hans Muller committed
451 452 453 454 455 456 457 458 459 460 461 462 463
      // All of the licenses page text is English. We don't want localized text
      // or text direction.
      body: new Localizations.override(
        locale: const Locale('en', 'US'),
        context: context,
        child: new DefaultTextStyle(
          style: Theme.of(context).textTheme.caption,
          child: new Scrollbar(
            child: new ListView(
              padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
              shrinkWrap: true,
              children: contents,
            ),
464 465 466
          ),
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
467 468 469 470 471
    );
  }
}

String _defaultApplicationName(BuildContext context) {
472
  final Title ancestorTitle = context.ancestorWidgetOfExactType(Title);
473
  return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
Ian Hickson's avatar
Ian Hickson committed
474 475 476 477 478 479 480
}

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

481
Widget _defaultApplicationIcon(BuildContext context) {
Ian Hickson's avatar
Ian Hickson committed
482 483 484
  // TODO(ianh): Get this from the embedder somehow.
  return null;
}