| // Copyright 2015 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 'package:flutter/rendering.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'arc.dart'; |
| import 'colors.dart'; |
| import 'floating_action_button.dart'; |
| import 'icons.dart'; |
| import 'material_localizations.dart'; |
| import 'page.dart'; |
| import 'theme.dart'; |
| |
| /// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage |
| /// developers to be intentional about their [DefaultTextStyle]. |
| /// |
| /// In Material Design, most [Text] widgets are contained in [Material] widgets, |
| /// which sets a specific [DefaultTextStyle]. If you're seeing text that uses |
| /// this text style, consider putting your text in a [Material] widget (or |
| /// another widget that sets a [DefaultTextStyle]). |
| const TextStyle _errorTextStyle = const TextStyle( |
| color: const Color(0xD0FF0000), |
| fontFamily: 'monospace', |
| fontSize: 48.0, |
| fontWeight: FontWeight.w900, |
| decoration: TextDecoration.underline, |
| decorationColor: const Color(0xFFFFFF00), |
| decorationStyle: TextDecorationStyle.double, |
| debugLabel: 'fallback style; consider putting your text in a Material', |
| ); |
| |
| /// An application that uses material design. |
| /// |
| /// A convenience widget that wraps a number of widgets that are commonly |
| /// required for material design applications. It builds upon a [WidgetsApp] by |
| /// adding material-design specific functionality, such as [AnimatedTheme] and |
| /// [GridPaper]. |
| /// |
| /// The [MaterialApp] configures the top-level [Navigator] to search for routes |
| /// in the following order: |
| /// |
| /// 1. For the `/` route, the [home] property, if non-null, is used. |
| /// |
| /// 2. Otherwise, the [routes] table is used, if it has an entry for the route. |
| /// |
| /// 3. Otherwise, [onGenerateRoute] is called, if provided. It should return a |
| /// non-null value for any _valid_ route not handled by [home] and [routes]. |
| /// |
| /// 4. Finally if all else fails [onUnknownRoute] is called. |
| /// |
| /// At least one of these options must handle the `/` route, since it is used |
| /// when an invalid [initialRoute] is specified on startup (e.g. by another |
| /// application launching this one with an intent on Android; see |
| /// [Window.defaultRouteName]). |
| /// |
| /// This widget also configures the top-level [Navigator]'s observer to perform |
| /// [Hero] animations. |
| /// |
| /// See also: |
| /// |
| /// * [Scaffold], which provides standard app elements like an [AppBar] and a [Drawer]. |
| /// * [Navigator], which is used to manage the app's stack of pages. |
| /// * [MaterialPageRoute], which defines an app page that transitions in a material-specific way. |
| /// * [WidgetsApp], which defines the basic app elements but does not depend on the material library. |
| class MaterialApp extends StatefulWidget { |
| /// Creates a MaterialApp. |
| /// |
| /// At least one of [home], [routes], or [onGenerateRoute] must be given. If |
| /// only [routes] is given, it must include an entry for the |
| /// [Navigator.defaultRouteName] (`/`), since that is the route used when the |
| /// application is launched with an intent that specifies an otherwise |
| /// unsupported route. |
| /// |
| /// This class creates an instance of [WidgetsApp]. |
| /// |
| /// The boolean arguments, [routes], and [navigatorObservers], must not be null. |
| MaterialApp({ // can't be const because the asserts use methods on Map :-( |
| Key key, |
| this.title: '', |
| this.onGenerateTitle, |
| this.color, |
| this.theme, |
| this.home, |
| this.routes: const <String, WidgetBuilder>{}, |
| this.initialRoute, |
| this.onGenerateRoute, |
| this.onUnknownRoute, |
| this.locale, |
| this.localizationsDelegates, |
| this.localeResolutionCallback, |
| this.supportedLocales: const <Locale>[const Locale('en', 'US')], |
| this.navigatorObservers: const <NavigatorObserver>[], |
| this.debugShowMaterialGrid: false, |
| this.showPerformanceOverlay: false, |
| this.checkerboardRasterCacheImages: false, |
| this.checkerboardOffscreenLayers: false, |
| this.showSemanticsDebugger: false, |
| this.debugShowCheckedModeBanner: true |
| }) : assert(title != null), |
| assert(routes != null), |
| assert(navigatorObservers != null), |
| assert(debugShowMaterialGrid != null), |
| assert(showPerformanceOverlay != null), |
| assert(checkerboardRasterCacheImages != null), |
| assert(checkerboardOffscreenLayers != null), |
| assert(showSemanticsDebugger != null), |
| assert(debugShowCheckedModeBanner != null), |
| assert( |
| home == null || |
| !routes.containsKey(Navigator.defaultRouteName), |
| 'If the home property is specified, the routes table ' |
| 'cannot include an entry for "/", since it would be redundant.' |
| ), |
| assert( |
| home != null || |
| routes.containsKey(Navigator.defaultRouteName) || |
| onGenerateRoute != null || |
| onUnknownRoute != null, |
| 'Either the home property must be specified, ' |
| 'or the routes table must include an entry for "/", ' |
| 'or there must be on onGenerateRoute callback specified, ' |
| 'or there must be an onUnknownRoute callback specified, ' |
| 'because otherwise there is nothing to fall back on if the ' |
| 'app is started with an intent that specifies an unknown route.' |
| ), |
| super(key: key); |
| |
| /// A one-line description used by the device to identify the app for the user. |
| /// |
| /// On Android the titles appear above the task manager's app snapshots which are |
| /// displayed when the user presses the "recent apps" button. Similarly, on |
| /// iOS the titles appear in the App Switcher when the user double presses the |
| /// home button. |
| /// |
| /// To provide a localized title instead, use [onGenerateTitle]. |
| /// |
| /// This value is passed unmodified to [WidgetsApp.title]. |
| final String title; |
| |
| /// If non-null this callback is called to produce the app's |
| /// title string, otherwise [title] is used. |
| /// |
| /// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s |
| /// [Localizations] widget so that this callback can be used to produce a |
| /// localized title. |
| /// |
| /// This callback function must not return null. |
| /// |
| /// The [onGenerateTitle] callback is called each time the [MaterialApp] |
| /// rebuilds. |
| /// |
| /// This value is passed unmodified to [WidgetsApp.onGenerateTitle]. |
| final GenerateAppTitle onGenerateTitle; |
| |
| /// The colors to use for the application's widgets. |
| final ThemeData theme; |
| |
| /// The widget for the default route of the app ([Navigator.defaultRouteName], |
| /// which is `/`). |
| /// |
| /// This is the route that is displayed first when the application is started |
| /// normally, unless [initialRoute] is specified. It's also the route that's |
| /// displayed if the [initialRoute] can't be displayed. |
| /// |
| /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code |
| /// that sets the [home] argument in the constructor, you can use a [Builder] |
| /// widget to get a [BuildContext]. |
| /// |
| /// If [home] is specified, then [routes] must not include an entry for `/`, |
| /// as [home] takes its place. |
| final Widget home; |
| |
| /// The primary color to use for the application in the operating system |
| /// interface. |
| /// |
| /// For example, on Android this is the color used for the application in the |
| /// application switcher. |
| final Color color; |
| |
| /// The application's top-level routing table. |
| /// |
| /// When a named route is pushed with [Navigator.pushNamed], the route name is |
| /// looked up in this map. If the name is present, the associated |
| /// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs |
| /// an appropriate transition, including [Hero] animations, to the new route. |
| /// |
| /// If the app only has one page, then you can specify it using [home] instead. |
| /// |
| /// If [home] is specified, then it implies an entry in this table for the |
| /// [Navigator.defaultRouteName] route (`/`), and it is an error to |
| /// redundantly provide such a route in the [routes] table. |
| /// |
| /// If a route is requested that is not specified in this table (or by |
| /// [home]), then the [onGenerateRoute] callback is called to build the page |
| /// instead. |
| final Map<String, WidgetBuilder> routes; |
| |
| /// The name of the first route to show. |
| /// |
| /// Defaults to [Window.defaultRouteName], which may be overridden by the code |
| /// that launched the application. |
| /// |
| /// If the route contains slashes, then it is treated as a "deep link", and |
| /// before this route is pushed, the routes leading to this one are pushed |
| /// also. For example, if the route was `/a/b/c`, then the app would start |
| /// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order. |
| /// |
| /// If any part of this process fails to generate routes, then the |
| /// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead |
| /// (`/`). This can happen if the app is started with an intent that specifies |
| /// a non-existent route. |
| /// |
| /// See also: |
| /// |
| /// * [Navigator.initialRoute], which is used to implement this property. |
| /// * [Navigator.push], for pushing additional routes. |
| /// * [Navigator.pop], for removing a route from the stack. |
| final String initialRoute; |
| |
| /// The route generator callback used when the app is navigated to a |
| /// named route. |
| /// |
| /// This is used if [routes] does not contain the requested route. |
| /// |
| /// If this returns null when building the routes to handle the specified |
| /// [initialRoute], then all the routes are discarded and |
| /// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute]. |
| /// |
| /// During normal app operation, the [onGenerateRoute] callback will only be |
| /// applied to route names pushed by the application, and so should never |
| /// return null. |
| final RouteFactory onGenerateRoute; |
| |
| /// Called when [onGenerateRoute] fails to generate a route, except for the |
| /// [initialRoute]. |
| /// |
| /// This callback is typically used for error handling. For example, this |
| /// callback might always generate a "not found" page that describes the route |
| /// that wasn't found. |
| /// |
| /// The default implementation pushes a route that displays an ugly error |
| /// message. |
| final RouteFactory onUnknownRoute; |
| |
| /// The initial locale for this app's [Localizations] widget. |
| /// |
| /// If the `locale` is null the system's locale value is used. |
| final Locale locale; |
| |
| /// The delegates for this app's [Localizations] widget. |
| /// |
| /// The delegates collectively define all of the localized resources |
| /// for this application's [Localizations] widget. |
| /// |
| /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations] |
| /// are included automatically. Apps can provide their own versions of these |
| /// localizations by creating implementations of |
| /// [LocalizationsDelegate<WidgetsLocalizations>] or |
| /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return |
| /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations]. |
| /// |
| /// For example: to add support to [MaterialLocalizations] for a |
| /// locale it doesn't already support, say `const Locale('foo', 'BR')`, |
| /// one could just extend [DefaultMaterialLocalizations]: |
| /// |
| /// ```dart |
| /// class FooLocalizations extends DefaultMaterialLocalizations { |
| /// FooLocalizations(Locale locale) : super(locale); |
| /// @override |
| /// String get okButtonLabel { |
| /// if (locale == const Locale('foo', 'BR')) |
| /// return 'foo'; |
| /// return super.okButtonLabel; |
| /// } |
| /// } |
| /// |
| /// ``` |
| /// |
| /// A `FooLocalizationsDelegate` is essentially just a method that constructs |
| /// a `FooLocalizations` object. We return a [SynchronousFuture] here because |
| /// no asynchronous work takes place upon "loading" the localizations object. |
| /// |
| /// ```dart |
| /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { |
| /// const FooLocalizationsDelegate(); |
| /// @override |
| /// Future<FooLocalizations> load(Locale locale) { |
| /// return new SynchronousFuture(new FooLocalizations(locale)); |
| /// } |
| /// @override |
| /// bool shouldReload(FooLocalizationsDelegate old) => false; |
| /// } |
| /// ``` |
| /// |
| /// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides |
| /// the automatically included delegate for [MaterialLocalizations] because |
| /// only the first delegate of each [LocalizationsDelegate.type] is used and |
| /// the automatically included delegates are added to the end of the app's |
| /// [localizationsDelegates] list. |
| /// |
| /// ```dart |
| /// new MaterialApp( |
| /// localizationsDelegates: [ |
| /// const FooLocalizationsDelegate(), |
| /// ], |
| /// // ... |
| /// ) |
| /// ``` |
| final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates; |
| |
| /// This callback is responsible for choosing the app's locale |
| /// when the app is started, and when the user changes the |
| /// device's locale. |
| /// |
| /// The returned value becomes the locale of this app's [Localizations] |
| /// widget. The callback's `locale` parameter is the device's locale when |
| /// the app started, or the device locale the user selected after the app was |
| /// started. The callback's `supportedLocales` parameter is just the value |
| /// [supportedLocales]. |
| /// |
| /// An app could use this callback to substitute locales based on the app's |
| /// intended audience. If the device's OS provides a prioritized |
| /// list of locales, this callback could be used to defer to it. |
| /// |
| /// If the callback is null then the resolved locale is: |
| /// - The callback's `locale` parameter if it's equal to a supported locale. |
| /// - The first supported locale with the same [Locale.languageCode] as the |
| /// callback's `locale` parameter. |
| /// - The first supported locale. |
| /// |
| /// This callback is passed along to the [WidgetsApp] built by this widget. |
| final LocaleResolutionCallback localeResolutionCallback; |
| |
| /// The list of locales that this app has been localized for. |
| /// |
| /// By default only the American English locale is supported. Apps should |
| /// configure this list to match the locales they support. |
| /// |
| /// This list must not null. It's default value is just |
| /// `[const Locale('en', 'US')]`. It is passed along unmodified to the |
| /// [WidgetsApp] built by this widget. |
| /// |
| /// The order of the list matters. By default, if the device's locale doesn't |
| /// exactly match a locale in [supportedLocales] then the first locale in |
| /// [supportedLocales] with a matching [Locale.languageCode] is used. If that |
| /// fails then the first locale in [supportedLocales] is used. The default |
| /// locale resolution algorithm can be overridden with [localeResolutionCallback]. |
| /// |
| /// The material widgets include translations for locales with the following |
| /// language codes: |
| /// ``` |
| /// ar - Arabic |
| /// de - German |
| /// en - English |
| /// es - Spanish |
| /// fa - Farsi (Persian) |
| /// fr - French |
| /// he - Hebrew |
| /// it - Italian |
| /// ja - Japanese |
| /// ps - Pashto |
| /// pt - Portugese |
| /// ru - Russian |
| /// sd - Sindhi |
| /// ur - Urdu |
| /// zh - Chinese (simplified) |
| /// ``` |
| final Iterable<Locale> supportedLocales; |
| |
| /// Turns on a performance overlay. |
| /// |
| /// See also: |
| /// |
| /// * <https://flutter.io/debugging/#performanceoverlay> |
| final bool showPerformanceOverlay; |
| |
| /// Turns on checkerboarding of raster cache images. |
| final bool checkerboardRasterCacheImages; |
| |
| /// Turns on checkerboarding of layers rendered to offscreen bitmaps. |
| final bool checkerboardOffscreenLayers; |
| |
| /// Turns on an overlay that shows the accessibility information |
| /// reported by the framework. |
| final bool showSemanticsDebugger; |
| |
| /// Turns on a little "SLOW MODE" banner in checked mode to indicate |
| /// that the app is in checked mode. This is on by default (in |
| /// checked mode), to turn it off, set the constructor argument to |
| /// false. In release mode this has no effect. |
| /// |
| /// To get this banner in your application if you're not using |
| /// WidgetsApp, include a [CheckedModeBanner] widget in your app. |
| /// |
| /// This banner is intended to deter people from complaining that your |
| /// app is slow when it's in checked mode. In checked mode, Flutter |
| /// enables a large number of expensive diagnostics to aid in |
| /// development, and so performance in checked mode is not |
| /// representative of what will happen in release mode. |
| final bool debugShowCheckedModeBanner; |
| |
| /// The list of observers for the [Navigator] created for this app. |
| final List<NavigatorObserver> navigatorObservers; |
| |
| /// Turns on a [GridPaper] overlay that paints a baseline grid |
| /// Material apps. |
| /// |
| /// Only available in checked mode. |
| /// |
| /// See also: |
| /// |
| /// * <https://material.google.com/layout/metrics-keylines.html> |
| final bool debugShowMaterialGrid; |
| |
| @override |
| _MaterialAppState createState() => new _MaterialAppState(); |
| } |
| |
| class _MaterialScrollBehavior extends ScrollBehavior { |
| @override |
| TargetPlatform getPlatform(BuildContext context) { |
| return Theme.of(context).platform; |
| } |
| |
| @override |
| Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) { |
| // When modifying this function, consider modifying the implementation in |
| // the base class as well. |
| switch (getPlatform(context)) { |
| case TargetPlatform.iOS: |
| return child; |
| case TargetPlatform.android: |
| case TargetPlatform.fuchsia: |
| return new GlowingOverscrollIndicator( |
| child: child, |
| axisDirection: axisDirection, |
| color: Theme.of(context).accentColor, |
| ); |
| } |
| return null; |
| } |
| } |
| |
| class _MaterialAppState extends State<MaterialApp> { |
| HeroController _heroController; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _heroController = new HeroController(createRectTween: _createRectTween); |
| } |
| |
| // Combine the Localizations for Material with the ones contributed |
| // by the localizationsDelegates parameter, if any. Only the first delegate |
| // of a particular LocalizationsDelegate.type is loaded so the |
| // localizationsDelegate parameter can be used to override |
| // _MaterialLocalizationsDelegate. |
| Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* { |
| if (widget.localizationsDelegates != null) |
| yield* widget.localizationsDelegates; |
| yield DefaultMaterialLocalizations.delegate; |
| } |
| |
| RectTween _createRectTween(Rect begin, Rect end) { |
| return new MaterialRectArcTween(begin: begin, end: end); |
| } |
| |
| Route<dynamic> _onGenerateRoute(RouteSettings settings) { |
| final String name = settings.name; |
| WidgetBuilder builder; |
| if (name == Navigator.defaultRouteName && widget.home != null) |
| builder = (BuildContext context) => widget.home; |
| else |
| builder = widget.routes[name]; |
| if (builder != null) { |
| return new MaterialPageRoute<dynamic>( |
| builder: builder, |
| settings: settings, |
| ); |
| } |
| if (widget.onGenerateRoute != null) |
| return widget.onGenerateRoute(settings); |
| return null; |
| } |
| |
| Route<dynamic> _onUnknownRoute(RouteSettings settings) { |
| assert(() { |
| if (widget.onUnknownRoute == null) { |
| throw new FlutterError( |
| 'Could not find a generator for route $settings in the $runtimeType.\n' |
| 'Generators for routes are searched for in the following order:\n' |
| ' 1. For the "/" route, the "home" property, if non-null, is used.\n' |
| ' 2. Otherwise, the "routes" table is used, if it has an entry for ' |
| 'the route.\n' |
| ' 3. Otherwise, onGenerateRoute is called. It should return a ' |
| 'non-null value for any valid route not handled by "home" and "routes".\n' |
| ' 4. Finally if all else fails onUnknownRoute is called.\n' |
| 'Unfortunately, onUnknownRoute was not set.' |
| ); |
| } |
| return true; |
| }()); |
| final Route<dynamic> result = widget.onUnknownRoute(settings); |
| assert(() { |
| if (result == null) { |
| throw new FlutterError( |
| 'The onUnknownRoute callback returned null.\n' |
| 'When the $runtimeType requested the route $settings from its ' |
| 'onUnknownRoute callback, the callback returned null. Such callbacks ' |
| 'must never return null.' |
| ); |
| } |
| return true; |
| }()); |
| return result; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final ThemeData theme = widget.theme ?? new ThemeData.fallback(); |
| Widget result = new AnimatedTheme( |
| data: theme, |
| isMaterialAppTheme: true, |
| child: new WidgetsApp( |
| key: new GlobalObjectKey(this), |
| title: widget.title, |
| onGenerateTitle: widget.onGenerateTitle, |
| textStyle: _errorTextStyle, |
| // blue is the primary color of the default theme |
| color: widget.color ?? theme?.primaryColor ?? Colors.blue, |
| navigatorObservers: |
| new List<NavigatorObserver>.from(widget.navigatorObservers) |
| ..add(_heroController), |
| initialRoute: widget.initialRoute, |
| onGenerateRoute: _onGenerateRoute, |
| onUnknownRoute: _onUnknownRoute, |
| locale: widget.locale, |
| localizationsDelegates: _localizationsDelegates, |
| localeResolutionCallback: widget.localeResolutionCallback, |
| supportedLocales: widget.supportedLocales, |
| showPerformanceOverlay: widget.showPerformanceOverlay, |
| checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, |
| checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, |
| showSemanticsDebugger: widget.showSemanticsDebugger, |
| debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, |
| inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { |
| return new FloatingActionButton( |
| child: const Icon(Icons.search), |
| onPressed: onPressed, |
| mini: true, |
| ); |
| }, |
| ) |
| ); |
| |
| assert(() { |
| if (widget.debugShowMaterialGrid) { |
| result = new GridPaper( |
| color: const Color(0xE0F9BBE0), |
| interval: 8.0, |
| divisions: 2, |
| subdivisions: 1, |
| child: result, |
| ); |
| } |
| return true; |
| }()); |
| |
| return new ScrollConfiguration( |
| behavior: new _MaterialScrollBehavior(), |
| child: result, |
| ); |
| } |
| } |