From 6a2390125669cc7a51842617e690bd3af58609a2 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Sat, 5 Mar 2016 23:55:29 -0800 Subject: [PATCH] Factor out non-Material parts of MaterialApp into WidgetsApp Fixes https://github.com/flutter/flutter/issues/1346 --- packages/flutter/lib/src/material/app.dart | 239 ++++----------------- packages/flutter/lib/src/widgets/app.dart | 223 +++++++++++++++++++ packages/flutter/lib/widgets.dart | 1 + 3 files changed, 267 insertions(+), 196 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/app.dart diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index f9832022a68..d958ee709bb 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -2,13 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; -import 'dart:ui' as ui show WindowPadding, window; - import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'colors.dart'; import 'page.dart'; import 'theme.dart'; @@ -21,204 +18,75 @@ const TextStyle _errorTextStyle = const TextStyle( fontWeight: FontWeight.w900, textAlign: TextAlign.right, decoration: TextDecoration.underline, - decorationColor: const Color(0xFFFF00), + decorationColor: const Color(0xFFFFFF00), decorationStyle: TextDecorationStyle.double ); -AssetBundle _initDefaultBundle() { - if (rootBundle != null) - return rootBundle; - return new NetworkAssetBundle(Uri.base); -} -final AssetBundle _defaultBundle = _initDefaultBundle(); - -class RouteArguments { - const RouteArguments({ this.context }); - final BuildContext context; -} -typedef Widget RouteBuilder(RouteArguments args); - -typedef Future LocaleChangedCallback(Locale locale); - -class MaterialApp extends StatefulComponent { +class MaterialApp extends WidgetsApp { MaterialApp({ Key key, - this.title, - this.theme, - this.routes: const {}, - this.onGenerateRoute, - this.onLocaleChanged, + String title, + ThemeData theme, + Map routes: const {}, + RouteFactory onGenerateRoute, + LocaleChangedCallback onLocaleChanged, this.debugShowMaterialGrid: false, - this.showPerformanceOverlay: false, - this.showSemanticsDebugger: false, - this.debugShowCheckedModeBanner: true - }) : super(key: key) { - assert(routes != null); - assert(routes.containsKey(Navigator.defaultRouteName) || onGenerateRoute != null); + bool showPerformanceOverlay: false, + bool showSemanticsDebugger: false, + bool debugShowCheckedModeBanner: true + }) : theme = theme, + super( + key: key, + title: title, + textStyle: _errorTextStyle, + color: theme?.primaryColor ?? Colors.blue[500], // blue[500] is the primary color of the default theme + routes: routes, + onGenerateRoute: (RouteSettings settings) { + RouteBuilder builder = routes[settings.name]; + if (builder != null) { + return new MaterialPageRoute( + builder: (BuildContext context) { + return builder(new RouteArguments(context: context)); + }, + settings: settings + ); + } + if (onGenerateRoute != null) + return onGenerateRoute(settings); + return null; + }, + onLocaleChanged: onLocaleChanged, + showPerformanceOverlay: showPerformanceOverlay, + showSemanticsDebugger: showSemanticsDebugger, + debugShowCheckedModeBanner: debugShowCheckedModeBanner + ) { assert(debugShowMaterialGrid != null); - assert(showPerformanceOverlay != null); - assert(showSemanticsDebugger != 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 default table of routes for the application. When the - /// [Navigator] is given a named route, the name will be looked up - /// in this table first. If the name is not available, then - /// [onGenerateRoute] will be called instead. - final Map routes; - - /// The route generator callback used when the app is navigated to a - /// named route but the name is not in the [routes] table. - final RouteFactory onGenerateRoute; - - /// Callback that is invoked when the operating system changes the - /// current locale. - final LocaleChangedCallback onLocaleChanged; - /// Turns on a [GridPaper] overlay that paints a baseline grid /// Material apps: /// https://www.google.com/design/spec/layout/metrics-keylines.html /// Only available in checked mode. final bool debugShowMaterialGrid; - /// 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 "SLOW MODE" little 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 - /// MaterialApp, include a [CheckedModeBanner] widget in your app. - /// - /// This banner is intended to avoid people 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; - _MaterialAppState createState() => new _MaterialAppState(); } -EdgeDims _getPadding(ui.WindowPadding padding) { - return new EdgeDims.TRBL(padding.top, padding.right, padding.bottom, padding.left); -} - -class _MaterialAppState extends State implements BindingObserver { - - GlobalObjectKey _navigator; - - LocaleQueryData _localeData; - - void initState() { - super.initState(); - _navigator = new GlobalObjectKey(this); - didChangeLocale(ui.window.locale); - WidgetFlutterBinding.instance.addObserver(this); - } - - void dispose() { - WidgetFlutterBinding.instance.removeObserver(this); - super.dispose(); - } - - bool didPopRoute() { - assert(mounted); - NavigatorState navigator = _navigator.currentState; - assert(navigator != null); - bool result = false; - navigator.openTransaction((NavigatorTransaction transaction) { - result = transaction.pop(); - }); - return result; - } - - void didChangeMetrics() { - setState(() { - // The properties of ui.window have changed. We use them in our build - // function, so we need setState(), but we don't cache anything locally. - }); - } - - void didChangeLocale(Locale locale) { - if (config.onLocaleChanged != null) { - config.onLocaleChanged(locale).then((LocaleQueryData data) { - if (mounted) - setState(() { _localeData = data; }); - }); - } - } - - void didChangeAppLifecycleState(AppLifecycleState state) { } +class _MaterialAppState extends WidgetsAppState { final HeroController _heroController = new HeroController(); - - Route _generateRoute(RouteSettings settings) { - RouteBuilder builder = config.routes[settings.name]; - if (builder != null) { - return new MaterialPageRoute( - builder: (BuildContext context) { - return builder(new RouteArguments(context: context)); - }, - settings: settings - ); - } - if (config.onGenerateRoute != null) - return config.onGenerateRoute(settings); - return null; - } + NavigatorObserver get navigatorObserver => _heroController; Widget build(BuildContext context) { - if (config.onLocaleChanged != null && _localeData == null) { - // If the app expects a locale but we don't yet know the locale, then - // don't build the widgets now. - return new Container(); - } - ThemeData theme = config.theme ?? new ThemeData.fallback(); - Widget result = new MediaQuery( - data: new MediaQueryData( - size: ui.window.size, - devicePixelRatio: ui.window.devicePixelRatio, - padding: _getPadding(ui.window.padding) - ), - child: new LocaleQuery( - data: _localeData, - child: new AnimatedTheme( - data: theme, - duration: kThemeAnimationDuration, - child: new DefaultTextStyle( - style: _errorTextStyle, - child: new AssetVendor( - bundle: _defaultBundle, - devicePixelRatio: ui.window.devicePixelRatio, - child: new Title( - title: config.title, - color: theme.primaryColor, - child: new Navigator( - key: _navigator, - initialRoute: ui.window.defaultRouteName, - onGenerateRoute: _generateRoute, - observer: _heroController - ) - ) - ) - ) - ) - ) + Widget result = new AnimatedTheme( + data: theme, + duration: kThemeAnimationDuration, + child: super.build(context) ); assert(() { if (config.debugShowMaterialGrid) { @@ -232,27 +100,6 @@ class _MaterialAppState extends State implements BindingObserver { } return true; }); - if (config.showPerformanceOverlay) { - result = new Stack( - children: [ - result, - new Positioned(bottom: 0.0, left: 0.0, right: 0.0, child: new PerformanceOverlay.allEnabled()), - ] - ); - } - if (config.showSemanticsDebugger) { - result = new SemanticsDebugger( - child: result - ); - } - assert(() { - if (config.debugShowCheckedModeBanner) { - result = new CheckedModeBanner( - child: result - ); - } - return true; - }); return result; } diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart new file mode 100644 index 00000000000..553676a3bbb --- /dev/null +++ b/packages/flutter/lib/src/widgets/app.dart @@ -0,0 +1,223 @@ +// 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:async'; +import 'dart:ui' as ui show Locale, WindowPadding, window; + +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +import 'asset_vendor.dart'; +import 'basic.dart'; +import 'binding.dart'; +import 'checked_mode_banner.dart'; +import 'framework.dart'; +import 'locale_query.dart'; +import 'media_query.dart'; +import 'navigator.dart'; +import 'performance_overlay.dart'; +import 'semantics_debugger.dart'; +import 'title.dart'; + +AssetBundle _initDefaultBundle() { + if (rootBundle != null) + return rootBundle; + return new NetworkAssetBundle(Uri.base); +} + +final AssetBundle _defaultBundle = _initDefaultBundle(); + +class RouteArguments { + const RouteArguments({ this.context }); + final BuildContext context; +} +typedef Widget RouteBuilder(RouteArguments args); + +typedef Future LocaleChangedCallback(Locale locale); + +class WidgetsApp extends StatefulComponent { + WidgetsApp({ + Key key, + this.title, + this.textStyle, + this.color, + this.routes: const {}, + this.onGenerateRoute, + this.onLocaleChanged, + this.showPerformanceOverlay: false, + this.showSemanticsDebugger: false, + this.debugShowCheckedModeBanner: true + }) : super(key: key) { + assert(routes != null); + assert(routes.containsKey(Navigator.defaultRouteName) || onGenerateRoute != null); + assert(showPerformanceOverlay != null); + assert(showSemanticsDebugger != null); + } + + /// A one-line description of this app for use in the window manager. + final String title; + + /// The default text style for [Text] in the application. + final TextStyle textStyle; + + /// 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 default table of routes for the application. When the + /// [Navigator] is given a named route, the name will be looked up + /// in this table first. If the name is not available, then + /// [onGenerateRoute] will be called instead. + final Map routes; + + /// The route generator callback used when the app is navigated to a + /// named route but the name is not in the [routes] table. + final RouteFactory onGenerateRoute; + + /// Callback that is invoked 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 "SLOW MODE" little 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 avoid people 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; + + WidgetsAppState createState() => new WidgetsAppState(); +} + +EdgeDims _getPadding(ui.WindowPadding padding) { + return new EdgeDims.TRBL(padding.top, padding.right, padding.bottom, padding.left); +} + +class WidgetsAppState extends State implements BindingObserver { + + GlobalObjectKey _navigator; + + LocaleQueryData _localeData; + + void initState() { + super.initState(); + _navigator = new GlobalObjectKey(this); + didChangeLocale(ui.window.locale); + WidgetFlutterBinding.instance.addObserver(this); + } + + void dispose() { + WidgetFlutterBinding.instance.removeObserver(this); + super.dispose(); + } + + bool didPopRoute() { + assert(mounted); + NavigatorState navigator = _navigator.currentState; + assert(navigator != null); + bool result = false; + navigator.openTransaction((NavigatorTransaction transaction) { + result = transaction.pop(); + }); + return result; + } + + void didChangeMetrics() { + setState(() { + // The properties of ui.window have changed. We use them in our build + // function, so we need setState(), but we don't cache anything locally. + }); + } + + void didChangeLocale(Locale locale) { + if (config.onLocaleChanged != null) { + config.onLocaleChanged(locale).then((LocaleQueryData data) { + if (mounted) + setState(() { _localeData = data; }); + }); + } + } + + void didChangeAppLifecycleState(AppLifecycleState state) { } + + NavigatorObserver get navigatorObserver => null; + + Widget build(BuildContext context) { + if (config.onLocaleChanged != null && _localeData == null) { + // If the app expects a locale but we don't yet know the locale, then + // don't build the widgets now. + // TODO(ianh): Make this unnecessary. See https://github.com/flutter/flutter/issues/1865 + return new Container(); + } + + Widget result = new MediaQuery( + data: new MediaQueryData( + size: ui.window.size, + devicePixelRatio: ui.window.devicePixelRatio, + padding: _getPadding(ui.window.padding) + ), + child: new LocaleQuery( + data: _localeData, + child: new DefaultTextStyle( + style: config.textStyle, + child: new AssetVendor( + bundle: _defaultBundle, + devicePixelRatio: ui.window.devicePixelRatio, + child: new Title( + title: config.title, + color: config.color, + child: new Navigator( + key: _navigator, + initialRoute: ui.window.defaultRouteName, + onGenerateRoute: config.onGenerateRoute, + observer: navigatorObserver + ) + ) + ) + ) + ) + ); + if (config.showPerformanceOverlay) { + result = new Stack( + children: [ + result, + new Positioned(bottom: 0.0, left: 0.0, right: 0.0, child: new PerformanceOverlay.allEnabled()), + ] + ); + } + if (config.showSemanticsDebugger) { + result = new SemanticsDebugger( + child: result + ); + } + assert(() { + if (config.debugShowCheckedModeBanner) { + result = new CheckedModeBanner( + child: result + ); + } + return true; + }); + return result; + } + +} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 5e50f13468b..fcea6581e2e 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -5,6 +5,7 @@ /// The Flutter widget framework. library widgets; +export 'src/widgets/app.dart'; export 'src/widgets/asset_vendor.dart'; export 'src/widgets/auto_layout.dart'; export 'src/widgets/basic.dart';