2019-07-15 12:09:10 +01:00
// 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.
import ' dart:async ' ;
//import 'dart:developer' show Timeline, Flow;
import ' dart:developer ' as dev ;
import ' dart:io ' show Platform ;
import ' package:flutter/foundation.dart ' ;
import ' package:flutter/scheduler.dart ' ;
import ' package:flutter/widgets.dart ' hide Flow ;
import ' package:flutter/material.dart ' ;
/// A [ListTile] that shows an about box.
///
/// This widget is often added to an app's [Drawer]. When tapped it shows
/// an about box dialog with [showAboutDialog].
///
/// The about box will include a button that shows licenses for software used by
/// the application. The licenses shown are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
///
/// If your application does not have a [Drawer], you should provide an
/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
class AboutListTile extends StatelessWidget {
/// Creates a list tile for showing 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.
const AboutListTile ( {
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.
/// Otherwise, defaults to [Platform.resolvedExecutable].
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.
///
/// Typically this will be an [ImageIcon] widget. It should honor the
/// [IconTheme]'s [IconThemeData.size].
///
/// This is not necessarily the same as the icon shown on the drawer item
/// itself, which is controlled by the [icon] property.
final Widget applicationIcon ;
/// 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 ) ) ;
assert ( debugCheckHasMaterialLocalizations ( context ) ) ;
return ListTile (
leading: icon ,
title: child ? ?
2019-08-21 14:53:52 +01:00
Text ( MaterialLocalizations . of ( context ) . aboutListTileTitle (
applicationName ? ? _defaultApplicationName ( context ) ) ) ,
2019-07-15 12:09:10 +01:00
onTap: ( ) {
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].
///
/// If the application has a [Drawer], consider using [AboutListTile] instead
/// 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].
///
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
///
/// The `context` argument is passed to [showDialog], the documentation for
/// which discusses how it is used.
void showAboutDialog ( {
@ required BuildContext context ,
String applicationName ,
String applicationVersion ,
Widget applicationIcon ,
String applicationLegalese ,
List < Widget > children ,
} ) {
assert ( context ! = null ) ;
showDialog < void > (
context: context ,
builder: ( BuildContext context ) {
return 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].
///
/// If the application has a [Drawer], consider using [AboutListTile] instead
/// of calling this directly.
///
/// 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.
void showLicensePage ( {
@ required BuildContext context ,
String applicationName ,
String applicationVersion ,
Widget applicationIcon ,
String applicationLegalese ,
} ) {
assert ( context ! = null ) ;
2019-08-21 14:53:52 +01:00
Navigator . push (
context ,
MaterialPageRoute < void > (
builder: ( BuildContext context ) = > LicensePage (
applicationName: applicationName ,
applicationVersion: applicationVersion ,
applicationLegalese: applicationLegalese ,
) ) ) ;
2019-07-15 12:09:10 +01:00
}
/// 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].
///
/// If the application has a [Drawer], the [AboutListTile] widget can make the
/// 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.
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.
const AboutDialog ( {
Key key ,
this . applicationName ,
this . applicationVersion ,
this . applicationIcon ,
this . applicationLegalese ,
this . children ,
} ) : super ( key: key ) ;
/// The name of the application.
///
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
/// Otherwise, defaults to [Platform.resolvedExecutable].
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.
///
/// Typically this will be an [ImageIcon] widget. It should honor the
/// [IconTheme]'s [IconThemeData.size].
final Widget applicationIcon ;
/// 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 ) {
assert ( debugCheckHasMaterialLocalizations ( context ) ) ;
final String name = applicationName ? ? _defaultApplicationName ( context ) ;
2019-08-21 14:53:52 +01:00
final String version =
applicationVersion ? ? _defaultApplicationVersion ( context ) ;
2019-07-15 12:09:10 +01:00
final Widget icon = applicationIcon ? ? _defaultApplicationIcon ( context ) ;
List < Widget > body = < Widget > [ ] ;
2019-08-21 14:53:52 +01:00
if ( icon ! = null ) {
body . add (
2019-09-02 12:57:14 +01:00
IconTheme ( data: const IconThemeData ( size: 45.0 ) , child: icon ) ,
2019-08-21 14:53:52 +01:00
) ;
}
2019-07-15 12:09:10 +01:00
body . add ( Expanded (
child: Padding (
padding: const EdgeInsets . symmetric ( horizontal: 24.0 ) ,
child: ListBody (
children: < Widget > [
2019-08-21 10:16:46 +01:00
Container (
child: Text ( name , style: Theme . of ( context ) . textTheme . title ) ,
) ,
2019-07-15 12:09:10 +01:00
Text ( version , style: Theme . of ( context ) . textTheme . body1 ) ,
2019-08-21 14:53:52 +01:00
Text ( applicationLegalese ? ? ' ' ,
style: Theme . of ( context ) . textTheme . caption ) ,
2019-07-15 12:09:10 +01:00
] ,
) ,
) ,
) ) ;
body = < Widget > [
Row (
crossAxisAlignment: CrossAxisAlignment . start ,
children: body ,
) ,
] ;
2019-08-21 14:53:52 +01:00
if ( children ! = null ) body . addAll ( children ) ;
2019-07-15 12:09:10 +01:00
return AlertDialog (
content: SingleChildScrollView (
child: ListBody ( children: body ) ,
) ,
actions: < Widget > [
FlatButton (
2019-08-21 14:53:52 +01:00
child:
Text ( MaterialLocalizations . of ( context ) . viewLicensesButtonLabel ) ,
2019-07-15 12:09:10 +01:00
onPressed: ( ) {
showLicensePage (
context: context ,
applicationName: applicationName ,
applicationVersion: applicationVersion ,
applicationIcon: applicationIcon ,
applicationLegalese: applicationLegalese ,
) ;
} ,
) ,
FlatButton (
child: Text ( MaterialLocalizations . of ( context ) . closeButtonLabel ) ,
onPressed: ( ) {
Navigator . pop ( context ) ;
} ,
) ,
] ,
) ;
}
}
/// A page that shows licenses for software used by the application.
///
/// To show a [LicensePage], use [showLicensePage].
///
/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] 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.
class LicensePage extends StatefulWidget {
/// 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.
///
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
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.
/// Otherwise, defaults to [Platform.resolvedExecutable].
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 ;
@ override
_LicensePageState createState ( ) = > _LicensePageState ( ) ;
}
class _LicensePageState extends State < LicensePage > {
@ override
void initState ( ) {
super . initState ( ) ;
_initLicenses ( ) ;
}
final List < Widget > _licenses = < Widget > [ ] ;
bool _loaded = false ;
Future < void > _initLicenses ( ) async {
int debugFlowId = - 1 ;
assert ( ( ) {
final dev . Flow flow = dev . Flow . begin ( ) ;
2019-08-21 14:53:52 +01:00
dev . Timeline . timeSync ( ' _initLicenses() ' , ( ) { } , flow: flow ) ;
2019-07-15 12:09:10 +01:00
debugFlowId = flow . id ;
return true ;
} ( ) ) ;
await for ( LicenseEntry license in LicenseRegistry . licenses ) {
if ( ! mounted ) {
return ;
}
assert ( ( ) {
2019-08-21 14:53:52 +01:00
dev . Timeline . timeSync ( ' _initLicenses() ' , ( ) { } ,
flow: dev . Flow . step ( debugFlowId ) ) ;
2019-07-15 12:09:10 +01:00
return true ;
} ( ) ) ;
final List < LicenseParagraph > paragraphs =
2019-08-21 14:53:52 +01:00
await SchedulerBinding . instance . scheduleTask < List < LicenseParagraph > > (
2019-07-15 12:09:10 +01:00
license . paragraphs . toList ,
Priority . animation ,
debugLabel: ' License ' ,
) ;
setState ( ( ) {
_licenses . add ( const Padding (
padding: EdgeInsets . symmetric ( vertical: 18.0 ) ,
child: Text (
' 🍀 ' , // That's U+1F340. Could also use U+2766 (❦) if U+1F340 doesn't work everywhere.
textAlign: TextAlign . center ,
) ,
) ) ;
_licenses . add ( Container (
decoration: const BoxDecoration (
2019-08-21 14:53:52 +01:00
border: Border ( bottom: BorderSide ( width: 0.0 ) ) ) ,
2019-07-15 12:09:10 +01:00
child: Text (
license . packages . join ( ' , ' ) ,
style: const TextStyle ( fontWeight: FontWeight . bold ) ,
textAlign: TextAlign . center ,
) ,
) ) ;
for ( LicenseParagraph paragraph in paragraphs ) {
if ( paragraph . indent = = LicenseParagraph . centeredIndent ) {
_licenses . add ( Padding (
padding: const EdgeInsets . only ( top: 16.0 ) ,
child: Text (
paragraph . text ,
style: const TextStyle ( fontWeight: FontWeight . bold ) ,
textAlign: TextAlign . center ,
) ,
) ) ;
} else {
assert ( paragraph . indent > = 0 ) ;
_licenses . add ( Padding (
2019-08-21 14:53:52 +01:00
padding: EdgeInsetsDirectional . only (
top: 8.0 , start: 16.0 * paragraph . indent ) ,
2019-07-15 12:09:10 +01:00
child: Text ( paragraph . text ) ,
) ) ;
}
}
} ) ;
}
setState ( ( ) {
_loaded = true ;
} ) ;
assert ( ( ) {
2019-08-21 14:53:52 +01:00
dev . Timeline . timeSync ( ' Build scheduled ' , ( ) { } ,
flow: dev . Flow . end ( debugFlowId ) ) ;
2019-07-15 12:09:10 +01:00
return true ;
} ( ) ) ;
}
@ override
Widget build ( BuildContext context ) {
assert ( debugCheckHasMaterialLocalizations ( context ) ) ;
2019-08-21 14:53:52 +01:00
final String name =
widget . applicationName ? ? _defaultApplicationName ( context ) ;
final String version =
widget . applicationVersion ? ? _defaultApplicationVersion ( context ) ;
final MaterialLocalizations localizations =
MaterialLocalizations . of ( context ) ;
2019-07-15 12:09:10 +01:00
final List < Widget > contents = < Widget > [
2019-08-21 14:53:52 +01:00
Text ( name ,
style: Theme . of ( context ) . textTheme . headline ,
textAlign: TextAlign . center ) ,
Text ( version ,
style: Theme . of ( context ) . textTheme . body1 ,
textAlign: TextAlign . center ) ,
2019-07-15 12:09:10 +01:00
Container ( height: 18.0 ) ,
2019-08-21 14:53:52 +01:00
Text ( widget . applicationLegalese ? ? ' ' ,
style: Theme . of ( context ) . textTheme . caption ,
textAlign: TextAlign . center ) ,
2019-07-15 12:09:10 +01:00
Container ( height: 18.0 ) ,
2019-08-21 14:53:52 +01:00
Text ( ' Powered by Flutter ' ,
style: Theme . of ( context ) . textTheme . body1 ,
textAlign: TextAlign . center ) ,
2019-07-15 12:09:10 +01:00
Container ( height: 24.0 ) ,
] ;
contents . addAll ( _licenses ) ;
if ( ! _loaded ) {
contents . add ( const Padding (
padding: EdgeInsets . symmetric ( vertical: 24.0 ) ,
child: Center (
child: CircularProgressIndicator ( ) ,
) ,
) ) ;
}
return Scaffold (
appBar: AppBar (
title: Text ( localizations . licensesPageTitle ) ,
) ,
// All of the licenses page text is English. We don't want localized text
// or text direction.
body: Localizations . override (
locale: const Locale ( ' en ' , ' US ' ) ,
context: context ,
child: DefaultTextStyle (
style: Theme . of ( context ) . textTheme . caption ,
child: SafeArea (
bottom: false ,
child: Scrollbar (
child: ListView (
2019-08-21 14:53:52 +01:00
padding:
const EdgeInsets . symmetric ( horizontal: 8.0 , vertical: 12.0 ) ,
2019-07-15 12:09:10 +01:00
children: contents ,
) ,
) ,
) ,
) ,
) ,
) ;
}
}
String _defaultApplicationName ( BuildContext context ) {
// 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.
final Title ancestorTitle = context . ancestorWidgetOfExactType ( Title ) ;
2019-08-21 14:53:52 +01:00
return ancestorTitle ? . title ? ?
Platform . resolvedExecutable . split ( Platform . pathSeparator ) . last ;
2019-07-15 12:09:10 +01:00
}
String _defaultApplicationVersion ( BuildContext context ) {
// TODO(ianh): Get this from the embedder somehow.
return ' ' ;
}
Widget _defaultApplicationIcon ( BuildContext context ) {
// TODO(ianh): Get this from the embedder somehow.
return null ;
}