blob: 2a73b71acb1bbad7f83c9178d45e5063a99c18dd [file] [log] [blame]
// 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,
);
}
}