app.dart 9.63 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/rendering.dart';
6
import 'package:flutter/widgets.dart';
7

8
import 'arc.dart';
9
import 'colors.dart';
10
import 'overscroll_indicator.dart';
11
import 'page.dart';
12
import 'theme.dart';
13

14 15
export 'dart:ui' show Locale;

16 17 18 19 20
const TextStyle _errorTextStyle = const TextStyle(
  color: const Color(0xD0FF0000),
  fontFamily: 'monospace',
  fontSize: 48.0,
  fontWeight: FontWeight.w900,
21
  decoration: TextDecoration.underline,
22
  decorationColor: const Color(0xFFFFFF00),
23 24 25
  decorationStyle: TextDecorationStyle.double
);

26 27 28
/// An application that uses material design.
///
/// A convenience widget that wraps a number of widgets that are commonly
29
/// required for material design applications. It builds upon a
30 31
/// [WidgetsApp] by adding material-design specific functionality, such as
/// [AnimatedTheme] and [GridPaper]. This widget also configures the top-level
32
/// [Navigator]'s observer to perform [Hero] animations.
33 34 35 36 37 38
///
/// See also:
///
///  * [WidgetsApp]
///  * [Scaffold]
///  * [MaterialPageRoute]
39 40
class MaterialApp extends StatefulWidget {

41 42 43 44 45 46
  /// 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] (`'/'`).
  ///
47
  /// This class creates an instance of [WidgetsApp].
Adam Barth's avatar
Adam Barth committed
48
  MaterialApp({
49
    Key key,
50
    this.title,
51
    this.color,
52 53 54
    this.theme,
    this.home,
    this.routes: const <String, WidgetBuilder>{},
55
    this.initialRoute,
56 57
    this.onGenerateRoute,
    this.onLocaleChanged,
58
    this.debugShowMaterialGrid: false,
59 60 61 62
    this.showPerformanceOverlay: false,
    this.showSemanticsDebugger: false,
    this.debugShowCheckedModeBanner: true
  }) : super(key: key) {
Ian Hickson's avatar
Ian Hickson committed
63
    assert(debugShowMaterialGrid != null);
64 65 66
    assert(routes != null);
    assert(!routes.containsKey(Navigator.defaultRouteName) || (home == null));
    assert(routes.containsKey(Navigator.defaultRouteName) || (home != null) || (onGenerateRoute != null));
67 68 69
 }
  /// A one-line description of this app for use in the window manager.
  final String title;
70

71
  /// The colors to use for the application's widgets.
72
  final ThemeData theme;
73

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
  /// The widget for the default route of the app
  /// ([Navigator.defaultRouteName], which is `'/'`).
  ///
  /// This is the page that is displayed first when the application is
  /// started normally.
  ///
  /// 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 this is not specified, then either the route with name `'/'`
  /// must be given in [routes], or the [onGenerateRoute] callback
  /// must be able to build a widget for that route.
  final Widget home;

90 91 92 93 94 95 96
  /// 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;

97 98 99 100 101 102
  /// 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.
103 104 105 106 107 108 109
  ///
  /// If the app only has one page, then you can specify it using [home] instead.
  ///
  /// If [home] is specified, then it is an error to provide a route
  /// in this map for the [Navigator.defaultRouteName] route (`'/'`).
  ///
  /// If a route is requested that is not specified in this table (or
110
  /// by [home]), then the [onGenerateRoute] callback is called to
111
  /// build the page instead.
Ian Hickson's avatar
Ian Hickson committed
112 113
  final Map<String, WidgetBuilder> routes;

114 115 116 117 118
  /// The name of the first route to show.
  ///
  /// Defaults to [Window.defaultRouteName].
  final String initialRoute;

119 120 121 122
  /// The route generator callback used when the app is navigated to a
  /// named route.
  final RouteFactory onGenerateRoute;

123
  /// Callback that is called when the operating system changes the
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
  /// current locale.
  final LocaleChangedCallback onLocaleChanged;

  /// Turns on a performance overlay.
  /// https://flutter.io/debugging/#performanceoverlay
  final bool showPerformanceOverlay;

  /// 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;

150 151
  /// Turns on a [GridPaper] overlay that paints a baseline grid
  /// Material apps:
152
  /// https://material.google.com/layout/metrics-keylines.html
153
  /// Only available in checked mode.
Ian Hickson's avatar
Ian Hickson committed
154
  final bool debugShowMaterialGrid;
155

156
  @override
Adam Barth's avatar
Adam Barth committed
157
  _MaterialAppState createState() => new _MaterialAppState();
158 159
}

