
* Prefix and Suffix support for TextFields * Adding Tests * Removing spurious newline. * Fixing a small problem with the test * Code review changes * Code Review Changes * Review Changes * Export the new StrokeJoin enum * Added example for line styles, and enabled line join styles. * Reverting inadvertent change to main.dart. * Updated due to code review of engine code * Removed example. * Added arguments to named routes, with test. * Fixing some formatting * Fixing Navigator.pop for named routes. * Fixing comment. * Simplifying test. * Fixing new -> const for Text object. * Tiny text change (also to kick a new Travis build) * Added a more realistic test case. * Reverting unintentional iml changes. * Fixing trailing newline * Removing some changes that snuck in.
398 lines
15 KiB
Dart
398 lines
15 KiB
Dart
// 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,
|
|
);
|
|
}
|
|
}
|