| // 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/cupertino.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'arc.dart'; |
| import 'colors.dart'; |
| import 'floating_action_button.dart'; |
| import 'icons.dart'; |
| import 'material_localizations.dart'; |
| import 'page.dart'; |
| import 'theme.dart'; |
| |
| /// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage |
| /// developers to be intentional about their [DefaultTextStyle]. |
| /// |
| /// In Material Design, most [Text] widgets are contained in [Material] widgets, |
| /// which sets a specific [DefaultTextStyle]. If you're seeing text that uses |
| /// this text style, consider putting your text in a [Material] widget (or |
| /// another widget that sets a [DefaultTextStyle]). |
| const TextStyle _errorTextStyle = TextStyle( |
| color: Color(0xD0FF0000), |
| fontFamily: 'monospace', |
| fontSize: 48.0, |
| fontWeight: FontWeight.w900, |
| decoration: TextDecoration.underline, |
| decorationColor: Color(0xFFFFFF00), |
| decorationStyle: TextDecorationStyle.double, |
| debugLabel: 'fallback style; consider putting your text in a Material', |
| ); |
| |
| /// 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. |
| /// |
| /// If a [Navigator] is created, 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 observer of the top-level [Navigator] (if |
| /// any) to perform [Hero] animations. |
| /// |
| /// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null, |
| /// and [builder] is not null, then no [Navigator] is created. |
| /// |
| /// 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. |
| /// * The Flutter Internationalization Tutorial, |
| /// <https://flutter.io/tutorials/internationalization/>. |
| class MaterialApp extends StatefulWidget { |
| /// Creates a MaterialApp. |
| /// |
| /// At least one of [home], [routes], [onGenerateRoute], or [builder] must be |
| /// non-null. 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. |
| const MaterialApp({ |
| Key key, |
| this.navigatorKey, |
| this.home, |
| this.routes = const <String, WidgetBuilder>{}, |
| this.initialRoute, |
| this.onGenerateRoute, |
| this.onUnknownRoute, |
| this.navigatorObservers = const <NavigatorObserver>[], |
| this.builder, |
| this.title = '', |
| this.onGenerateTitle, |
| this.color, |
| this.theme, |
| this.locale, |
| this.localizationsDelegates, |
| this.localeResolutionCallback, |
| this.supportedLocales = const <Locale>[Locale('en', 'US')], |
| 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(title != null), |
| assert(debugShowMaterialGrid != null), |
| assert(showPerformanceOverlay != null), |
| assert(checkerboardRasterCacheImages != null), |
| assert(checkerboardOffscreenLayers != null), |
| assert(showSemanticsDebugger != null), |
| assert(debugShowCheckedModeBanner != null), |
| super(key: key); |
| |
| /// {@macro flutter.widgets.widgetsApp.navigatorKey} |
| final GlobalKey<NavigatorState> navigatorKey; |
| |
| /// {@macro flutter.widgets.widgetsApp.home} |
| final Widget home; |
| |
| /// 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. |
| /// |
| /// {@macro flutter.widgets.widgetsApp.routes} |
| final Map<String, WidgetBuilder> routes; |
| |
| /// {@macro flutter.widgets.widgetsApp.initialRoute} |
| final String initialRoute; |
| |
| /// {@macro flutter.widgets.widgetsApp.onGenerateRoute} |
| final RouteFactory onGenerateRoute; |
| |
| /// {@macro flutter.widgets.widgetsApp.onUnknownRoute} |
| final RouteFactory onUnknownRoute; |
| |
| /// {@macro flutter.widgets.widgetsApp.navigatorObservers} |
| final List<NavigatorObserver> navigatorObservers; |
| |
| /// {@macro flutter.widgets.widgetsApp.builder} |
| /// |
| /// Material specific features such as [showDialog] and [showMenu], and widgets |
| /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly |
| /// function. |
| final TransitionBuilder builder; |
| |
| /// {@macro flutter.widgets.widgetsApp.title} |
| /// |
| /// This value is passed unmodified to [WidgetsApp.title]. |
| final String title; |
| |
| /// {@macro flutter.widgets.widgetsApp.onGenerateTitle} |
| /// |
| /// This value is passed unmodified to [WidgetsApp.onGenerateTitle]. |
| final GenerateAppTitle onGenerateTitle; |
| |
| /// The colors to use for the application's widgets. |
| final ThemeData theme; |
| |
| /// {@macro flutter.widgets.widgetsApp.color} |
| final Color color; |
| |
| /// {@macro flutter.widgets.widgetsApp.locale} |
| final Locale locale; |
| |
| /// {@macro flutter.widgets.widgetsApp.localizationsDelegates} |
| /// |
| /// Internationalized apps that require translations for one of the locales |
| /// listed in [GlobalMaterialLocalizations] should specify this paramter |
| /// and list the [supportedLocales] that the application can handle. |
| /// |
| /// ```dart |
| /// import 'package:flutter_localizations/flutter_localizations.dart'; |
| /// MaterialApp( |
| /// localizationsDelegates: [ |
| /// // ... app-specific localization delegate[s] here |
| /// GlobalMaterialLocalizations.delegate, |
| /// GlobalWidgetsLocalizations.delegate, |
| /// ], |
| /// supportedLocales: [ |
| /// const Locale('en', 'US'), // English |
| /// const Locale('he', 'IL'), // Hebrew |
| /// // ... other locales the app supports |
| /// ], |
| /// // ... |
| /// ) |
| /// ``` |
| /// |
| /// ## Adding localizations for a new locale |
| /// |
| /// The information that follows applies to the unusual case of an app |
| /// adding translations for a language not already supported by |
| /// [GlobalMaterialLocalizations]. |
| /// |
| /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations] |
| /// are included automatically. Apps can provide their own versions of these |
| /// localizations by creating implementations of |
| /// [LocalizationsDelegate<WidgetsLocalizations>] or |
| /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return |
| /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations]. |
| /// |
| /// For example: to add support to [MaterialLocalizations] for a |
| /// locale it doesn't already support, say `const Locale('foo', 'BR')`, |
| /// one could just extend [DefaultMaterialLocalizations]: |
| /// |
| /// ```dart |
| /// class FooLocalizations extends DefaultMaterialLocalizations { |
| /// FooLocalizations(Locale locale) : super(locale); |
| /// @override |
| /// String get okButtonLabel { |
| /// if (locale == const Locale('foo', 'BR')) |
| /// return 'foo'; |
| /// return super.okButtonLabel; |
| /// } |
| /// } |
| /// |
| /// ``` |
| /// |
| /// A `FooLocalizationsDelegate` is essentially just a method that constructs |
| /// a `FooLocalizations` object. We return a [SynchronousFuture] here because |
| /// no asynchronous work takes place upon "loading" the localizations object. |
| /// |
| /// ```dart |
| /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { |
| /// const FooLocalizationsDelegate(); |
| /// @override |
| /// Future<FooLocalizations> load(Locale locale) { |
| /// return SynchronousFuture(FooLocalizations(locale)); |
| /// } |
| /// @override |
| /// bool shouldReload(FooLocalizationsDelegate old) => false; |
| /// } |
| /// ``` |
| /// |
| /// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides |
| /// the automatically included delegate for [MaterialLocalizations] because |
| /// only the first delegate of each [LocalizationsDelegate.type] is used and |
| /// the automatically included delegates are added to the end of the app's |
| /// [localizationsDelegates] list. |
| /// |
| /// ```dart |
| /// MaterialApp( |
| /// localizationsDelegates: [ |
| /// const FooLocalizationsDelegate(), |
| /// ], |
| /// // ... |
| /// ) |
| /// ``` |
| /// See also: |
| /// |
| /// * [supportedLocales], which must be specified along with |
| /// [localizationsDelegates]. |
| /// * [GlobalMaterialLocalizations], a [localizationsDelegates] value |
| /// which provides material localizations for many languages. |
| /// * The Flutter Internationalization Tutorial, |
| /// <https://flutter.io/tutorials/internationalization/>. |
| final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates; |
| |
| /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback} |
| /// |
| /// This callback is passed along to the [WidgetsApp] built by this widget. |
| final LocaleResolutionCallback localeResolutionCallback; |
| |
| /// {@macro flutter.widgets.widgetsApp.supportedLocales} |
| /// |
| /// It is passed along unmodified to the [WidgetsApp] built by this widget. |
| /// |
| /// See also: |
| /// |
| /// * [localizationsDelegates], which must be specified for localized |
| /// applications. |
| /// * [GlobalMaterialLocalizations], a [localizationsDelegates] value |
| /// which provides material localizations for many languages. |
| /// * The Flutter Internationalization Tutorial, |
| /// <https://flutter.io/tutorials/internationalization/>. |
| final Iterable<Locale> supportedLocales; |
| |
| /// 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; |
| |
| /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner} |
| final bool debugShowCheckedModeBanner; |
| |
| /// 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() => _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 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 = HeroController(createRectTween: _createRectTween); |
| _updateNavigator(); |
| } |
| |
| @override |
| void didUpdateWidget(MaterialApp oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (widget.navigatorKey != oldWidget.navigatorKey) { |
| // If the Navigator changes, we have to create a new observer, because the |
| // old Navigator won't be disposed (and thus won't unregister with its |
| // observers) until after the new one has been created (because the |
| // Navigator has a GlobalKey). |
| _heroController = HeroController(createRectTween: _createRectTween); |
| } |
| _updateNavigator(); |
| } |
| |
| List<NavigatorObserver> _navigatorObservers; |
| |
| void _updateNavigator() { |
| if (widget.home != null || |
| widget.routes.isNotEmpty || |
| widget.onGenerateRoute != null || |
| widget.onUnknownRoute != null) { |
| _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers) |
| ..add(_heroController); |
| } else { |
| _navigatorObservers = null; |
| } |
| } |
| |
| RectTween _createRectTween(Rect begin, Rect end) { |
| return MaterialRectArcTween(begin: begin, end: end); |
| } |
| |
| // Combine the Localizations for Material with the ones contributed |
| // by the localizationsDelegates parameter, if any. Only the first delegate |
| // of a particular LocalizationsDelegate.type is loaded so the |
| // localizationsDelegate parameter can be used to override |
| // _MaterialLocalizationsDelegate. |
| Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* { |
| if (widget.localizationsDelegates != null) |
| yield* widget.localizationsDelegates; |
| yield DefaultMaterialLocalizations.delegate; |
| yield DefaultCupertinoLocalizations.delegate; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final ThemeData theme = widget.theme ?? ThemeData.fallback(); |
| Widget result = AnimatedTheme( |
| data: theme, |
| isMaterialAppTheme: true, |
| child: WidgetsApp( |
| key: GlobalObjectKey(this), |
| navigatorKey: widget.navigatorKey, |
| navigatorObservers: _navigatorObservers, |
| // TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved |
| // this can use type arguments again |
| pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => |
| MaterialPageRoute<dynamic>(settings: settings, builder: builder), |
| home: widget.home, |
| routes: widget.routes, |
| initialRoute: widget.initialRoute, |
| onGenerateRoute: widget.onGenerateRoute, |
| onUnknownRoute: widget.onUnknownRoute, |
| builder: widget.builder, |
| title: widget.title, |
| onGenerateTitle: widget.onGenerateTitle, |
| textStyle: _errorTextStyle, |
| // blue is the primary color of the default theme |
| color: widget.color ?? theme?.primaryColor ?? Colors.blue, |
| locale: widget.locale, |
| localizationsDelegates: _localizationsDelegates, |
| localeResolutionCallback: widget.localeResolutionCallback, |
| supportedLocales: widget.supportedLocales, |
| showPerformanceOverlay: widget.showPerformanceOverlay, |
| checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, |
| checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, |
| showSemanticsDebugger: widget.showSemanticsDebugger, |
| debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, |
| inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { |
| return FloatingActionButton( |
| child: const Icon(Icons.search), |
| onPressed: onPressed, |
| mini: true, |
| ); |
| }, |
| ) |
| ); |
| |
| assert(() { |
| if (widget.debugShowMaterialGrid) { |
| result = GridPaper( |
| color: const Color(0xE0F9BBE0), |
| interval: 8.0, |
| divisions: 2, |
| subdivisions: 1, |
| child: result, |
| ); |
| } |
| return true; |
| }()); |
| |
| return ScrollConfiguration( |
| behavior: _MaterialScrollBehavior(), |
| child: result, |
| ); |
| } |
| } |