291 lines
9.6 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/widgets.dart';
import 'arc.dart';
import 'colors.dart';
import 'overscroll_indicator.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]. This widget also configures the top-level
/// [Navigator]'s observer to perform [Hero] animations.
///
/// See also:
///
/// * [WidgetsApp]
/// * [Scaffold]
/// * [MaterialPageRoute]
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] (`'/'`).
///
/// This class creates an instance of [WidgetsApp].
MaterialApp({
Key key,
this.title,
this.color,
this.theme,
this.home,
this.routes: const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onLocaleChanged,
this.debugShowMaterialGrid: false,
this.showPerformanceOverlay: false,
this.showSemanticsDebugger: false,
this.debugShowCheckedModeBanner: true
}) : super(key: key) {
assert(debugShowMaterialGrid != null);
assert(routes != null);
assert(!routes.containsKey(Navigator.defaultRouteName) || (home == null));
assert(routes.containsKey(Navigator.defaultRouteName) || (home != null) || (onGenerateRoute != null));
}
/// 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 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;
/// 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 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
/// 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].
final String initialRoute;
/// The route generator callback used when the app is navigated to a
/// named route.
final RouteFactory onGenerateRoute;
/// Callback that is called when the operating system changes the
/// 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;
/// Turns on a [GridPaper] overlay that paints a baseline grid
/// Material apps:
/// https://material.google.com/layout/metrics-keylines.html
/// Only available in checked mode.
final bool debugShowMaterialGrid;
@override
_MaterialAppState createState() => new _MaterialAppState();
}
class _ScrollLikeCupertinoDelegate extends ScrollConfigurationDelegate {
const _ScrollLikeCupertinoDelegate();
@override
TargetPlatform get platform => TargetPlatform.iOS;
@override
ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: TargetPlatform.iOS);
@override
bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
}
class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate {
const _ScrollLikeMountainViewDelegate(this.platform);
@override
final TargetPlatform platform;
@override
ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: TargetPlatform.android);
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;
}
@override
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
);
}
@override
bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
}
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) {
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;
}
ScrollConfigurationDelegate _getScrollDelegate(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
return const _ScrollLikeMountainViewDelegate(TargetPlatform.android);
case TargetPlatform.fuchsia:
return const _ScrollLikeMountainViewDelegate(TargetPlatform.fuchsia);
case TargetPlatform.iOS:
return const _ScrollLikeCupertinoDelegate();
}
return null;
}
@override
Widget build(BuildContext context) {
ThemeData theme = config.theme ?? new ThemeData.fallback();
Widget result = new AnimatedTheme(
data: theme,
isMaterialAppTheme: true,
child: new WidgetsApp(
key: new GlobalObjectKey(this),
title: config.title,
textStyle: _errorTextStyle,
// blue[500] is the primary color of the default theme
color: config.color ?? theme?.primaryColor ?? Colors.blue[500],
navigatorObserver: _heroController,
initialRoute: config.initialRoute,
onGenerateRoute: _onGenerateRoute,
onLocaleChanged: config.onLocaleChanged,
showPerformanceOverlay: config.showPerformanceOverlay,
showSemanticsDebugger: config.showSemanticsDebugger,
debugShowCheckedModeBanner: config.debugShowCheckedModeBanner
)
);
assert(() {
if (config.debugShowMaterialGrid) {
result = new GridPaper(
color: const Color(0xE0F9BBE0),
interval: 8.0,
divisions: 2,
subDivisions: 1,
child: result
);
}
return true;
});
return new ScrollConfiguration(
delegate: _getScrollDelegate(theme.platform),
child: result
);
}
}