// 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 'dart:ui' as ui; 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.dev/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.darkTheme, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, 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; /// Default visual properties, like colors fonts and shapes, for this app's /// material widgets. /// /// A second [darkTheme] [ThemeData] value, which is used when the underlying /// platform requests a "dark mode" UI, can also be specified. /// /// The default value of this property is the value of [ThemeData.light()]. /// /// See also: /// /// * [MediaQueryData.platformBrightness], which indicates the platform's /// desired brightness and is used to automatically toggle between [theme] /// and [darkTheme] in [MaterialApp]. /// * [ThemeData.brightness], which indicates the [Brightness] of a theme's /// colors. final ThemeData theme; /// The [ThemeData] to use when the platform specifically requests a dark /// themed UI. /// /// Host platforms such as Android Pie can request a system-wide "dark mode" /// when entering battery saver mode. /// /// When the host platform requests a [Brightness.dark] mode, you may want to /// supply a [ThemeData.brightness] that's also [Brightness.dark]. /// /// Uses [theme] instead when null. Defaults to the value of /// [ThemeData.light()] when both [darkTheme] and [theme] are null. /// /// See also: /// /// * [MediaQueryData.platformBrightness], which indicates the platform's /// desired brightness and is used to automatically toggle between [theme] /// and [darkTheme] in [MaterialApp]. /// * [ThemeData.brightness], which is typically set to the value of /// [MediaQueryData.platformBrightness]. final ThemeData darkTheme; /// {@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 parameter /// 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.dev/tutorials/internationalization/>. final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates; /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback} /// /// This callback is passed along to the [WidgetsApp] built by this widget. final LocaleListResolutionCallback localeListResolutionCallback; /// {@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.dev/tutorials/internationalization/>. final Iterable<Locale> supportedLocales; /// Turns on a performance overlay. /// /// See also: /// /// * <https://flutter.dev/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.io/design/layout/spacing-methods.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 = const <NavigatorObserver>[]; } } 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) { Widget result = WidgetsApp( key: GlobalObjectKey(this), navigatorKey: widget.navigatorKey, navigatorObservers: _navigatorObservers, pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => MaterialPageRoute<T>(settings: settings, builder: builder), home: widget.home, routes: widget.routes, initialRoute: widget.initialRoute, onGenerateRoute: widget.onGenerateRoute, onUnknownRoute: widget.onUnknownRoute, builder: (BuildContext context, Widget child) { // Use a light theme, dark theme, or fallback theme. ThemeData theme; final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context); if (platformBrightness == ui.Brightness.dark && widget.darkTheme != null) { theme = widget.darkTheme; } else if (widget.theme != null) { theme = widget.theme; } else { theme = ThemeData.fallback(); } return AnimatedTheme( data: theme, isMaterialAppTheme: true, child: widget.builder != null ? Builder( builder: (BuildContext context) { // Why are we surrounding a builder with a builder? // // The widget.builder may contain code that invokes // Theme.of(), which should return the theme we selected // above in AnimatedTheme. However, if we invoke // widget.builder() directly as the child of AnimatedTheme // then there is no Context separating them, and the // widget.builder() will not find the theme. Therefore, we // surround widget.builder with yet another builder so that // a context separates them and Theme.of() correctly // resolves to the theme we passed to AnimatedTheme. return widget.builder(context, child); }, ) : child, ); }, title: widget.title, onGenerateTitle: widget.onGenerateTitle, textStyle: _errorTextStyle, // The color property is always pulled from the light theme, even if dark // mode is activated. This was done to simplify the technical details // of switching themes and it was deemed acceptable because this color // property is only used on old Android OSes to color the app bar in // Android's switcher UI. // // blue is the primary color of the default theme color: widget.color ?? widget.theme?.primaryColor ?? Colors.blue, locale: widget.locale, localizationsDelegates: _localizationsDelegates, localeResolutionCallback: widget.localeResolutionCallback, localeListResolutionCallback: widget.localeListResolutionCallback, 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, ); } }