| // 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 'page.dart'; |
| import 'theme.dart'; |
| |
| export 'dart:ui' show Locale; |
| |
| 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 |
| ); |
| |
| /// 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.color, |
| this.theme, |
| this.home, |
| this.routes: const <String, WidgetBuilder>{}, |
| this.initialRoute, |
| this.onGenerateRoute, |
| this.onUnknownRoute, |
| this.onLocaleChanged, |
| this.navigatorObservers: const <NavigatorObserver>[], |
| this.debugShowMaterialGrid: false, |
| this.showPerformanceOverlay: false, |
| this.checkerboardRasterCacheImages: false, |
| this.checkerboardOffscreenLayers: false, |
| this.showSemanticsDebugger: false, |
| this.debugShowCheckedModeBanner: true |
| }) : 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 of this app for use in the window manager. |
| final String title; |
| |
| /// 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], |
| /// [LocaleQuery.of], etc, in the code 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; |
| |
| /// Callback that is called when the operating system changes the |
| /// current locale. |
| final LocaleChangedCallback onLocaleChanged; |
| |
| /// 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); |
| } |
| |
| 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, |
| 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, |
| onLocaleChanged: widget.onLocaleChanged, |
| showPerformanceOverlay: widget.showPerformanceOverlay, |
| checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, |
| checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, |
| showSemanticsDebugger: widget.showSemanticsDebugger, |
| debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, |
| ) |
| ); |
| |
| 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, |
| ); |
| } |
| } |