160 161 162
class _ScrollLikeCupertinoDelegate extends ScrollConfigurationDelegate {
  const _ScrollLikeCupertinoDelegate();

163
  @override
164 165 166 167 168 169 170
  TargetPlatform get platform => TargetPlatform.iOS;

  @override
  ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: TargetPlatform.iOS);

  @override
  bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
171
}
172

173
class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate {
174
  const _ScrollLikeMountainViewDelegate(this.platform);
175 176

  @override
177
  final TargetPlatform platform;
178 179 180 181

  @override
  ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: TargetPlatform.android);

182 183 184 185 186 187 188 189 190 191 192 193 194 195
  ScrollableEdge _overscrollIndicatorEdge(ScrollableEdge edge) {
    switch (edge) {
      case ScrollableEdge.leading:
        return ScrollableEdge.trailing;
      case ScrollableEdge.trailing:
        return ScrollableEdge.leading;
      case ScrollableEdge.both:
        return ScrollableEdge.none;
      case ScrollableEdge.none:
        return ScrollableEdge.both;
    }
    return ScrollableEdge.both;
  }

196
  @override
197 198 199 200 201 202 203 204
  Widget wrapScrollWidget(BuildContext context, Widget scrollWidget) {
    // Only introduce an overscroll indicator for the edges of the scrollable
    // that aren't already clamped.
    return new OverscrollIndicator(
      edge: _overscrollIndicatorEdge(ClampOverscrolls.of(context)?.edge),
      child: scrollWidget
    );
  }
205 206 207 208

  @override
  bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
}
209

210
class _MaterialAppState extends State<MaterialApp> {
211 212 213 214 215 216 217 218 219 220 221
  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);
  }
222

223 224 225 226 227 228 229 230 231 232 233 234 235 236
  Route<dynamic> _onGenerateRoute(RouteSettings settings) {
    WidgetBuilder builder = config.routes[settings.name];
    if (builder == null && config.home != null && settings.name == Navigator.defaultRouteName)
      builder = (BuildContext context) => config.home;
    if (builder != null) {
      return new MaterialPageRoute<Null>(
        builder: builder,
        settings: settings
      );
    }
    if (config.onGenerateRoute != null)
      return config.onGenerateRoute(settings);
    return null;
  }
237

238 239 240
  ScrollConfigurationDelegate _getScrollDelegate(TargetPlatform platform) {
    switch (platform) {
      case TargetPlatform.android:
241 242 243
        return const _ScrollLikeMountainViewDelegate(TargetPlatform.android);
      case TargetPlatform.fuchsia:
        return const _ScrollLikeMountainViewDelegate(TargetPlatform.fuchsia);
244
      case TargetPlatform.iOS:
245
        return const _ScrollLikeCupertinoDelegate();
246 247 248 249
    }
    return null;
  }

250
  @override
251
  Widget build(BuildContext context) {
252
    ThemeData theme = config.theme ?? new ThemeData.fallback();
253 254
    Widget result = new AnimatedTheme(
      data: theme,
255
      isMaterialAppTheme: true,
256
      child: new WidgetsApp(
257
        key: new GlobalObjectKey(this),
258 259
        title: config.title,
        textStyle: _errorTextStyle,
260 261
        // blue[500] is the primary color of the default theme
        color: config.color ?? theme?.primaryColor ?? Colors.blue[500],
262
        navigatorObserver: _heroController,
263
        initialRoute: config.initialRoute,
264 265 266 267 268 269
        onGenerateRoute: _onGenerateRoute,
        onLocaleChanged: config.onLocaleChanged,
        showPerformanceOverlay: config.showPerformanceOverlay,
        showSemanticsDebugger: config.showSemanticsDebugger,
        debugShowCheckedModeBanner: config.debugShowCheckedModeBanner
      )
270
    );
271

Ian Hickson's avatar
Ian Hickson committed
272 273 274 275 276 277 278 279 280 281 282 283
    assert(() {
      if (config.debugShowMaterialGrid) {
        result = new GridPaper(
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
          divisions: 2,
          subDivisions: 1,
          child: result
        );
      }
      return true;
    });
284

285
    return new ScrollConfiguration(
286
      delegate: _getScrollDelegate(theme.platform),
287 288
      child: result
    );
289
  }
290
}