From 06deba25ee9f255ff77a946dc157555b54ba4fe1 Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 31 May 2017 15:13:08 -0700 Subject: [PATCH 001/110] move more tests to Linux hosts (#10413) * move more tests to Linux hosts * fix test --- dev/devicelab/manifest.yaml | 64 +++++++++++++-------------- dev/devicelab/test/manifest_test.dart | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index d42e025e936d6..e57af6b716607 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -37,13 +37,6 @@ tasks: # TODO: make these not require "has-android-device"; it is only there to # ensure we have the Android SDK. - flutter_gallery__build: - description: > - Collects various performance metrics from AOT builds of the Flutter - Gallery. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - complex_layout__build: description: > Collects various performance metrics from AOT builds of the Complex @@ -90,25 +83,12 @@ tasks: stage: devicelab required_agent_capabilities: ["has-android-device"] - flutter_gallery__start_up: - description: > - Measures the startup time of the Flutter Gallery app on Android. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - complex_layout__start_up: description: > Measures the startup time of the Complex Layout sample app on Android. stage: devicelab required_agent_capabilities: ["has-android-device"] - flutter_gallery__transition_perf: - description: > - Measures the performance of screen transitions in Flutter Gallery on - Android. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - hot_mode_dev_cycle__benchmark: description: > Measures the performance of Dart VM hot patching feature. @@ -127,18 +107,6 @@ tasks: stage: devicelab required_agent_capabilities: ["has-android-device"] - flutter_gallery__memory_nav: - description: > - Measures memory usage after repeated navigation in Gallery. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - - flutter_gallery__back_button_memory: - description: > - Measures memory usage after Android app suspend and resume. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - microbenchmarks: description: > Runs benchmarks from dev/benchmarks/microbenchmarks. @@ -284,3 +252,35 @@ tasks: stage: devicelab required_agent_capabilities: ["linux/android"] flaky: true + + flutter_gallery__build: + description: > + Collects various performance metrics from AOT builds of the Flutter + Gallery. + stage: devicelab + required_agent_capabilities: ["linux/android"] + + flutter_gallery__start_up: + description: > + Measures the startup time of the Flutter Gallery app on Android. + stage: devicelab + required_agent_capabilities: ["linux/android"] + + flutter_gallery__transition_perf: + description: > + Measures the performance of screen transitions in Flutter Gallery on + Android. + stage: devicelab + required_agent_capabilities: ["linux/android"] + + flutter_gallery__memory_nav: + description: > + Measures memory usage after repeated navigation in Gallery. + stage: devicelab + required_agent_capabilities: ["linux/android"] + + flutter_gallery__back_button_memory: + description: > + Measures memory usage after Android app suspend and resume. + stage: devicelab + required_agent_capabilities: ["linux/android"] diff --git a/dev/devicelab/test/manifest_test.dart b/dev/devicelab/test/manifest_test.dart index eb1e39874ede5..12185569d0022 100644 --- a/dev/devicelab/test/manifest_test.dart +++ b/dev/devicelab/test/manifest_test.dart @@ -15,7 +15,7 @@ void main() { final ManifestTask task = manifest.tasks.firstWhere((ManifestTask task) => task.name == 'flutter_gallery__start_up'); expect(task.description, 'Measures the startup time of the Flutter Gallery app on Android.\n'); expect(task.stage, 'devicelab'); - expect(task.requiredAgentCapabilities, ['has-android-device']); + expect(task.requiredAgentCapabilities, ['linux/android']); }); }); From 1b5b929c730a368c571b6786f66a236a74adac64 Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 31 May 2017 15:40:14 -0700 Subject: [PATCH 002/110] mark flutter_gallery_ios__transition_perf as flaky (#10420) --- dev/devicelab/manifest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index e57af6b716607..2515df07e994b 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -174,6 +174,7 @@ tasks: iOS. stage: devicelab_ios required_agent_capabilities: ["has-ios-device"] + flaky: true basic_material_app_ios__size: description: > From 0e97637299d7ef55be8c12f508ec9aece483b97d Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 1 Jun 2017 11:26:54 -0700 Subject: [PATCH 003/110] Implement a pushRoute service to satisfy the FlutterView.pushRoute API (#10415) Fixes https://github.com/flutter/flutter/issues/10399 --- packages/flutter/lib/src/widgets/app.dart | 9 ++++++ packages/flutter/lib/src/widgets/binding.dart | 29 ++++++++++++++++--- .../flutter/test/widgets/binding_test.dart | 23 +++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index ecdeed592d9c8..669716d9c136e 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -166,6 +166,15 @@ class _WidgetsAppState extends State implements WidgetsBindingObserv return await navigator.maybePop(); } + @override + Future didPushRoute(String route) async { + assert(mounted); + final NavigatorState navigator = _navigator.currentState; + assert(navigator != null); + navigator.pushNamed(route); + return true; + } + @override void didChangeMetrics() { setState(() { diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index db80dcc676cc7..6af1ed7175a12 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -37,6 +37,14 @@ abstract class WidgetsBindingObserver { /// its current route if possible. Future didPopRoute() => new Future.value(false); + /// Called when the host tells the app to push a new route onto the + /// navigator. + /// + /// Observers are expected to return true if they were able to + /// handle the notification. Observers are notified in registration + /// order until one returns true. + Future didPushRoute(String route) => new Future.value(false); + /// Called when the application's dimensions change. For example, /// when a phone is rotated. void didChangeMetrics() { } @@ -198,10 +206,23 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren SystemNavigator.pop(); } - Future _handleNavigationInvocation(MethodCall methodCall) async { - if (methodCall.method == 'popRoute') - handlePopRoute(); - // TODO(abarth): Handle 'pushRoute'. + /// Called when the host tells the app to push a new route onto the + /// navigator. + Future handlePushRoute(String route) async { + for (WidgetsBindingObserver observer in new List.from(_observers)) { + if (await observer.didPushRoute(route)) + return; + } + } + + Future _handleNavigationInvocation(MethodCall methodCall) { + switch (methodCall.method) { + case 'popRoute': + return handlePopRoute(); + case 'pushRoute': + return handlePushRoute(methodCall.arguments); + } + return new Future.value(); } /// Called when the application lifecycle state changes. diff --git a/packages/flutter/test/widgets/binding_test.dart b/packages/flutter/test/widgets/binding_test.dart index 610733afb1346..8839333224cec 100644 --- a/packages/flutter/test/widgets/binding_test.dart +++ b/packages/flutter/test/widgets/binding_test.dart @@ -26,6 +26,16 @@ class AppLifecycleStateObserver extends WidgetsBindingObserver { } } +class PushRouteObserver extends WidgetsBindingObserver { + String pushedRoute; + + @override + Future didPushRoute(String route) async { + pushedRoute = route; + return true; + } +} + void main() { setUp(() { WidgetsFlutterBinding.ensureInitialized(); @@ -61,4 +71,17 @@ void main() { await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {}); expect(observer.lifecycleState, AppLifecycleState.suspending); }); + + testWidgets('didPushRoute callback', (WidgetTester tester) async { + final PushRouteObserver observer = new PushRouteObserver(); + WidgetsBinding.instance.addObserver(observer); + + final String testRouteName = 'testRouteName'; + final ByteData message = const JSONMethodCodec().encodeMethodCall( + new MethodCall('pushRoute', testRouteName)); + await BinaryMessages.handlePlatformMessage('flutter/navigation', message, (_) {}); + expect(observer.pushedRoute, testRouteName); + + WidgetsBinding.instance.removeObserver(observer); + }); } From 498cfc67c39a8ad0742d48ccf9f7c9cb025c681b Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 1 Jun 2017 12:52:08 -0700 Subject: [PATCH 004/110] (Update and) Integrate Rendering README into code (#10389) * (Update and) Integrate Rendering README into code * Add mising whitespace * Review comments and other touch-ups --- packages/flutter/lib/src/rendering/README.md | 112 ------------------ .../flutter/lib/src/rendering/object.dart | 61 ++++++++-- 2 files changed, 53 insertions(+), 120 deletions(-) delete mode 100644 packages/flutter/lib/src/rendering/README.md diff --git a/packages/flutter/lib/src/rendering/README.md b/packages/flutter/lib/src/rendering/README.md deleted file mode 100644 index 540ac8ef7fa18..0000000000000 --- a/packages/flutter/lib/src/rendering/README.md +++ /dev/null @@ -1,112 +0,0 @@ -Flutter Rendering Layer -======================= - -This document is intended to describe some of the core designs of the -Flutter rendering layer. - -Layout ------- - -Paint ------ - -Compositing ------------ - -Semantics ---------- - -The last phase of a frame is the Semantics phase. This only occurs if -a semantics server has been installed, for example if the user is -using an accessibility tool. - -Each frame, the semantics phase starts with a call to the -`PipelineOwner.flushSemantics()` method from the `Renderer` binding's -`beginFrame()` method. - -Each node marked as needing semantics (which initially is just the -root node, as scheduled by `scheduleInitialSemantics()`), in depth -order, has its semantics updated by calling `_updateSemantics()`. - -The `_updateSemantics()` method calls `_getSemantics()` to obtain an -`_InterestingSemanticsFragment`, and then calls `compile()` on that -fragment to obtain a `SemanticsNode` which becomes the value of the -`RenderObject`'s `_semantics` field. **This is essentially a two-pass -walk of the render tree. The first pass determines the shape of the -output tree, and the second creates the nodes of this tree and hooks -them together.** The second walk is a sparse walk; it only walks the -nodes that are interesting for the purpose of semantics. - -`_getSemantics()` is the core function that walks the render tree to -obtain the semantics. It collects semantic annotators for this -`RenderObject`, then walks its children collecting -`_SemanticsFragment`s for them, and then returns an appropriate -`_SemanticsFragment` object that describes the `RenderObject`'s -semantics. - -Semantic annotators are functions that, given a `SemanticsNode`, set -some flags or strings on the object. They are obtained from -`getSemanticsAnnotators()`. For example, here is how `RenderParagraph` -annotates the `SemanticsNode` with its text: - -```dart - Iterable getSemanticsAnnotators() sync* { - yield (SemanticsNode node) { - node.label = text.toPlainText(); - }; - } -``` - -A `_SemanticsFragment` object is a node in a short-lived tree which is -used to create the final `SemanticsNode` tree that is sent to the -semantics server. These objects have a list of semantic annotators, -and a list of `_SemanticsFragment` children. - -There are several `_SemanticsFragment` classes. The `_getSemantics()` -method picks its return value as follows: - -* `_CleanSemanticsFragment` is used to represent a `RenderObject` that - has a `SemanticsNode` and which is in no way dirty. This class has - no children and no annotators, and when compiled, it returns the - `SemanticsNode` that the `RenderObject` already has. - -* `_RootSemanticsFragment`* is used to represent the `RenderObject` - found at the top of the render tree. This class always compiles to a - `SemanticsNode` with ID 0. - -* `_ConcreteSemanticsFragment`* is used to represent a `RenderObject` - that has `hasSemantics` set to true. It returns the `SemanticsNode` - for that `RenderObject`. - -* `_ImplicitSemanticsFragment`* is used to represent a `RenderObject` - that does not have `hasSemantics` set to true, but which does have - some semantic annotators. When it is compiled, if the nearest - ancestor `_SemanticsFragment` that isn't also an - `_ImplicitSemanticsFragment` is a `_RootSemanticsFragment` or a - `_ConcreteSemanticsFragment`, then the `SemanticsNode` from that - object is reused. Otherwise, a new one is created. - -* `_ForkingSemanticsFragment` is used to represent a `RenderObject` - that introduces no semantics of its own, but which has two or more - descendants that do introduce semantics (and which are not ancestors - or descendants of each other). - -* For `RenderObject` nodes that introduce no semantics but which have - a (single) child that does, the `_SemanticsFragment` of the child is - returned. - -* For `RenderObject` nodes that introduce no semantics and have no - descendants that introduce semantics, `null` is returned. - -The classes marked with an asterisk * above are the -`_InterestingSemanticsFragment` classes. - -When the `_SemanticsFragment` tree is then compiled, the -`SemanticsNode` objects are created (if necessary), the semantic -annotators are run on each `SemanticsNode`, the geometry (matrix, -size, and clip) is applied, and the children are updated. - -As part of this, the code clears out the `_semantics` field of any -`RenderObject` that previously had a `SemanticsNode` but no longer -does. This is done as part of the first walk where possible, and as -part of the second otherwise. diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 9b7dd0b26b1af..3880119661459 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -652,6 +652,12 @@ class _SemanticsGeometry { } } +/// Describes the shape of the semantic tree. +/// +/// A [_SemanticsFragment] object is a node in a short-lived tree which is used +/// to create the final [SemanticsNode] tree that is sent to the semantics +/// server. These objects have a [SemanticsAnnotator], and a list of +/// [_SemanticsFragment] children. abstract class _SemanticsFragment { _SemanticsFragment({ @required RenderObject renderObjectOwner, @@ -689,9 +695,11 @@ abstract class _SemanticsFragment { String toString() => '$runtimeType#$hashCode'; } -/// Represents a subtree that doesn't need updating, it already has a -/// SemanticsNode and isn't dirty. (We still update the matrix, since -/// that comes from the (dirty) ancestors.) +/// Represents a [RenderObject] which is in no way dirty. +/// +/// This class has no children and no annotators, and when compiled, it returns +/// the [SemanticsNode] that the [RenderObject] already has. (We still update +/// the matrix, since that comes from the (dirty) ancestors.) class _CleanSemanticsFragment extends _SemanticsFragment { _CleanSemanticsFragment({ @required RenderObject renderObjectOwner @@ -750,6 +758,9 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { _SemanticsGeometry createSemanticsGeometryForChild(_SemanticsGeometry geometry); } +/// Represents the [RenderObject] found at the top of the render tree. +/// +/// This class always compiles to a [SemanticsNode] with ID 0. class _RootSemanticsFragment extends _InterestingSemanticsFragment { _RootSemanticsFragment({ RenderObject renderObjectOwner, @@ -780,6 +791,9 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { } } +/// Represents a RenderObject that has [isSemanticBoundary] set to `true`. +/// +/// It returns the SemanticsNode for that [RenderObject]. class _ConcreteSemanticsFragment extends _InterestingSemanticsFragment { _ConcreteSemanticsFragment({ RenderObject renderObjectOwner, @@ -808,6 +822,13 @@ class _ConcreteSemanticsFragment extends _InterestingSemanticsFragment { } } +/// Represents a RenderObject that does not have [isSemanticBoundary] set to +/// `true`, but which does have some semantic annotators. +/// +/// When it is compiled, if the nearest ancestor [_SemanticsFragment] that isn't +/// also an [_ImplicitSemanticsFragment] is a [_RootSemanticsFragment] or a +/// [_ConcreteSemanticsFragment], then the [SemanticsNode] from that object is +/// reused. Otherwise, a new one is created. class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment { _ImplicitSemanticsFragment({ RenderObject renderObjectOwner, @@ -851,6 +872,9 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment { } } +/// Represents a [RenderObject] that introduces no semantics of its own, but +/// which has two or more descendants that do introduce semantics +/// (and which are not ancestors or descendants of each other). class _ForkingSemanticsFragment extends _SemanticsFragment { _ForkingSemanticsFragment({ RenderObject renderObjectOwner, @@ -1180,7 +1204,11 @@ class PipelineOwner { bool _debugDoingSemantics = false; final List _nodesNeedingSemantics = []; - /// Update the semantics for all render objects. + /// Update the semantics for render objects marked as needing a semantics + /// update. + /// + /// Initially, only the root node, as scheduled by [scheduleInitialSemantics], + /// needs a semantics update. /// /// This function is one of the core stages of the rendering pipeline. The /// semantics are compiled after painting and only after @@ -2486,6 +2514,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { } } + /// Updates the semantic information of the render object. + /// + /// This is essentially a two-pass walk of the render tree. The first pass + /// determines the shape of the output tree (captured in + /// [_SemanticsFragment]s), and the second creates the nodes of this tree and + /// hooks them together. The second walk is a sparse walk; it only walks the + /// nodes that are interesting for the purpose of semantics. void _updateSemantics() { try { assert(_needsSemanticsUpdate); @@ -2500,6 +2535,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { } } + /// Core function that walks the render tree to obtain the semantics. + /// + /// It collects semantic annotators for this RenderObject, then walks its + /// children collecting [_SemanticsFragments] for them, and then returns an + /// appropriate [_SemanticsFragment] object that describes the RenderObject's + /// semantics. _SemanticsFragment _getSemanticsFragment() { // early-exit if we're not dirty and have our own semantics if (!_needsSemanticsUpdate && isSemanticBoundary) { @@ -2533,17 +2574,21 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { if (annotator != null) return new _ImplicitSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children); _semantics = null; - if (children == null) + if (children == null) { + // Introduces no semantics and has no descendants that introduce semantics. return null; + } if (children.length > 1) return new _ForkingSemanticsFragment(renderObjectOwner: this, children: children); assert(children.length == 1); return children.single; } - /// Called when collecting the semantics of this node. Subclasses - /// that have children that are not semantically relevant (e.g. - /// because they are invisible) should skip those children here. + /// Called when collecting the semantics of this node. + /// + /// The implementation has to return the children in paint order skipping all + /// children that are not semantically relevant (e.g. because they are + /// invisible). /// /// The default implementation mirrors the behavior of /// [visitChildren()] (which is supposed to walk all the children). From 1b9c6a68351bc2925118494df185acdde6cb7936 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Thu, 1 Jun 2017 12:58:34 -0700 Subject: [PATCH 005/110] More documentation (#10426) --- .../flutter/lib/src/animation/curves.dart | 71 +++++- .../flutter/lib/src/material/typography.dart | 117 +++++----- .../flutter/lib/src/painting/box_fit.dart | 16 -- .../flutter/lib/src/rendering/proxy_box.dart | 11 +- packages/flutter/lib/src/widgets/basic.dart | 210 +++++++++++++++++- 5 files changed, 342 insertions(+), 83 deletions(-) diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index cecba6ba4d7df..dcb7aa013e812 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -27,6 +27,13 @@ abstract class Curve { /// Returns a new curve that is the reversed inversion of this one. /// This is often useful as the reverseCurve of an [Animation]. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_in.png) + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_flipped.png) + /// + /// See also: + /// + /// * [FlippedCurve], the class that is used to implement this getter. Curve get flipped => new FlippedCurve(this); @override @@ -49,6 +56,8 @@ class _Linear extends Curve { /// /// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back /// to 0.0 each iteration. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_sawtooth.png) class SawTooth extends Curve { /// Creates a sawtooth curve. /// @@ -80,6 +89,8 @@ class SawTooth extends Curve { /// animation that uses an [Interval] with its [begin] set to 0.5 and its [end] /// set to 1.0 will essentially become a three-second animation that starts /// three seconds later. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_interval.png) class Interval extends Curve { /// Creates an interval curve. /// @@ -127,6 +138,8 @@ class Interval extends Curve { } /// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_threshold.png) class Threshold extends Curve { /// Creates a threshold curve. /// @@ -151,14 +164,17 @@ class Threshold extends Curve { /// A cubic polynomial mapping of the unit interval. /// -/// See [Curves] for a number of commonly used cubic curves. -/// -/// See also: +/// The [Curves] class contains some commonly used cubic curves: /// /// * [Curves.ease] /// * [Curves.easeIn] /// * [Curves.easeOut] /// * [Curves.easeInOut] +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_in.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_in_out.png) class Cubic extends Curve { /// Creates a cubic curve. /// @@ -232,6 +248,11 @@ class Cubic extends Curve { /// This curve evalutes the given curve in reverse (i.e., from 1.0 to 0.0 as t /// increases from 0.0 to 1.0) and returns the inverse of the given curve's value /// (i.e., 1.0 minus the given curve's value). +/// +/// This is the class used to implement the [flipped] getter on curves. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_in.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_flipped_curve.png) class FlippedCurve extends Curve { /// Creates a flipped curve. /// @@ -334,6 +355,11 @@ class _BounceInOutCurve extends Curve { // ELASTIC CURVES /// An oscillating curve that grows in magnitude while overshooting its bounds. +/// +/// An instance of this class using the default period of 0.4 is available as +/// [Curves.elasticIn]. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_in.png) class ElasticInCurve extends Curve { /// Creates an elastic-in curve. /// @@ -358,6 +384,11 @@ class ElasticInCurve extends Curve { } /// An oscillating curve that shrinks in magnitude while overshooting its bounds. +/// +/// An instance of this class using the default period of 0.4 is available as +/// [Curves.elasticOut]. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_out.png) class ElasticOutCurve extends Curve { /// Creates an elastic-out curve. /// @@ -380,7 +411,13 @@ class ElasticOutCurve extends Curve { } } -/// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds. +/// An oscillating curve that grows and then shrinks in magnitude while +/// overshooting its bounds. +/// +/// An instance of this class using the default period of 0.4 is available as +/// [Curves.elasticInOut]. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_in_out.png) class ElasticInOutCurve extends Curve { /// Creates an elastic-in-out curve. /// @@ -419,6 +456,8 @@ class Curves { /// This is the identity map over the unit interval: its [Curve.transform] /// method returns its input unmodified. This is useful as a default curve for /// cases where a [Curve] is required but no actual curve is desired. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_linear.png) static const Curve linear = const _Linear._(); /// A curve where the rate of change starts out quickly and then decelerates; an @@ -426,18 +465,28 @@ class Curves { /// /// This is equivalent to the Android `DecelerateInterpolator` class with a unit /// factor (the default factor). + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_decelerate.png) static const Curve decelerate = const _DecelerateCurve._(); /// A cubic animation curve that speeds up quickly and ends slowly. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease.png) static const Cubic ease = const Cubic(0.25, 0.1, 0.25, 1.0); /// A cubic animation curve that starts slowly and ends quickly. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_in.png) static const Cubic easeIn = const Cubic(0.42, 0.0, 1.0, 1.0); /// A cubic animation curve that starts quickly and ends slowly. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_out.png) static const Cubic easeOut = const Cubic(0.0, 0.0, 0.58, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then and ends slowly. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_in_out.png) static const Cubic easeInOut = const Cubic(0.42, 0.0, 0.58, 1.0); /// A curve that starts quickly and eases into its final position. @@ -445,23 +494,37 @@ class Curves { /// Over the course of the animation, the object spends more time near its /// final destination. As a result, the user isn’t left waiting for the /// animation to finish, and the negative effects of motion are minimized. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_fast_out_slow_in.png) static const Cubic fastOutSlowIn = const Cubic(0.4, 0.0, 0.2, 1.0); /// An oscillating curve that grows in magnitude. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_in.png) static const Curve bounceIn = const _BounceInCurve._(); /// An oscillating curve that first grows and then shrink in magnitude. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_out.png) static const Curve bounceOut = const _BounceOutCurve._(); /// An oscillating curve that first grows and then shrink in magnitude. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_in_out.png) static const Curve bounceInOut = const _BounceInOutCurve._(); /// An oscillating curve that grows in magnitude while overshootings its bounds. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_in.png) static const ElasticInCurve elasticIn = const ElasticInCurve(); /// An oscillating curve that shrinks in magnitude while overshootings its bounds. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_out.png) static const ElasticOutCurve elasticOut = const ElasticOutCurve(); /// An oscillating curve that grows and then shrinks in magnitude while overshootings its bounds. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_in_out.png) static const ElasticInOutCurve elasticInOut = const ElasticInOutCurve(); } diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart index 92e4bcad0f824..eb388a187dff2 100644 --- a/packages/flutter/lib/src/material/typography.dart +++ b/packages/flutter/lib/src/material/typography.dart @@ -9,7 +9,7 @@ import 'colors.dart'; // TODO(eseidel): Font weights are supposed to be language relative. // TODO(jackson): Baseline should be language relative. -// These values are for English-like text. +// TODO(ianh): These values are for English-like text. /// Material design text theme. /// @@ -20,11 +20,21 @@ import 'colors.dart'; /// To obtain the current text theme, call [Theme.of] with the current /// [BuildContext] and read the [ThemeData.textTheme] property. /// +/// The following image [from the material design +/// specification](https://material.io/guidelines/style/typography.html#typography-styles) +/// shows the recommended styles for each of the properties of a [TextTheme]. +/// This image uses the `Roboto` font, which is the font used on Android. On +/// iOS, the [San Francisco +/// font](https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/) +/// is automatically used instead. +/// +/// ![To see the image, visit the typography site referenced below.](https://storage.googleapis.com/material-design/publish/material_v_11/assets/0Bzhp5Z4wHba3alhXZ2pPWGk3Zjg/style_typography_styles_scale.png) +/// /// See also: /// -/// * [Typography] -/// * [Theme] -/// * [ThemeData] +/// * [Typography], the class that generates [TextTheme]s appropriate for a platform. +/// * [Theme], for other aspects of a material design application that can be +/// globally adjusted, such as the color scheme. /// * @immutable class TextTheme { @@ -49,34 +59,34 @@ class TextTheme { this.body2, this.body1, this.caption, - this.button + this.button, }); const TextTheme._blackMountainView() - : display4 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic), - display3 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), - display2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), - display1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), - headline = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - title = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - subhead = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - body2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - body1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - caption = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), - button = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic); + : display4 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic), + display3 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), + display2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), + display1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), + headline = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + title = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + subhead = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + body2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + body1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + caption = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), + button = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic); const TextTheme._whiteMountainView() - : display4 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic), - display3 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), - display2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), - display1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), - headline = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), - title = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), - subhead = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), - body2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), - body1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), - caption = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), - button = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic); + : display4 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic), + display3 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), + display2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), + display1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), + headline = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), + title = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), + subhead = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), + body2 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), + body1 = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), + caption = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), + button = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic); const TextTheme._blackCupertino() : display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic), @@ -85,11 +95,11 @@ class TextTheme { display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), title = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), - caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), - button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic); + subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), + caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), + button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic); const TextTheme._whiteCupertino() : display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic), @@ -98,11 +108,11 @@ class TextTheme { display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), title = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), - subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), - body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), - body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), - caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), - button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic); + subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), + body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), + body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), + caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), + button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic); /// Extremely large text. /// @@ -173,7 +183,7 @@ class TextTheme { body2: body2 ?? this.body2, body1: body1 ?? this.body1, caption: caption ?? this.caption, - button: button ?? this.button + button: button ?? this.button, ); } @@ -199,67 +209,67 @@ class TextTheme { color: displayColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), display3: display3.apply( color: displayColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), display2: display2.apply( color: displayColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), display1: display1.apply( color: displayColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), headline: headline.apply( color: bodyColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), title: title.apply( color: bodyColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), subhead: subhead.apply( color: bodyColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), body2: body2.apply( color: bodyColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), body1: body1.apply( color: bodyColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), caption: caption.apply( color: displayColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), button: button.apply( color: bodyColor, fontFamily: fontFamily, fontSizeFactor: fontSizeFactor, - fontSizeDelta: fontSizeDelta + fontSizeDelta: fontSizeDelta, ), ); } @@ -277,7 +287,7 @@ class TextTheme { body2: TextStyle.lerp(begin.body2, end.body2, t), body1: TextStyle.lerp(begin.body1, end.body1, t), caption: TextStyle.lerp(begin.caption, end.caption, t), - button: TextStyle.lerp(begin.button, end.button, t) + button: TextStyle.lerp(begin.button, end.button, t), ); } @@ -285,7 +295,7 @@ class TextTheme { bool operator ==(dynamic other) { if (identical(this, other)) return true; - if (other is! TextTheme) + if (other.runtimeType != runtimeType) return false; final TextTheme typedOther = other; return display4 == typedOther.display4 && @@ -331,8 +341,9 @@ class TextTheme { /// /// See also: /// -/// * [Theme] -/// * [ThemeData] +/// * [TextTheme], which shows what the text styles in a theme look like. +/// * [Theme], for other aspects of a material design application that can be +/// globally adjusted, such as the color scheme. /// * class Typography { /// Creates the default typography for the specified platform. diff --git a/packages/flutter/lib/src/painting/box_fit.dart b/packages/flutter/lib/src/painting/box_fit.dart index 595e9058354b8..5d538fc47d64e 100644 --- a/packages/flutter/lib/src/painting/box_fit.dart +++ b/packages/flutter/lib/src/painting/box_fit.dart @@ -13,22 +13,6 @@ import 'basic_types.dart'; /// /// See also [applyBoxFit], which applies the sizing semantics of these values /// (though not the alignment semantics). -/// -/// The following diagrams show the effects of each value: -/// -/// ![`fill`: Fill the target box by distorting the source's aspect ratio.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_fill.png) -/// -/// ![`contain`: As large as possible while still containing the source entirely within the target box.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_contain.png) -/// -/// ![`cover`: As small as possible while still covering the entire target box.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_cover.png) -/// -/// ![`fitWidth`: Make sure the full width of the source is shown.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_fitWidth.png) -/// -/// ![`fitHeight`: Make sure the full height of the source is shown.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_fitHeight.png) -/// -/// ![`none`: Do not resize the source.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_none.png) -/// -/// ![`scaleDown`: Same as `contain` if that would shrink the image, otherwise same as `none`.](https://flutter.github.io/assets-for-api-docs/painting/box_fit_scaleDown.png) enum BoxFit { /// Fill the target box by distorting the source's aspect ratio. /// diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 47a3a1d3d6cfc..3cae3cd91f5a1 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -773,7 +773,7 @@ class RenderOpacity extends RenderProxyBox { /// Signature for a function that creates a [Shader] for a given [Rect]. /// -/// Used by [RenderShaderMask]. +/// Used by [RenderShaderMask] and the [ShaderMask] widget. typedef Shader ShaderCallback(Rect bounds); /// Applies a mask generated by a [Shader] to its child. @@ -1868,7 +1868,14 @@ class RenderFractionalTranslation extends RenderProxyBox { /// } /// /// @override -/// bool shouldRepaint(CustomPainter oldDelegate) => false; +/// bool shouldRepaint(Sky oldDelegate) { +/// // Since this Sky painter has no fields, it always paints +/// // the same thing, and therefore we return false here. If +/// // we had fields (set from the constructor) then we would +/// // return true if any of them differed from the same +/// // fields on the oldDelegate. +/// return false; +/// } /// } /// ``` /// diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 9776c2e545d48..843aa419e7aff 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -61,6 +61,25 @@ export 'package:flutter/rendering.dart' show /// expensive because it requires painting the child into an intermediate /// buffer. For the value 0.0, the child is simply not painted at all. For the /// value 1.0, the child is painted immediately without an intermediate buffer. +/// +/// ## Sample code +/// +/// This example shows some [Text] when the `_visible` member field is true, and +/// hides it when it is false: +/// +/// ```dart +/// new Opacity( +/// opacity: _visible ? 1.0 : 0.0, +/// child: const Text('Now you see me, now you don\'t!'), +/// ) +/// ``` +/// +/// This is more efficient than adding and removing the child widget from the +/// tree on demand. +/// +/// See also: +/// +/// * [ShaderMask], which can apply more elaborate effects to its child. class Opacity extends SingleChildRenderObjectWidget { /// Creates a widget that makes its child partially transparent. /// @@ -69,7 +88,7 @@ class Opacity extends SingleChildRenderObjectWidget { const Opacity({ Key key, @required this.opacity, - Widget child + Widget child, }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), super(key: key, child: child); @@ -104,6 +123,30 @@ class Opacity extends SingleChildRenderObjectWidget { /// /// For example, [ShaderMask] can be used to gradually fade out the edge /// of a child by using a [new ui.Gradient.linear] mask. +/// +/// ## Sample code +/// +/// This example makes the text look like it is on fire: +/// +/// ```dart +/// new ShaderMask( +/// shaderCallback: (Rect bounds) { +/// return new RadialGradient( +/// center: FractionalOffset.topLeft, +/// radius: 1.0, +/// colors: [Colors.yellow, Colors.deepOrange.shade900], +/// tileMode: TileMode.mirror, +/// ).createShader(bounds); +/// }, +/// child: const Text('I’m burning the memories'), +/// ) +/// ``` +/// +/// See also: +/// +/// * [Opacity], which can apply a uniform alpha effect to its child. +/// * [CustomPaint], which lets you draw directly on the canvas. +/// * [DecoratedBox], for another approach at decorating child widgets. class ShaderMask extends SingleChildRenderObjectWidget { /// Creates a widget that applies a mask generated by a [Shader] to its child. /// @@ -117,10 +160,14 @@ class ShaderMask extends SingleChildRenderObjectWidget { assert(blendMode != null), super(key: key, child: child); - /// Called to creates the [Shader] that generates the mask. + /// Called to create the [dart:ui.Shader] that generates the mask. /// /// The shader callback is called with the current size of the child so that /// it can customize the shader to the size and location of the child. + /// + /// Typically this will use a [LinearGradient] or [RadialGradient] to create + /// the [dart:ui.Shader], though the [dart:ui.ImageShader] class could also be + /// used. final ShaderCallback shaderCallback; /// The [BlendMode] to use when applying the shader to the child. @@ -133,7 +180,7 @@ class ShaderMask extends SingleChildRenderObjectWidget { RenderShaderMask createRenderObject(BuildContext context) { return new RenderShaderMask( shaderCallback: shaderCallback, - blendMode: blendMode + blendMode: blendMode, ); } @@ -682,6 +729,23 @@ class FractionalTranslation extends SingleChildRenderObjectWidget { /// Unlike [Transform], which applies a transform just prior to painting, /// this object applies its rotation prior to layout, which means the entire /// rotated box consumes only as much space as required by the rotated child. +/// +/// ## Sample code +/// +/// This snippet rotates the child (some [Text]) so that it renders from bottom +/// to top, like an axis label on a graph: +/// +/// ```dart +/// new RotatedBox( +/// quarterTurns: 3, +/// child: const Text('Hello World!'), +/// ) +/// ``` +/// +/// See also: +/// +/// * [Transform], which is a paint effect that allows you to apply an +/// arbitrary transform to a child. class RotatedBox extends SingleChildRenderObjectWidget { /// A widget that rotates its child. /// @@ -689,7 +753,7 @@ class RotatedBox extends SingleChildRenderObjectWidget { const RotatedBox({ Key key, @required this.quarterTurns, - Widget child + Widget child, }) : assert(quarterTurns != null), super(key: key, child: child); @@ -712,6 +776,17 @@ class RotatedBox extends SingleChildRenderObjectWidget { /// size. Padding then sizes itself to its child's size, inflated by the /// padding, effectively creating empty space around the child. /// +/// ## Sample code +/// +/// This snippet indents the child (a [Card] with some [Text]) by eight pixels in each direction: +/// +/// ```dart +/// new Padding( +/// padding: new EdgeInsets.all(8.0), +/// child: const Card(child: const Text('Hello World!')), +/// ) +/// ``` +/// /// ## Design discussion /// /// ### Why use a [Padding] widget rather than a [Container] with a [Container.padding] property? @@ -731,6 +806,12 @@ class RotatedBox extends SingleChildRenderObjectWidget { /// In fact, the majority of widgets in Flutter are simply combinations of other /// simpler widgets. Composition, rather than inheritance, is the primary /// mechansim for building up widgets. +/// +/// See also: +/// +/// * [EdgeInsets], the class that is used to describe the padding dimensions. +/// * [Center], which positions the child at its natural dimensions, centered +/// in the parent. class Padding extends SingleChildRenderObjectWidget { /// Creates a widget that insets its child. /// @@ -738,7 +819,7 @@ class Padding extends SingleChildRenderObjectWidget { const Padding({ Key key, @required this.padding, - Widget child + Widget child, }) : assert(padding != null), super(key: key, child: child); @@ -1001,6 +1082,30 @@ class CustomMultiChildLayout extends MultiChildRenderObjectWidget { /// The [new SizedBox.expand] constructor can be used to make a [SizedBox] that /// sizes itself to fit the parent. It is equivalent to setting [width] and /// [height] to [double.INFINITY]. +/// +/// ## Sample code +/// +/// This snippet makes the child widget (a [Card] with some [Text]) have the +/// exact size 200x300, parental constraints permitting: +/// +/// ```dart +/// new SizedBox( +/// width: 200.0, +/// height: 300.0, +/// child: const Card(child: const Text('Hello World!')), +/// ) +/// ``` +/// +/// See also: +/// +/// * [ConstrainedBox], a more generic version of this class that takes +/// arbitrary [BoxConstraints] instead of an explicit width and height. +/// * [FractionallySizedBox], a widget that sizes its child to a fraction of +/// the total available space. +/// * [AspectRatio], a widget that attempts to fit within the parent's +/// constraints while also sizing its child to match a given sapect ratio. +/// * [FittedBox], which sizes and positions its child widget to fit the parent +/// according to a given [BoxFit] discipline. class SizedBox extends SingleChildRenderObjectWidget { /// Creates a fixed size box. The [width] and [height] parameters can be null /// to indicate that the size of the box should not be constrained in @@ -1028,7 +1133,7 @@ class SizedBox extends SingleChildRenderObjectWidget { @override RenderConstrainedBox createRenderObject(BuildContext context) => new RenderConstrainedBox( - additionalConstraints: _additionalConstraints + additionalConstraints: _additionalConstraints, ); BoxConstraints get _additionalConstraints { @@ -1062,8 +1167,32 @@ class SizedBox extends SingleChildRenderObjectWidget { /// A widget that imposes additional constraints on its child. /// /// For example, if you wanted [child] to have a minimum height of 50.0 logical -/// pixels, you could use `const BoxConstraints(minHeight: 50.0)`` as the +/// pixels, you could use `const BoxConstraints(minHeight: 50.0)` as the /// [constraints]. +/// +/// ## Sample code +/// +/// This snippet makes the child widget (a [Card] with some [Text]) fill the +/// parent, by applying [BoxConstraints.expand] constraints: +/// +/// ```dart +/// new ConstrainedBox( +/// constraints: const BoxConstraints.expand(), +/// child: const Card(child: const Text('Hello World!')), +/// ) +/// ``` +/// +/// The same behaviour can be obtained using the [new SizedBox.expand] widget. +/// +/// See also: +/// +/// * [BoxConstraints], the class that describes constraints. +/// * [SizedBox], which lets you specify tight constraints by explicitly +/// specifying the height or width. +/// * [FractionallySizedBox], a widget that sizes its child to a fraction of +/// the total available space. +/// * [AspectRatio], a widget that attempts to fit within the parent's +/// constraints while also sizing its child to match a given sapect ratio. class ConstrainedBox extends SingleChildRenderObjectWidget { /// Creates a widget that imposes additional constraints on its child. /// @@ -1190,6 +1319,13 @@ class FractionallySizedBox extends SingleChildRenderObjectWidget { /// This is useful when composing widgets that normally try to match their /// parents' size, so that they behave reasonably in lists (which are /// unbounded). +/// +/// See also: +/// +/// * [ConstrainedBox], which applies its constraints in all cases, not just +/// when the incoming constraints are unbounded. +/// * [SizedBox], which lets you specify tight constraints by explicitly +/// specifying the height or width. class LimitedBox extends SingleChildRenderObjectWidget { /// Creates a box that limits its size only when it's unconstrained. /// @@ -1199,7 +1335,7 @@ class LimitedBox extends SingleChildRenderObjectWidget { Key key, this.maxWidth: double.INFINITY, this.maxHeight: double.INFINITY, - Widget child + Widget child, }) : assert(maxWidth != null && maxWidth >= 0.0), assert(maxHeight != null && maxHeight >= 0.0), super(key: key, child: child); @@ -2188,6 +2324,64 @@ class Flex extends MultiChildRenderObjectWidget { /// ) /// ``` /// +/// ## Troubleshooting +/// +/// ### Why is my row turning red? +/// +/// If the contents of the row overflow, meaning that together they are wider +/// than the row, then the row runs out of space to give its [Expanded] and +/// [Flexible] children, and reports this by drawing a red warning box on the +/// edge that is overflowing. +/// +/// #### Story time +/// +/// Suppose, for instance, that you had this code: +/// +/// ```dart +/// new Row( +/// children: [ +/// const FlutterLogo(), +/// const Text('Flutter\'s hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android.'), +/// const Icon(Icons.sentiment_very_satisfied), +/// ], +/// ) +/// ``` +/// +/// The row first asks its first child, the [FlutterLogo], to lay out, at +/// whatever size the logo would like. The logo is friendly and happily decides +/// to be 24 pixels to a side. This leaves lots of room for the next child. The +/// row then asks that next child, the text, to lay out, at whatever size it +/// thinks is best. +/// +/// At this point, the text, not knowing how wide is too wide, says "Ok, I will +/// be thiiiiiiiiiiiiiiiiiiiis wide.", and goes well beyond the space that the +/// row has available, not wrapping. The row responds, "That's not fair, now I +/// have no more room available for my other children!", and gets angry and +/// turns red. +/// +/// The fix is to wrap the second child in an [Expanded] widget, which tells the +/// row that the child should be given the remaining room: +/// +/// ```dart +/// new Row( +/// children: [ +/// const FlutterLogo(), +/// const Expanded( +/// child: const Text('Flutter\'s hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android.'), +/// ), +/// const Icon(Icons.sentiment_very_satisfied), +/// ], +/// ) +/// ``` +/// +/// Now, the row first asks the logo to lay out, and then asks the _icon_ to lay +/// out. The [Icon], like the logo, is happy to take on a reasonable size (also +/// 24 pixels, not coincidentally, since both [FlutterLogo] and [Icon] honor the +/// ambient [IconTheme]). This leaves some room left over, and now the row tells +/// the text exactly how wide to be: the exact width of the remaining space. The +/// text, now happy to comply to a reasonable request, wraps the text within +/// that width, and you end up with a paragraph split over several lines. +/// /// ## Layout algorithm /// /// _This section describes how a [Row] is rendered by the framework._ From c63ac6b33af23eccccb080e90ad20f3fb4ba3380 Mon Sep 17 00:00:00 2001 From: Mikkel Nygaard Ravn Date: Thu, 1 Jun 2017 22:09:28 +0200 Subject: [PATCH 006/110] Upgrade to new template (#10374) --- .../animated_list/MainActivity.java | 15 +++----- .../ios/Runner.xcodeproj/project.pbxproj | 12 +++--- examples/catalog/ios/Runner/AppDelegate.m | 37 +++---------------- examples/catalog/ios/Runner/PluginRegistry.h | 18 --------- examples/catalog/ios/Runner/PluginRegistry.m | 15 -------- examples/catalog/lib/main.dart | 7 ++++ 6 files changed, 24 insertions(+), 80 deletions(-) delete mode 100644 examples/catalog/ios/Runner/PluginRegistry.h delete mode 100644 examples/catalog/ios/Runner/PluginRegistry.m create mode 100644 examples/catalog/lib/main.dart diff --git a/examples/catalog/android/app/src/main/java/com/yourcompany/animated_list/MainActivity.java b/examples/catalog/android/app/src/main/java/com/yourcompany/animated_list/MainActivity.java index 7ba5d182f6d14..4b45672f6ac72 100644 --- a/examples/catalog/android/app/src/main/java/com/yourcompany/animated_list/MainActivity.java +++ b/examples/catalog/android/app/src/main/java/com/yourcompany/animated_list/MainActivity.java @@ -2,15 +2,12 @@ import android.os.Bundle; import io.flutter.app.FlutterActivity; -import io.flutter.plugins.PluginRegistry; +import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { - PluginRegistry pluginRegistry; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - pluginRegistry = new PluginRegistry(); - pluginRegistry.registerAll(this); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } } diff --git a/examples/catalog/ios/Runner.xcodeproj/project.pbxproj b/examples/catalog/ios/Runner.xcodeproj/project.pbxproj index e6291935a57a6..43c2147621e94 100644 --- a/examples/catalog/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/catalog/ios/Runner.xcodeproj/project.pbxproj @@ -7,10 +7,10 @@ objects = { /* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* PluginRegistry.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74970F651EDBF3AE000507F3 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 74970F641EDBF3AE000507F3 /* GeneratedPluginRegistrant.m */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; @@ -39,10 +39,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* PluginRegistry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PluginRegistry.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* PluginRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PluginRegistry.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 74970F631EDBF3AE000507F3 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 74970F641EDBF3AE000507F3 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -114,6 +114,8 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 74970F631EDBF3AE000507F3 /* GeneratedPluginRegistrant.h */, + 74970F641EDBF3AE000507F3 /* GeneratedPluginRegistrant.m */, 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, @@ -121,8 +123,6 @@ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* PluginRegistry.h */, - 1498D2331E8E89220040F4C2 /* PluginRegistry.m */, ); path = Runner; sourceTree = ""; @@ -252,7 +252,7 @@ files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, - 1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */, + 74970F651EDBF3AE000507F3 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/catalog/ios/Runner/AppDelegate.m b/examples/catalog/ios/Runner/AppDelegate.m index 3b6ab51b9e117..d4277583c2430 100644 --- a/examples/catalog/ios/Runner/AppDelegate.m +++ b/examples/catalog/ios/Runner/AppDelegate.m @@ -1,38 +1,11 @@ #include "AppDelegate.h" -#include "PluginRegistry.h" +#include "GeneratedPluginRegistrant.h" -@implementation AppDelegate { - PluginRegistry *plugins; -} +@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - FlutterViewController *flutterController = - (FlutterViewController *)self.window.rootViewController; - plugins = [[PluginRegistry alloc] initWithController:flutterController]; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; } - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - @end diff --git a/examples/catalog/ios/Runner/PluginRegistry.h b/examples/catalog/ios/Runner/PluginRegistry.h deleted file mode 100644 index df039db5157cd..0000000000000 --- a/examples/catalog/ios/Runner/PluginRegistry.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Generated file. Do not edit. -// - -#ifndef PluginRegistry_h -#define PluginRegistry_h - -#import - - -@interface PluginRegistry : NSObject - - -- (instancetype)initWithController:(FlutterViewController *)controller; - -@end - -#endif /* PluginRegistry_h */ diff --git a/examples/catalog/ios/Runner/PluginRegistry.m b/examples/catalog/ios/Runner/PluginRegistry.m deleted file mode 100644 index 0a3472994685d..0000000000000 --- a/examples/catalog/ios/Runner/PluginRegistry.m +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -#import "PluginRegistry.h" - -@implementation PluginRegistry - -- (instancetype)initWithController:(FlutterViewController *)controller { - if (self = [super init]) { - } - return self; -} - -@end diff --git a/examples/catalog/lib/main.dart b/examples/catalog/lib/main.dart new file mode 100644 index 0000000000000..af45e3cd425c7 --- /dev/null +++ b/examples/catalog/lib/main.dart @@ -0,0 +1,7 @@ +// Copyright 2017 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/widgets.dart'; + +void main() => runApp(const Center(child: const Text('flutter run -t lib/xxx.dart'))); From 457554beaf59697e97d0ef7919b35402ad628e76 Mon Sep 17 00:00:00 2001 From: Rafal Wachol Date: Fri, 2 Jun 2017 06:47:40 +0900 Subject: [PATCH 007/110] Added test for box decoration hit (#10438) --- .../test/widgets/box_decoration_test.dart | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/packages/flutter/test/widgets/box_decoration_test.dart b/packages/flutter/test/widgets/box_decoration_test.dart index 7adf5fa51bdd5..667652b4166f0 100644 --- a/packages/flutter/test/widgets/box_decoration_test.dart +++ b/packages/flutter/test/widgets/box_decoration_test.dart @@ -86,4 +86,82 @@ void main() { expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill)); }); + + testWidgets('Can hit test on BoxDecoration', (WidgetTester tester) async { + + List itemsTapped; + + final Key key = const Key('Container with BoxDecoration'); + Widget buildFrame(Border border) { + itemsTapped = []; + return new Center( + child: new GestureDetector( + behavior: HitTestBehavior.deferToChild, + child: new Container( + key: key, + width: 100.0, + height: 50.0, + decoration: new BoxDecoration(border: border), + ), + onTap: () { + itemsTapped.add(1); + }, + ) + ); + } + + await tester.pumpWidget(buildFrame(new Border.all())); + expect(itemsTapped, isEmpty); + + await tester.tap(find.byKey(key)); + expect(itemsTapped, [1]); + + await tester.tapAt(const Offset(350.0, 275.0)); + expect(itemsTapped, [1,1]); + + await tester.tapAt(const Offset(449.0, 324.0)); + expect(itemsTapped, [1,1,1]); + + }); + + testWidgets('Can hit test on BoxDecoration circle', (WidgetTester tester) async { + + List itemsTapped; + + final Key key = const Key('Container with BoxDecoration'); + Widget buildFrame(Border border) { + itemsTapped = []; + return new Center( + child: new GestureDetector( + behavior: HitTestBehavior.deferToChild, + child: new Container( + key: key, + width: 100.0, + height: 50.0, + decoration: new BoxDecoration(border: border, shape: BoxShape.circle), + ), + onTap: () { + itemsTapped.add(1); + }, + ) + ); + } + + await tester.pumpWidget(buildFrame(new Border.all())); + expect(itemsTapped, isEmpty); + + await tester.tapAt(const Offset(0.0, 0.0)); + expect(itemsTapped, isEmpty); + + await tester.tapAt(const Offset(350.0, 275.0)); + expect(itemsTapped, isEmpty); + + await tester.tapAt(const Offset(400.0, 300.0)); + expect(itemsTapped, [1]); + + await tester.tap(find.byKey(key)); + expect(itemsTapped, [1,1]); + + }); + } From a8777ce6b089e3a8ae323af75b4d4695238adfa1 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 1 Jun 2017 16:24:14 -0700 Subject: [PATCH 008/110] CupertinoNavigationBar part 1 - extract common layout logic (#10337) Extract layout logic in material app bar to a common file that can be reused for cupertino --- .../flutter/lib/src/material/app_bar.dart | 117 +++------------ .../lib/src/widgets/navigation_toolbar.dart | 137 ++++++++++++++++++ packages/flutter/lib/widgets.dart | 1 + .../flutter/test/material/app_bar_test.dart | 18 ++- 4 files changed, 174 insertions(+), 99 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/navigation_toolbar.dart diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 7c7ab7b8a5062..c11f6e437fdd7 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -21,70 +21,7 @@ import 'tabs.dart'; import 'theme.dart'; import 'typography.dart'; -enum _ToolbarSlot { - leading, - title, - actions, -} - -class _ToolbarLayout extends MultiChildLayoutDelegate { - _ToolbarLayout({ this.centerTitle }); - - // If false the title should be left or right justified within the space bewteen - // the leading and actions widgets, depending on the locale's writing direction. - // If true the title is centered within the toolbar (not within the horizontal - // space bewteen the leading and actions widgets). - final bool centerTitle; - - static const double kLeadingWidth = 56.0; // So it's square with kToolbarHeight. - static const double kTitleLeftWithLeading = 72.0; // As per https://material.io/guidelines/layout/metrics-keylines.html#metrics-keylines-keylines-spacing. - static const double kTitleLeftWithoutLeading = 16.0; - - @override - void performLayout(Size size) { - double actionsWidth = 0.0; - - if (hasChild(_ToolbarSlot.leading)) { - final BoxConstraints constraints = new BoxConstraints.tight(new Size(kLeadingWidth, size.height)); - layoutChild(_ToolbarSlot.leading, constraints); - positionChild(_ToolbarSlot.leading, Offset.zero); - } - - if (hasChild(_ToolbarSlot.actions)) { - final BoxConstraints constraints = new BoxConstraints.loose(size); - final Size actionsSize = layoutChild(_ToolbarSlot.actions, constraints); - final double actionsLeft = size.width - actionsSize.width; - final double actionsTop = (size.height - actionsSize.height) / 2.0; - actionsWidth = actionsSize.width; - positionChild(_ToolbarSlot.actions, new Offset(actionsLeft, actionsTop)); - } - - if (hasChild(_ToolbarSlot.title)) { - final double titleLeftMargin = - hasChild(_ToolbarSlot.leading) ? kTitleLeftWithLeading : kTitleLeftWithoutLeading; - final double maxWidth = math.max(size.width - titleLeftMargin - actionsWidth, 0.0); - final BoxConstraints constraints = new BoxConstraints.loose(size).copyWith(maxWidth: maxWidth); - final Size titleSize = layoutChild(_ToolbarSlot.title, constraints); - final double titleY = (size.height - titleSize.height) / 2.0; - double titleX = titleLeftMargin; - - // If the centered title will not fit between the leading and actions - // widgets, then align its left or right edge with the adjacent boundary. - if (centerTitle) { - titleX = (size.width - titleSize.width) / 2.0; - if (titleX + titleSize.width > size.width - actionsWidth) - titleX = size.width - actionsWidth - titleSize.width; - else if (titleX < titleLeftMargin) - titleX = titleLeftMargin; - } - - positionChild(_ToolbarSlot.title, new Offset(titleX, titleY)); - } - } - - @override - bool shouldRelayout(_ToolbarLayout oldDelegate) => centerTitle != oldDelegate.centerTitle; -} +const double _kLeadingWidth = kToolbarHeight; // So the leading button is square. // Bottom justify the kToolbarHeight child which may overflow the top. class _ToolbarContainerLayout extends SingleChildLayoutDelegate { @@ -390,7 +327,6 @@ class _AppBarState extends State { ); } - final List toolbarChildren = []; Widget leading = widget.leading; if (leading == null) { if (hasDrawer) { @@ -405,47 +341,38 @@ class _AppBarState extends State { } } if (leading != null) { - toolbarChildren.add( - new LayoutId( - id: _ToolbarSlot.leading, - child: leading - ) + leading = new ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: _kLeadingWidth), + child: leading, ); } - if (widget.title != null) { - toolbarChildren.add( - new LayoutId( - id: _ToolbarSlot.title, - child: new DefaultTextStyle( - style: centerStyle, - softWrap: false, - overflow: TextOverflow.ellipsis, - child: widget.title, - ), - ), + Widget title = widget.title; + if (title != null) { + title = new DefaultTextStyle( + style: centerStyle, + softWrap: false, + overflow: TextOverflow.ellipsis, + child: title, ); } + + Widget actions; if (widget.actions != null && widget.actions.isNotEmpty) { - toolbarChildren.add( - new LayoutId( - id: _ToolbarSlot.actions, - child: new Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: widget.actions, - ), - ), + actions = new Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: widget.actions, ); } final Widget toolbar = new Padding( padding: const EdgeInsets.only(right: 4.0), - child: new CustomMultiChildLayout( - delegate: new _ToolbarLayout( - centerTitle: widget._getEffectiveCenterTitle(themeData), - ), - children: toolbarChildren, + child: new NavigationToolbar( + leading: leading, + middle: title, + trailing: actions, + centerMiddle: widget._getEffectiveCenterTitle(themeData), ), ); diff --git a/packages/flutter/lib/src/widgets/navigation_toolbar.dart b/packages/flutter/lib/src/widgets/navigation_toolbar.dart new file mode 100644 index 0000000000000..5a4ef6af2ada7 --- /dev/null +++ b/packages/flutter/lib/src/widgets/navigation_toolbar.dart @@ -0,0 +1,137 @@ +// Copyright 2017 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:math' as math; + +import 'package:flutter/rendering.dart'; + +import 'basic.dart'; +import 'framework.dart'; + +/// [NavigationToolbar] is a layout helper to position 3 widgets or groups of +/// widgets along a horizontal axis that's sensible for an application's +/// navigation bar such as in Material Design and in iOS. +/// +/// [leading] and [trailing] widgets occupy the edges of the widget with +/// reasonable size constraints while the [middle] widget occupies the remaining +/// space in either a center aligned or start aligned fashion. +/// +/// Either directly use the themed app bars such as the Material [AppBar] or +/// the iOS [CupertinoNavigationBar] or wrap this widget with more theming +/// specifications for your own custom app bar. +class NavigationToolbar extends StatelessWidget { + const NavigationToolbar({ + Key key, + this.leading, + this.middle, + this.trailing, + this.centerMiddle: true, + }) : assert(centerMiddle != null), + super(key: key); + + /// Widget to place at the start of the horizontal toolbar. + final Widget leading; + + /// Widget to place in the middle of the horizontal toolbar, occupying + /// as much remaining space as possible. + final Widget middle; + + /// Widget to place at the end of the horizontal toolbar. + final Widget trailing; + + /// Whether to align the [middle] widget to the center of this widget or + /// next to the [leading] widget when false. + final bool centerMiddle; + + @override + Widget build(BuildContext context) { + final List children = []; + + if (leading != null) + children.add(new LayoutId(id: _ToolbarSlot.leading, child: leading)); + + if (middle != null) + children.add(new LayoutId(id: _ToolbarSlot.middle, child: middle)); + + if (trailing != null) + children.add(new LayoutId(id: _ToolbarSlot.trailing, child: trailing)); + + return new CustomMultiChildLayout( + delegate: new _ToolbarLayout( + centerMiddle: centerMiddle, + ), + children: children, + ); + } +} + +enum _ToolbarSlot { + leading, + middle, + trailing, +} + +const double _kMiddleMargin = 16.0; + +// TODO(xster): support RTL. +class _ToolbarLayout extends MultiChildLayoutDelegate { + _ToolbarLayout({ this.centerMiddle }); + + // If false the middle widget should be left justified within the space + // between the leading and trailing widgets. + // If true the middle widget is centered within the toolbar (not within the horizontal + // space bewteen the leading and trailing widgets). + // TODO(xster): document RTL once supported. + final bool centerMiddle; + + @override + void performLayout(Size size) { + double leadingWidth = 0.0; + double trailingWidth = 0.0; + + if (hasChild(_ToolbarSlot.leading)) { + final BoxConstraints constraints = new BoxConstraints( + minWidth: 0.0, + maxWidth: size.width / 3.0, // The leading widget shouldn't take up more than 1/3 of the space. + minHeight: size.height, // The height should be exactly the height of the bar. + maxHeight: size.height, + ); + leadingWidth = layoutChild(_ToolbarSlot.leading, constraints).width; + positionChild(_ToolbarSlot.leading, Offset.zero); + } + + if (hasChild(_ToolbarSlot.trailing)) { + final BoxConstraints constraints = new BoxConstraints.loose(size); + final Size trailingSize = layoutChild(_ToolbarSlot.trailing, constraints); + final double trailingLeft = size.width - trailingSize.width; + final double trailingTop = (size.height - trailingSize.height) / 2.0; + trailingWidth = trailingSize.width; + positionChild(_ToolbarSlot.trailing, new Offset(trailingLeft, trailingTop)); + } + + if (hasChild(_ToolbarSlot.middle)) { + final double maxWidth = math.max(size.width - leadingWidth - trailingWidth - _kMiddleMargin * 2.0, 0.0); + final BoxConstraints constraints = new BoxConstraints.loose(size).copyWith(maxWidth: maxWidth); + final Size middleSize = layoutChild(_ToolbarSlot.middle, constraints); + + final double middleLeftMargin = leadingWidth + _kMiddleMargin; + double middleX = middleLeftMargin; + final double middleY = (size.height - middleSize.height) / 2.0; + // If the centered middle will not fit between the leading and trailing + // widgets, then align its left or right edge with the adjacent boundary. + if (centerMiddle) { + middleX = (size.width - middleSize.width) / 2.0; + if (middleX + middleSize.width > size.width - trailingWidth) + middleX = size.width - trailingWidth - middleSize.width; + else if (middleX < middleLeftMargin) + middleX = middleLeftMargin; + } + + positionChild(_ToolbarSlot.middle, new Offset(middleX, middleY)); + } + } + + @override + bool shouldRelayout(_ToolbarLayout oldDelegate) => centerMiddle != oldDelegate.centerMiddle; +} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index a411ff948bfab..6811b8e747938 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -46,6 +46,7 @@ export 'src/widgets/layout_builder.dart'; export 'src/widgets/locale_query.dart'; export 'src/widgets/media_query.dart'; export 'src/widgets/modal_barrier.dart'; +export 'src/widgets/navigation_toolbar.dart'; export 'src/widgets/navigator.dart'; export 'src/widgets/nested_scroll_view.dart'; export 'src/widgets/notification_listener.dart'; diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 293ffbfc559ca..36ac6f744e846 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -216,8 +216,12 @@ void main() { final Finder title = find.byKey(titleKey); expect(tester.getTopLeft(title).dx, 72.0); - // The toolbar's contents are padded on the right by 4.0 - expect(tester.getSize(title).width, equals(800.0 - 72.0 - 4.0)); + expect(tester.getSize(title).width, equals( + 800.0 // Screen width. + - 4.0 // Left margin before the leading button. + - 56.0 // Leading button width. + - 16.0 // Leading button to title padding. + - 16.0)); // Title right side padding. actions = [ const SizedBox(width: 100.0), @@ -227,13 +231,19 @@ void main() { expect(tester.getTopLeft(title).dx, 72.0); // The title shrinks by 200.0 to allow for the actions widgets. - expect(tester.getSize(title).width, equals(800.0 - 72.0 - 4.0 - 200.0)); + expect(tester.getSize(title).width, equals( + 800.0 // Screen width. + - 4.0 // Left margin before the leading button. + - 56.0 // Leading button width. + - 16.0 // Leading button to title padding. + - 16.0 // Title to actions padding + - 200.0)); // Actions' width. leading = new Container(); // AppBar will constrain the width to 24.0 await tester.pumpWidget(buildApp()); expect(tester.getTopLeft(title).dx, 72.0); // Adding a leading widget shouldn't effect the title's size - expect(tester.getSize(title).width, equals(800.0 - 72.0 - 4.0 - 200.0)); + expect(tester.getSize(title).width, equals(800.0 - 4.0 - 56.0 - 16.0 - 16.0 - 200.0)); }); testWidgets('AppBar centerTitle:true title overflow OK ', (WidgetTester tester) async { From 2aaa7f88b3196730295cd0d439f33b1c6d439c00 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 1 Jun 2017 17:17:21 -0700 Subject: [PATCH 009/110] CupertinoNavigationBar part 2 - create a bare bone CupertinoNavigationBar (#10423) Create a CupertinoNavigationBar without automatic adding of the lead button --- packages/flutter/lib/cupertino.dart | 1 + .../lib/src/cupertino/bottom_tab_bar.dart | 21 ++- .../flutter/lib/src/cupertino/nav_bar.dart | 146 ++++++++++++++++++ .../flutter/test/cupertino/nav_bar_test.dart | 71 +++++++++ 4 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 packages/flutter/lib/src/cupertino/nav_bar.dart create mode 100644 packages/flutter/test/cupertino/nav_bar_test.dart diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index 684c2fe4dbf4b..2726576eb2cdb 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -12,6 +12,7 @@ export 'src/cupertino/bottom_tab_bar.dart'; export 'src/cupertino/button.dart'; export 'src/cupertino/colors.dart'; export 'src/cupertino/dialog.dart'; +export 'src/cupertino/nav_bar.dart'; export 'src/cupertino/page.dart'; export 'src/cupertino/slider.dart'; export 'src/cupertino/switch.dart'; diff --git a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart index 8a0ddded09cd0..cd4cc55f9872d 100644 --- a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart +++ b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart @@ -13,7 +13,24 @@ import 'colors.dart'; const double _kTabBarHeight = 50.0; const Color _kDefaultTabBarBackgroundColor = const Color(0xCCF8F8F8); - +const Color _kDefaultTabBarBorderColor = const Color(0x4C000000); + +/// An iOS styled bottom navigation tab bar. +/// +/// Displays multiple tabs using [BottomNavigationBarItem] with one tab being +/// active, the first tab by default. +/// +/// This [StatelessWidget] doesn't store the active tab itself. You must +/// listen to the [onTap] callbacks and call `setState` with a new [currentIndex] +/// for the new selection to reflect. +/// +/// Tab changes typically trigger a switch between [Navigator]s, each with its +/// own navigation stack, per standard iOS design. +/// +/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by +/// default), it will produce a blurring effect to the content behind it. +/// +// TODO(xster): document using with a CupertinoScaffold. class CupertinoTabBar extends StatelessWidget { CupertinoTabBar({ Key key, @@ -72,7 +89,7 @@ class CupertinoTabBar extends StatelessWidget { decoration: new BoxDecoration( border: const Border( top: const BorderSide( - color: const Color(0x4C000000), + color: _kDefaultTabBarBorderColor, width: 0.0, // One physical pixel. style: BorderStyle.solid, ), diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart new file mode 100644 index 0000000000000..0ee64fe912cd3 --- /dev/null +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -0,0 +1,146 @@ +// Copyright 2017 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' show ImageFilter; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +import 'colors.dart'; + +// Standard iOS 10 nav bar height without the status bar. +const double _kNavBarHeight = 44.0; + +const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8); +const Color _kDefaultNavBarBorderColor = const Color(0x4C000000); + +/// An iOS styled navigation bar. +/// +/// The navigation bar is a toolbar that minimally consists of a widget, normally +/// a page title, in the [middle] of the toolbar. +/// +/// It also supports a [leading] and [trailing] widget before and after the +/// [middle] widget while keeping the [middle] widget centered. +/// +/// It should be placed at top of the screen and automatically accounts for +/// the OS's status bar. +/// +/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by +/// default), it will produce a blurring effect to the content behind it. +/// +// TODO(xster): document automatic addition of a CupertinoBackButton. +// TODO(xster): add sample code using icons. +// TODO(xster): document integration into a CupertinoScaffold. +class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWidget { + const CupertinoNavigationBar({ + Key key, + this.leading, + @required this.middle, + this.trailing, + this.backgroundColor: _kDefaultNavBarBackgroundColor, + this.actionsForegroundColor: CupertinoColors.activeBlue, + }) : assert(middle != null, 'There must be a middle widget, usually a title'), + super(key: key); + + /// Widget to place at the start of the nav bar. Normally a back button + /// for a normal page or a cancel button for full page dialogs. + final Widget leading; + + /// Widget to place in the middle of the nav bar. Normally a title or + /// a segmented control. + final Widget middle; + + /// Widget to place at the end of the nav bar. Normally additional actions + /// taken on the page such as a search or edit function. + final Widget trailing; + + // TODO(xster): implement support for double row nav bars. + + /// The background color of the nav bar. If it contains transparency, the + /// tab bar will automatically produce a blurring effect to the content + /// behind it. + final Color backgroundColor; + + /// Default color used for text and icons of the [leading] and [trailing] + /// widgets in the nav bar. + /// + /// The [title] remains black if it's a text as per iOS standard design. + final Color actionsForegroundColor; + + @override + Size get preferredSize => const Size.fromHeight(_kNavBarHeight); + + @override + Widget build(BuildContext context) { + final bool addBlur = backgroundColor.alpha != 0xFF; + + Widget styledMiddle = middle; + if (styledMiddle.runtimeType == Text || styledMiddle.runtimeType == DefaultTextStyle) { + // Let the middle be black rather than `actionsForegroundColor` in case + // it's a plain text title. + styledMiddle = DefaultTextStyle.merge( + style: const TextStyle(color: CupertinoColors.black), + child: middle, + ); + } + + // TODO(xster): automatically build a CupertinoBackButton. + + Widget result = new DecoratedBox( + decoration: new BoxDecoration( + border: const Border( + bottom: const BorderSide( + color: _kDefaultNavBarBorderColor, + width: 0.0, // One physical pixel. + style: BorderStyle.solid, + ), + ), + color: backgroundColor, + ), + child: new SizedBox( + height: _kNavBarHeight + MediaQuery.of(context).padding.top, + child: IconTheme.merge( + data: new IconThemeData( + color: actionsForegroundColor, + size: 22.0, + ), + child: DefaultTextStyle.merge( + style: new TextStyle( + fontSize: 17.0, + letterSpacing: -0.24, + color: actionsForegroundColor, + ), + child: new Padding( + padding: new EdgeInsets.only( + top: MediaQuery.of(context).padding.top, + // TODO(xster): dynamically reduce padding when an automatic + // CupertinoBackButton is present. + left: 16.0, + right: 16.0, + ), + child: new NavigationToolbar( + leading: leading, + middle: styledMiddle, + trailing: trailing, + centerMiddle: true, + ), + ), + ), + ), + ), + ); + + if (addBlur) { + // For non-opaque backgrounds, apply a blur effect. + result = new ClipRect( + child: new BackdropFilter( + filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: result, + ), + ); + } + + return result; + } +} diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart new file mode 100644 index 0000000000000..d143ac59a19e7 --- /dev/null +++ b/packages/flutter/test/cupertino/nav_bar_test.dart @@ -0,0 +1,71 @@ +// Copyright 2017 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 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Middle still in center with asymmetrical actions', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return const CupertinoNavigationBar( + leading: const CupertinoButton(child: const Text('Something'), onPressed: null,), + middle: const Text('Title'), + ); + }, + ); + }, + ), + ); + + // Expect the middle of the title to be exactly in the middle of the screen. + expect(tester.getCenter(find.text('Title')).dx, 400.0); + }); + + testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return const CupertinoNavigationBar( + middle: const Text('Title'), + backgroundColor: const Color(0xFFE5E5E5), + ); + }, + ); + }, + ), + ); + expect(find.byType(BackdropFilter), findsNothing); + }); + + testWidgets('Non-opaque background adds blur effects', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return const CupertinoNavigationBar( + middle: const Text('Title'), + ); + }, + ); + }, + ), + ); + expect(find.byType(BackdropFilter), findsOneWidget); + }); +} \ No newline at end of file From e3bab988cef13a52bb3264dcba459387ddc29224 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Fri, 2 Jun 2017 13:15:49 +0200 Subject: [PATCH 010/110] preview of prefer_asserts_in_initializer_list lint (#10441) * preview of prefer_asserts_in_initializer_list lint * fix issue --- .../lib/src/widgets/animated_list.dart | 7 +- packages/flutter/lib/src/widgets/banner.dart | 10 +- packages/flutter/lib/src/widgets/basic.dart | 15 +-- .../flutter/lib/src/widgets/container.dart | 21 ++-- .../flutter/lib/src/widgets/dismissible.dart | 7 +- .../flutter/lib/src/widgets/drag_target.dart | 7 +- .../flutter/lib/src/widgets/framework.dart | 17 ++- .../lib/src/widgets/gesture_detector.dart | 51 ++++---- packages/flutter/lib/src/widgets/heroes.dart | 4 +- .../lib/src/widgets/implicit_animations.dart | 21 ++-- .../flutter/lib/src/widgets/navigator.dart | 4 +- .../lib/src/widgets/nested_scroll_view.dart | 18 ++- packages/flutter/lib/src/widgets/overlay.dart | 28 ++--- .../lib/src/widgets/overscroll_indicator.dart | 8 +- .../flutter/lib/src/widgets/page_view.dart | 33 +++--- packages/flutter/lib/src/widgets/pages.dart | 13 +- .../lib/src/widgets/scroll_activity.dart | 20 ++-- .../lib/src/widgets/scroll_controller.dart | 4 +- .../lib/src/widgets/scroll_notification.dart | 11 +- .../lib/src/widgets/scroll_position.dart | 7 +- .../lib/src/widgets/scroll_simulation.dart | 15 ++- .../flutter/lib/src/widgets/scroll_view.dart | 112 +++++++++--------- .../src/widgets/single_child_scroll_view.dart | 22 ++-- .../widgets/size_changed_layout_notifier.dart | 5 +- packages/flutter/lib/src/widgets/table.dart | 74 ++++++------ .../lib/src/widgets/text_selection.dart | 6 +- packages/flutter/lib/src/widgets/title.dart | 5 +- .../flutter/lib/src/widgets/viewport.dart | 13 +- 28 files changed, 261 insertions(+), 297 deletions(-) diff --git a/packages/flutter/lib/src/widgets/animated_list.dart b/packages/flutter/lib/src/widgets/animated_list.dart index 98ef388e41961..6f0a55864ff16 100644 --- a/packages/flutter/lib/src/widgets/animated_list.dart +++ b/packages/flutter/lib/src/widgets/animated_list.dart @@ -57,10 +57,9 @@ class AnimatedList extends StatefulWidget { this.physics, this.shrinkWrap: false, this.padding, - }) : super(key: key) { - assert(itemBuilder != null); - assert(initialItemCount != null && initialItemCount >= 0); - } + }) : assert(itemBuilder != null), + assert(initialItemCount != null && initialItemCount >= 0), + super(key: key); /// Called, as needed, to build list item widgets. /// diff --git a/packages/flutter/lib/src/widgets/banner.dart b/packages/flutter/lib/src/widgets/banner.dart index ccd08f601b07f..2ca61a86553f9 100644 --- a/packages/flutter/lib/src/widgets/banner.dart +++ b/packages/flutter/lib/src/widgets/banner.dart @@ -47,12 +47,10 @@ class BannerPainter extends CustomPainter { @required this.location, this.color: _kColor, this.textStyle: _kTextStyle, - }) { - assert(message != null); - assert(location != null); - assert(color != null); - assert(textStyle != null); - } + }) : assert(message != null), + assert(location != null), + assert(color != null), + assert(textStyle != null); /// The message to show in the banner. final String message; diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 843aa419e7aff..11d4c3ad9de8f 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -1202,9 +1202,8 @@ class ConstrainedBox extends SingleChildRenderObjectWidget { @required this.constraints, Widget child }) : assert(constraints != null), - super(key: key, child: child) { - assert(constraints.debugAssertIsValid()); - } + assert(constraints.debugAssertIsValid()), + super(key: key, child: child); /// The additional constraints to impose on the child. final BoxConstraints constraints; @@ -2222,9 +2221,8 @@ class Flex extends MultiChildRenderObjectWidget { assert(mainAxisAlignment != null), assert(mainAxisSize != null), assert(crossAxisAlignment != null), - super(key: key, children: children) { - assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null); // https://github.com/dart-lang/sdk/issues/29278 - } + assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),// https://github.com/dart-lang/sdk/issues/29278 + super(key: key, children: children); /// The direction to use as the main axis. /// @@ -2900,9 +2898,8 @@ class Flow extends MultiChildRenderObjectWidget { Key key, @required this.delegate, List children: const [], - }) : super(key: key, children: children) { - assert(delegate != null); - } + }) : assert(delegate != null), + super(key: key, children: children); /// The delegate that controls the transformation matrices of the children. final FlowDelegate delegate; diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart index ad1d96d9e2bd6..53f93bff09615 100644 --- a/packages/flutter/lib/src/widgets/container.dart +++ b/packages/flutter/lib/src/widgets/container.dart @@ -198,22 +198,21 @@ class Container extends StatelessWidget { this.margin, this.transform, this.child, - }) : decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null), + }) : assert(margin == null || margin.isNonNegative), + assert(padding == null || padding.isNonNegative), + assert(decoration == null || decoration.debugAssertIsValid()), + assert(constraints == null || constraints.debugAssertIsValid()), + assert(color == null || decoration == null, + 'Cannot provide both a color and a decoration\n' + 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".' + ), + decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null), constraints = (width != null || height != null) ? constraints?.tighten(width: width, height: height) ?? new BoxConstraints.tightFor(width: width, height: height) : constraints, - super(key: key) { - assert(margin == null || margin.isNonNegative); - assert(padding == null || padding.isNonNegative); - assert(decoration == null || decoration.debugAssertIsValid()); - assert(constraints == null || constraints.debugAssertIsValid()); - assert(color == null || decoration == null, - 'Cannot provide both a color and a decoration\n' - 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".' - ); - } + super(key: key); /// The [child] contained by the container. /// diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart index 79724b3e3d837..1df7adf979b8d 100644 --- a/packages/flutter/lib/src/widgets/dismissible.dart +++ b/packages/flutter/lib/src/widgets/dismissible.dart @@ -129,10 +129,9 @@ class _DismissibleClipper extends CustomClipper { _DismissibleClipper({ @required this.axis, @required this.moveAnimation - }) : super(reclip: moveAnimation) { - assert(axis != null); - assert(moveAnimation != null); - } + }) : assert(axis != null), + assert(moveAnimation != null), + super(reclip: moveAnimation); final Axis axis; final Animation moveAnimation; diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 288252265ab19..2cba6ea3bc103 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -444,10 +444,9 @@ class _DragAvatar extends Drag { this.feedback, this.feedbackOffset: Offset.zero, this.onDragEnd - }) { - assert(overlayState != null); - assert(dragStartPoint != null); - assert(feedbackOffset != null); + }) : assert(overlayState != null), + assert(dragStartPoint != null), + assert(feedbackOffset != null) { _entry = new OverlayEntry(builder: _build); overlayState.insert(_entry); _position = initialPosition; diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 0e0d1ce395fb6..3a7fb14c8f6d6 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1573,9 +1573,8 @@ abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { /// objects. MultiChildRenderObjectWidget({ Key key, this.children: const [] }) : assert(children != null), - super(key: key) { - assert(!children.any((Widget child) => child == null)); // https://github.com/dart-lang/sdk/issues/29276 - } + assert(!children.any((Widget child) => child == null)), // https://github.com/dart-lang/sdk/issues/29276 + super(key: key); /// The widgets below this widget in the tree. /// @@ -2375,9 +2374,9 @@ abstract class Element implements BuildContext { /// Creates an element that uses the given widget as its configuration. /// /// Typically called by an override of [Widget.createElement]. - Element(Widget widget) : _widget = widget { - assert(widget != null); - } + Element(Widget widget) + : assert(widget != null), + _widget = widget; Element _parent; @@ -4363,9 +4362,9 @@ class SingleChildRenderObjectElement extends RenderObjectElement { /// are expected to inherit from [MultiChildRenderObjectWidget]. class MultiChildRenderObjectElement extends RenderObjectElement { /// Creates an element that uses the given widget as its configuration. - MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget) : super(widget) { - assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)); - } + MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget) + : assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)), + super(widget); @override MultiChildRenderObjectWidget get widget => super.widget; diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 9f59ec708653c..e039080a394d8 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -98,32 +98,31 @@ class GestureDetector extends StatelessWidget { this.onScaleEnd, this.behavior, this.excludeFromSemantics: false - }) : super(key: key) { - assert(excludeFromSemantics != null); - assert(() { - final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; - final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; - final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; - final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; - if (havePan || haveScale) { - if (havePan && haveScale) { - throw new FlutterError( - 'Incorrect GestureDetector arguments.\n' - 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.' - ); - } - final String recognizer = havePan ? 'pan' : 'scale'; - if (haveVerticalDrag && haveHorizontalDrag) { - throw new FlutterError( - 'Incorrect GestureDetector arguments.\n' - 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' - 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.' - ); - } - } - return true; - }); - } + }) : assert(excludeFromSemantics != null), + assert(() { + final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; + final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; + final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; + final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; + if (havePan || haveScale) { + if (havePan && haveScale) { + throw new FlutterError( + 'Incorrect GestureDetector arguments.\n' + 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.' + ); + } + final String recognizer = havePan ? 'pan' : 'scale'; + if (haveVerticalDrag && haveHorizontalDrag) { + throw new FlutterError( + 'Incorrect GestureDetector arguments.\n' + 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' + 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.' + ); + } + } + return true; + }), + super(key: key); /// The widget below this widget in the tree. final Widget child; diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index e5eab5e2bf536..ac5bd720d5ed5 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -180,9 +180,7 @@ class _HeroFlightManifest { @required this.fromHero, @required this.toHero, @required this.createRectTween, - }) { - assert(fromHero.widget.tag == toHero.widget.tag); - } + }) : assert(fromHero.widget.tag == toHero.widget.tag); final _HeroFlightType type; final OverlayState overlay; diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 3753638b93437..821674dedd80d 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -318,22 +318,21 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { this.child, Curve curve: Curves.linear, @required Duration duration, - }) : decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null), + }) : assert(margin == null || margin.isNonNegative), + assert(padding == null || padding.isNonNegative), + assert(decoration == null || decoration.debugAssertIsValid()), + assert(constraints == null || constraints.debugAssertIsValid()), + assert(color == null || decoration == null, + 'Cannot provide both a color and a decoration\n' + 'The color argument is just a shorthand for "decoration: new BoxDecoration(backgroundColor: color)".' + ), + decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null), constraints = (width != null || height != null) ? constraints?.tighten(width: width, height: height) ?? new BoxConstraints.tightFor(width: width, height: height) : constraints, - super(key: key, curve: curve, duration: duration) { - assert(margin == null || margin.isNonNegative); - assert(padding == null || padding.isNonNegative); - assert(decoration == null || decoration.debugAssertIsValid()); - assert(constraints == null || constraints.debugAssertIsValid()); - assert(color == null || decoration == null, - 'Cannot provide both a color and a decoration\n' - 'The color argument is just a shorthand for "decoration: new BoxDecoration(backgroundColor: color)".' - ); - } + super(key: key, curve: curve, duration: duration); /// The [child] contained by the container. /// diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index e6ee219b53c42..94ab3b5ebdc84 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -256,8 +256,8 @@ class NavigatorObserver { abstract class NavigationGestureController { /// Configures the NavigationGestureController and tells the given [Navigator] that /// a gesture has started. - NavigationGestureController(this._navigator) { - assert(_navigator != null); + NavigationGestureController(this._navigator) + : assert(_navigator != null) { // Disable Hero transitions until the gesture is complete. _navigator.didStartUserGesture(); } diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 388c554c43bba..a68905bb8597d 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -38,12 +38,11 @@ class NestedScrollView extends StatefulWidget { this.physics, @required this.headerSliverBuilder, @required this.body, - }) : super(key: key) { - assert(scrollDirection != null); - assert(reverse != null); - assert(headerSliverBuilder != null); - assert(body != null); - } + }) : assert(scrollDirection != null), + assert(reverse != null), + assert(headerSliverBuilder != null), + assert(body != null), + super(key: key); // TODO(ianh): we should expose a controller so you can call animateTo, etc. @@ -782,10 +781,9 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity { this.metrics, Simulation simulation, TickerProvider vsync, - ) : super(position, simulation, vsync) { - assert(metrics.minRange != metrics.maxRange); - assert(metrics.maxRange > metrics.minRange); - } + ) : assert(metrics.minRange != metrics.maxRange), + assert(metrics.maxRange > metrics.minRange), + super(position, simulation, vsync); final _NestedScrollCoordinator coordinator; final _NestedScrollMetrics metrics; diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 9f024dffcb00d..e8465c5024a7a 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -62,11 +62,11 @@ class OverlayEntry { @required this.builder, bool opaque: false, bool maintainState: false, - }) : _opaque = opaque, _maintainState = maintainState { - assert(builder != null); - assert(opaque != null); - assert(maintainState != null); - } + }) : assert(builder != null), + assert(opaque != null), + assert(maintainState != null), + _opaque = opaque, + _maintainState = maintainState; /// This entry will include the widget built by this builder in the overlay at /// the entry's position. @@ -154,9 +154,9 @@ class OverlayEntry { } class _OverlayEntry extends StatefulWidget { - _OverlayEntry(this.entry) : super(key: entry._key) { - assert(entry != null); - } + _OverlayEntry(this.entry) + : assert(entry != null), + super(key: entry._key); final OverlayEntry entry; @@ -388,10 +388,8 @@ class _Theatre extends RenderObjectWidget { _Theatre({ this.onstage, @required this.offstage, - }) { - assert(offstage != null); - assert(!offstage.any((Widget child) => child == null)); - } + }) : assert(offstage != null), + assert(!offstage.any((Widget child) => child == null)); final Stack onstage; @@ -405,9 +403,9 @@ class _Theatre extends RenderObjectWidget { } class _TheatreElement extends RenderObjectElement { - _TheatreElement(_Theatre widget) : super(widget) { - assert(!debugChildrenHaveDuplicateKeys(widget, widget.offstage)); - } + _TheatreElement(_Theatre widget) + : assert(!debugChildrenHaveDuplicateKeys(widget, widget.offstage)), + super(widget); @override _Theatre get widget => super.widget; diff --git a/packages/flutter/lib/src/widgets/overscroll_indicator.dart b/packages/flutter/lib/src/widgets/overscroll_indicator.dart index c628f96e36aec..be12919d10881 100644 --- a/packages/flutter/lib/src/widgets/overscroll_indicator.dart +++ b/packages/flutter/lib/src/widgets/overscroll_indicator.dart @@ -225,11 +225,11 @@ class _GlowController extends ChangeNotifier { @required TickerProvider vsync, @required Color color, @required Axis axis, - }) : _color = color, + }) : assert(vsync != null), + assert(color != null), + assert(axis != null), + _color = color, _axis = axis { - assert(vsync != null); - assert(color != null); - assert(axis != null); _glowController = new AnimationController(vsync: vsync) ..addStatusListener(_changePhase); final Animation decelerator = new CurvedAnimation( diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 353a200bdb62a..355deff5e4f10 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -42,11 +42,9 @@ class PageController extends ScrollController { PageController({ this.initialPage: 0, this.viewportFraction: 1.0, - }) { - assert(initialPage != null); - assert(viewportFraction != null); - assert(viewportFraction > 0.0); - } + }) : assert(initialPage != null), + assert(viewportFraction != null), + assert(viewportFraction > 0.0); /// The page to show when first creating the [PageView]. final int initialPage; @@ -154,18 +152,17 @@ class _PagePosition extends ScrollPositionWithSingleContext { this.initialPage: 0, double viewportFraction: 1.0, ScrollPosition oldPosition, - }) : _viewportFraction = viewportFraction, + }) : assert(initialPage != null), + assert(viewportFraction != null), + assert(viewportFraction > 0.0), + _viewportFraction = viewportFraction, _pageToUseOnStartup = initialPage.toDouble(), super( - physics: physics, - context: context, - initialPixels: null, - oldPosition: oldPosition, - ) { - assert(initialPage != null); - assert(viewportFraction != null); - assert(viewportFraction > 0.0); - } + physics: physics, + context: context, + initialPixels: null, + oldPosition: oldPosition, + ); final int initialPage; double _pageToUseOnStartup; @@ -358,9 +355,9 @@ class PageView extends StatefulWidget { this.physics, this.onPageChanged, @required this.childrenDelegate, - }) : controller = controller ?? _defaultPageController, super(key: key) { - assert(childrenDelegate != null); - } + }) : assert(childrenDelegate != null), + controller = controller ?? _defaultPageController, + super(key: key); /// The axis along which the page view scrolls. /// diff --git a/packages/flutter/lib/src/widgets/pages.dart b/packages/flutter/lib/src/widgets/pages.dart index b34ede2acbb46..558451b556cb6 100644 --- a/packages/flutter/lib/src/widgets/pages.dart +++ b/packages/flutter/lib/src/widgets/pages.dart @@ -76,13 +76,12 @@ class PageRouteBuilder extends PageRoute { this.barrierDismissible: false, this.barrierColor: null, this.maintainState: true, - }) : super(settings: settings) { - assert(pageBuilder != null); - assert(transitionsBuilder != null); - assert(opaque != null); - assert(barrierDismissible != null); - assert(maintainState != null); - } + }) : assert(pageBuilder != null), + assert(transitionsBuilder != null), + assert(opaque != null), + assert(barrierDismissible != null), + assert(maintainState != null), + super(settings: settings); /// Used build the route's primary contents. /// diff --git a/packages/flutter/lib/src/widgets/scroll_activity.dart b/packages/flutter/lib/src/widgets/scroll_activity.dart index c8913fd23724a..596e6ed79039c 100644 --- a/packages/flutter/lib/src/widgets/scroll_activity.dart +++ b/packages/flutter/lib/src/widgets/scroll_activity.dart @@ -215,10 +215,10 @@ class ScrollDragController implements Drag { @required ScrollActivityDelegate delegate, @required DragStartDetails details, this.onDragCanceled, - }) : _delegate = delegate, _lastDetails = details { - assert(delegate != null); - assert(details != null); - } + }) : assert(delegate != null), + assert(details != null), + _delegate = delegate, + _lastDetails = details; /// The object that will actuate the scroll view as the user drags. ScrollActivityDelegate get delegate => _delegate; @@ -466,12 +466,12 @@ class DrivenScrollActivity extends ScrollActivity { @required Duration duration, @required Curve curve, @required TickerProvider vsync, - }) : super(delegate) { - assert(from != null); - assert(to != null); - assert(duration != null); - assert(duration > Duration.ZERO); - assert(curve != null); + }) : assert(from != null), + assert(to != null), + assert(duration != null), + assert(duration > Duration.ZERO), + assert(curve != null), + super(delegate) { _completer = new Completer(); _controller = new AnimationController.unbounded( value: from, diff --git a/packages/flutter/lib/src/widgets/scroll_controller.dart b/packages/flutter/lib/src/widgets/scroll_controller.dart index 1b711bedfaaf5..d050414652a22 100644 --- a/packages/flutter/lib/src/widgets/scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/scroll_controller.dart @@ -43,9 +43,7 @@ class ScrollController extends ChangeNotifier { ScrollController({ this.initialScrollOffset: 0.0, this.debugLabel, - }) { - assert(initialScrollOffset != null); - } + }) : assert(initialScrollOffset != null); /// The initial value to use for [offset]. /// diff --git a/packages/flutter/lib/src/widgets/scroll_notification.dart b/packages/flutter/lib/src/widgets/scroll_notification.dart index 5b57351cf0ce1..6879803413f91 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification.dart @@ -171,12 +171,11 @@ class OverscrollNotification extends ScrollNotification { this.dragDetails, @required this.overscroll, this.velocity: 0.0, - }) : super(metrics: metrics, context: context) { - assert(overscroll != null); - assert(overscroll.isFinite); - assert(overscroll != 0.0); - assert(velocity != null); - } + }) : assert(overscroll != null), + assert(overscroll.isFinite), + assert(overscroll != 0.0), + assert(velocity != null), + super(metrics: metrics, context: context); /// If the [Scrollable] overscrolled because of a drag, the details about that /// drag update. diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 4b4e580183f8f..3785621a75c65 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -66,10 +66,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { @required this.context, ScrollPosition oldPosition, this.debugLabel, - }) { - assert(physics != null); - assert(context != null); - assert(context.vsync != null); + }) : assert(physics != null), + assert(context != null), + assert(context.vsync != null) { if (oldPosition != null) absorb(oldPosition); restoreScrollOffset(); diff --git a/packages/flutter/lib/src/widgets/scroll_simulation.dart b/packages/flutter/lib/src/widgets/scroll_simulation.dart index 03d3e2c3fc1ae..a462a79ad464e 100644 --- a/packages/flutter/lib/src/widgets/scroll_simulation.dart +++ b/packages/flutter/lib/src/widgets/scroll_simulation.dart @@ -35,14 +35,13 @@ class BouncingScrollSimulation extends Simulation { @required this.trailingExtent, @required this.spring, Tolerance tolerance: Tolerance.defaultTolerance, - }) : super(tolerance: tolerance) { - assert(position != null); - assert(velocity != null); - assert(leadingExtent != null); - assert(trailingExtent != null); - assert(leadingExtent <= trailingExtent); - assert(spring != null); - + }) : assert(position != null), + assert(velocity != null), + assert(leadingExtent != null), + assert(trailingExtent != null), + assert(leadingExtent <= trailingExtent), + assert(spring != null), + super(tolerance: tolerance) { if (position < leadingExtent) { _springSimulation = _underscrollSimulation(position, velocity); _springTime = double.NEGATIVE_INFINITY; diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index 27f3eb875a245..8ff1896254c5b 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -51,17 +51,15 @@ abstract class ScrollView extends StatelessWidget { bool primary, ScrollPhysics physics, this.shrinkWrap: false, - }) : primary = primary ?? controller == null && scrollDirection == Axis.vertical, - physics = physics ?? (primary == true || (primary == null && controller == null && scrollDirection == Axis.vertical) ? const AlwaysScrollableScrollPhysics() : null), - super(key: key) { - assert(reverse != null); - assert(shrinkWrap != null); - assert(this.primary != null); - assert(controller == null || !this.primary, + }) : assert(reverse != null), + assert(shrinkWrap != null), + assert(!(controller != null && primary == true), 'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. ' 'You cannot both set primary to true and pass an explicit controller.' - ); - } + ), + primary = primary ?? controller == null && scrollDirection == Axis.vertical, + physics = physics ?? (primary == true || (primary == null && controller == null && scrollDirection == Axis.vertical) ? const AlwaysScrollableScrollPhysics() : null), + super(key: key); /// The axis along which the scroll view scrolls. /// @@ -511,18 +509,17 @@ class ListView extends BoxScrollView { EdgeInsets padding, this.itemExtent, @required this.childrenDelegate, - }) : super( - key: key, - scrollDirection: scrollDirection, - reverse: reverse, - controller: controller, - primary: primary, - physics: physics, - shrinkWrap: shrinkWrap, - padding: padding, - ) { - assert(childrenDelegate != null); - } + }) : assert(childrenDelegate != null), + super( + key: key, + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + ); /// If non-null, forces the children to have the given extent in the scroll /// direction. @@ -607,18 +604,18 @@ class GridView extends BoxScrollView { EdgeInsets padding, @required this.gridDelegate, List children: const [], - }) : childrenDelegate = new SliverChildListDelegate(children), super( - key: key, - scrollDirection: scrollDirection, - reverse: reverse, - controller: controller, - primary: primary, - physics: physics, - shrinkWrap: shrinkWrap, - padding: padding, - ) { - assert(gridDelegate != null); - } + }) : assert(gridDelegate != null), + childrenDelegate = new SliverChildListDelegate(children), + super( + key: key, + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + ); /// Creates a scrollable, 2D array of widgets that are created on demand. /// @@ -645,18 +642,18 @@ class GridView extends BoxScrollView { @required this.gridDelegate, @required IndexedWidgetBuilder itemBuilder, int itemCount, - }) : childrenDelegate = new SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), super( - key: key, - scrollDirection: scrollDirection, - reverse: reverse, - controller: controller, - primary: primary, - physics: physics, - shrinkWrap: shrinkWrap, - padding: padding, - ) { - assert(gridDelegate != null); - } + }) : assert(gridDelegate != null), + childrenDelegate = new SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), + super( + key: key, + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + ); /// Creates a scrollable, 2D array of widgets with both a custom /// [SliverGridDelegate] and a custom [SliverChildDelegate]. @@ -676,19 +673,18 @@ class GridView extends BoxScrollView { EdgeInsets padding, @required this.gridDelegate, @required this.childrenDelegate, - }) : super( - key: key, - scrollDirection: scrollDirection, - reverse: reverse, - controller: controller, - primary: primary, - physics: physics, - shrinkWrap: shrinkWrap, - padding: padding, - ) { - assert(gridDelegate != null); - assert(childrenDelegate != null); - } + }) : assert(gridDelegate != null), + assert(childrenDelegate != null), + super( + key: key, + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + ); /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in /// the cross axis. diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart index 17b4ed6527d9f..e7af006eb435c 100644 --- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart @@ -49,15 +49,13 @@ class SingleChildScrollView extends StatelessWidget { this.physics, this.controller, this.child, - }) : primary = primary ?? controller == null && scrollDirection == Axis.vertical, - super(key: key) { - assert(scrollDirection != null); - assert(this.primary != null); - assert(controller == null || !this.primary, - 'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. ' - 'You cannot both set primary to true and pass an explicit controller.' - ); - } + }) : assert(scrollDirection != null), + assert(!(controller != null && primary == true), + 'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. ' + 'You cannot both set primary to true and pass an explicit controller.' + ), + primary = primary ?? controller == null && scrollDirection == Axis.vertical, + super(key: key); /// The axis along which the scroll view scrolls. /// @@ -180,10 +178,10 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix AxisDirection axisDirection: AxisDirection.down, @required ViewportOffset offset, RenderBox child, - }) : _axisDirection = axisDirection, + }) : assert(axisDirection != null), + assert(offset != null), + _axisDirection = axisDirection, _offset = offset { - assert(axisDirection != null); - assert(offset != null); this.child = child; } diff --git a/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart b/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart index a3d09ac0e9632..bc26360d8600b 100644 --- a/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart +++ b/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart @@ -58,9 +58,8 @@ class _RenderSizeChangedWithCallback extends RenderProxyBox { _RenderSizeChangedWithCallback({ RenderBox child, @required this.onLayoutChangedCallback - }) : super(child) { - assert(onLayoutChangedCallback != null); - } + }) : assert(onLayoutChangedCallback != null), + super(child); // There's a 1:1 relationship between the _RenderSizeChangedWithCallback and // the `context` that is captured by the closure created by createRenderObject diff --git a/packages/flutter/lib/src/widgets/table.dart b/packages/flutter/lib/src/widgets/table.dart index badd984aa7700..6f302b62885e9 100644 --- a/packages/flutter/lib/src/widgets/table.dart +++ b/packages/flutter/lib/src/widgets/table.dart @@ -100,22 +100,44 @@ class Table extends RenderObjectWidget { this.border, this.defaultVerticalAlignment: TableCellVerticalAlignment.top, this.textBaseline - }) : _rowDecorations = children.any((TableRow row) => row.decoration != null) - ? children.map((TableRow row) => row.decoration).toList(growable: false) - : null, + }) : assert(children != null), + assert(defaultColumnWidth != null), + assert(defaultVerticalAlignment != null), + assert(() { + if (children.any((TableRow row) => row.children.any((Widget cell) => cell == null))) { + throw new FlutterError( + 'One of the children of one of the rows of the table was null.\n' + 'The children of a TableRow must not be null.' + ); + } + return true; + }), + assert(() { + if (children.any((TableRow row1) => row1.key != null && children.any((TableRow row2) => row1 != row2 && row1.key == row2.key))) { + throw new FlutterError( + 'Two or more TableRow children of this Table had the same key.\n' + 'All the keyed TableRow children of a Table must have different Keys.' + ); + } + return true; + }), + assert(() { + if (children.isNotEmpty) { + final int cellCount = children.first.children.length; + if (children.any((TableRow row) => row.children.length != cellCount)) { + throw new FlutterError( + 'Table contains irregular row lengths.\n' + 'Every TableRow in a Table must have the same number of children, so that every cell is filled. ' + 'Otherwise, the table will contain holes.' + ); + } + } + return true; + }), + _rowDecorations = children.any((TableRow row) => row.decoration != null) + ? children.map((TableRow row) => row.decoration).toList(growable: false) + : null, super(key: key) { - assert(children != null); - assert(defaultColumnWidth != null); - assert(defaultVerticalAlignment != null); - assert(() { - if (children.any((TableRow row) => row.children.any((Widget cell) => cell == null))) { - throw new FlutterError( - 'One of the children of one of the rows of the table was null.\n' - 'The children of a TableRow must not be null.' - ); - } - return true; - }); assert(() { final List flatChildren = children.expand((TableRow row) => row.children).toList(growable: false); if (debugChildrenHaveDuplicateKeys(this, flatChildren)) { @@ -128,28 +150,6 @@ class Table extends RenderObjectWidget { } return true; }); - assert(() { - if (children.any((TableRow row1) => row1.key != null && children.any((TableRow row2) => row1 != row2 && row1.key == row2.key))) { - throw new FlutterError( - 'Two or more TableRow children of this Table had the same key.\n' - 'All the keyed TableRow children of a Table must have different Keys.' - ); - } - return true; - }); - assert(() { - if (children.isNotEmpty) { - final int cellCount = children.first.children.length; - if (children.any((TableRow row) => row.children.length != cellCount)) { - throw new FlutterError( - 'Table contains irregular row lengths.\n' - 'Every TableRow in a Table must have the same number of children, so that every cell is filled. ' - 'Otherwise, the table will contain holes.' - ); - } - } - return true; - }); } /// The rows of the table. diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 35e18d5b756b6..d589eb5f34a9f 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -95,9 +95,9 @@ class TextSelectionOverlay implements TextSelectionDelegate { this.renderObject, this.onSelectionOverlayChanged, this.selectionControls, - }): _value = value { - assert(value != null); - assert(context != null); + }): assert(value != null), + assert(context != null), + _value = value { final OverlayState overlay = Overlay.of(context); assert(overlay != null); _handleController = new AnimationController(duration: _kFadeDuration, vsync: overlay); diff --git a/packages/flutter/lib/src/widgets/title.dart b/packages/flutter/lib/src/widgets/title.dart index 001edc92232f4..c13e8a379fbb3 100644 --- a/packages/flutter/lib/src/widgets/title.dart +++ b/packages/flutter/lib/src/widgets/title.dart @@ -14,9 +14,8 @@ class Title extends StatelessWidget { this.title, this.color, @required this.child, - }) : super(key: key) { - assert(color == null || color.alpha == 0xFF); - } + }) : assert(color == null || color.alpha == 0xFF), + super(key: key); /// A one-line description of this app for use in the window manager. final String title; diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart index d39b5aa0fd0b6..71bc9156491e5 100644 --- a/packages/flutter/lib/src/widgets/viewport.dart +++ b/packages/flutter/lib/src/widgets/viewport.dart @@ -56,10 +56,10 @@ class Viewport extends MultiChildRenderObjectWidget { @required this.offset, this.center, List slivers: const [], - }) : super(key: key, children: slivers) { - assert(offset != null); - assert(center == null || children.where((Widget child) => child.key == center).length == 1); - } + }) : assert(offset != null), + assert(slivers != null), + assert(center == null || slivers.where((Widget child) => child.key == center).length == 1), + super(key: key, children: slivers); /// The direction in which the [offset]'s [ViewportOffset.pixels] increases. /// @@ -203,9 +203,8 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { this.axisDirection: AxisDirection.down, @required this.offset, List slivers: const [], - }) : super(key: key, children: slivers) { - assert(offset != null); - } + }) : assert(offset != null), + super(key: key, children: slivers); /// The direction in which the [offset]'s [ViewportOffset.pixels] increases. /// From a367dcb02c4cd20c20f82d389d67257a5b9c4359 Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Fri, 2 Jun 2017 09:44:36 -0700 Subject: [PATCH 011/110] Add flags to pass '--enable-software-rendering' flag to engine in run command (#10449) --- .../flutter_tools/lib/src/android/android_device.dart | 2 ++ packages/flutter_tools/lib/src/commands/run.dart | 8 ++++++++ packages/flutter_tools/lib/src/device.dart | 3 +++ packages/flutter_tools/lib/src/ios/devices.dart | 3 +++ 4 files changed, 16 insertions(+) diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index fb6fb99c00563..59019ab752a88 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -397,6 +397,8 @@ class AndroidDevice extends Device { cmd.addAll(['--ez', 'trace-startup', 'true']); if (route != null) cmd.addAll(['--es', 'route', route]); + if (debuggingOptions.enableSoftwareRendering) + cmd.addAll(['--ez', 'enable-software-rendering', 'true']); if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.buildMode == BuildMode.debug) cmd.addAll(['--ez', 'enable-checked-mode', 'true']); diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index b09fbe6735259..7f84958f3a0fa 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -88,6 +88,13 @@ class RunCommand extends RunCommandBase { defaultsTo: false, negatable: false, help: 'Start in a paused mode and wait for a debugger to connect.'); + argParser.addFlag('enable-software-rendering', + defaultsTo: false, + negatable: false, + help: 'Enable rendering using the Skia software backend. This is useful\n' + 'when testing Flutter on emulators. By default, Flutter will\n' + 'attempt to either use OpenGL or Vulkan and fall back to software\n' + 'when neither is available.'); argParser.addFlag('use-test-fonts', negatable: true, defaultsTo: false, @@ -218,6 +225,7 @@ class RunCommand extends RunCommandBase { getBuildMode(), startPaused: argResults['start-paused'], useTestFonts: argResults['use-test-fonts'], + enableSoftwareRendering: argResults['enable-software-rendering'], observatoryPort: observatoryPort, diagnosticPort: diagnosticPort, ); diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 68988066a4ad8..de3f873363797 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -299,6 +299,7 @@ abstract class Device { class DebuggingOptions { DebuggingOptions.enabled(this.buildMode, { this.startPaused: false, + this.enableSoftwareRendering: false, this.useTestFonts: false, this.observatoryPort, this.diagnosticPort @@ -308,6 +309,7 @@ class DebuggingOptions { debuggingEnabled = false, useTestFonts = false, startPaused = false, + enableSoftwareRendering = false, observatoryPort = null, diagnosticPort = null; @@ -315,6 +317,7 @@ class DebuggingOptions { final BuildMode buildMode; final bool startPaused; + final bool enableSoftwareRendering; final bool useTestFonts; final int observatoryPort; final int diagnosticPort; diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index efceac65547de..0b4d0be117234 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -242,6 +242,9 @@ class IOSDevice extends Device { // the port picked and scrape that later. } + if (debuggingOptions.enableSoftwareRendering) + launchArguments.add('--enable-software-rendering'); + if (platformArgs['trace-startup'] ?? false) launchArguments.add('--trace-startup'); From a6069aee30ff079b133a626487c5f1a7bc54e1e0 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 2 Jun 2017 10:10:36 -0700 Subject: [PATCH 012/110] Roll engine to 1f2aa075717169d6058ad97b3b5c2794a97a43d7 (#10447) Picks up accessibility fixes. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index f41ba1f4193fd..ad0afc77302d4 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -75c74dc463d56e17be10315cfde409010fd8f90b +1f2aa075717169d6058ad97b3b5c2794a97a43d7 From e2f54df5ab96808060c4f91bee0274b2fc126afe Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Fri, 2 Jun 2017 10:43:54 -0700 Subject: [PATCH 013/110] Release SDK version 0.0.7 (#10456) --- VERSION | 2 +- packages/flutter/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 61958cf4d74bf..a6f01adebff16 100644 --- a/VERSION +++ b/VERSION @@ -6,4 +6,4 @@ # incompatible way, this version number might not change. Instead, the version # number for package:flutter will update to reflect that change. -0.0.7-dev +0.0.7 diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 2bc6e197ef4be..65b57b48c3d33 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter -version: 0.0.27-dev +version: 0.0.27 author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 1a0bc6e4afc2e..275f983c61f98 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_driver -version: 0.0.5-dev +version: 0.0.5 description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 337555e31d8e9..71fefb90f1af7 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_test -version: 0.0.5-dev +version: 0.0.5 dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so From 2bb5cc9f3c818f8028644e254f628675656800e8 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Fri, 2 Jun 2017 12:36:09 -0700 Subject: [PATCH 014/110] Start 0.0.8-dev (#10459) --- VERSION | 2 +- packages/flutter/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index a6f01adebff16..3b82e709899db 100644 --- a/VERSION +++ b/VERSION @@ -6,4 +6,4 @@ # incompatible way, this version number might not change. Instead, the version # number for package:flutter will update to reflect that change. -0.0.7 +0.0.8-dev diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 65b57b48c3d33..132b05ddba1d1 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter -version: 0.0.27 +version: 0.0.28-dev author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 275f983c61f98..46b48c6fa5a38 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_driver -version: 0.0.5 +version: 0.0.6-dev description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 71fefb90f1af7..55453c8932fa2 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_test -version: 0.0.5 +version: 0.0.6-dev dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so From a8fe276c557c098311a705e3205be99f7598a6bf Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 2 Jun 2017 12:36:50 -0700 Subject: [PATCH 015/110] Roll engine to 9af413ca8b44ede5dd961dca099149ead4ac8358 (#10457) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index ad0afc77302d4..06d1529d74f05 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -1f2aa075717169d6058ad97b3b5c2794a97a43d7 +9af413ca8b44ede5dd961dca099149ead4ac8358 From 15928fbdf7b6d0c63f2026ee8c7b274e347e4101 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 2 Jun 2017 15:22:46 -0700 Subject: [PATCH 016/110] have app loggers log to their parent logger (#10402) * have app loggers log to their parent logger * rename field to parent * add todo * revert flutter_tools.iml change * ping the bots --- .../lib/src/commands/daemon.dart | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 78d5a7dbb6d78..8c39078f9b596 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -759,7 +759,7 @@ class AppInstance { } dynamic _runInZone(AppDomain domain, dynamic method()) { - _logger ??= new _AppRunLogger(domain, this, logToStdout: logToStdout); + _logger ??= new _AppRunLogger(domain, this, parent: logToStdout ? logger : null); final AppContext appContext = new AppContext(); appContext.setVariable(Logger, _logger); @@ -768,20 +768,25 @@ class AppInstance { } /// A [Logger] which sends log messages to a listening daemon client. +/// +/// This class can either: +/// 1) Send stdout messages and progress events to the client IDE +/// 1) Log messages to stdout and send progress events to the client IDE +/// +/// TODO(devoncarew): To simplify this code a bit, we could choose to specialize +/// this class into two, one for each of the above use cases. class _AppRunLogger extends Logger { - _AppRunLogger(this.domain, this.app, { this.logToStdout: false }); + _AppRunLogger(this.domain, this.app, { this.parent }); AppDomain domain; final AppInstance app; - final bool logToStdout; + final Logger parent; int _nextProgressId = 0; @override void printError(String message, { StackTrace stackTrace, bool emphasis: false }) { - if (logToStdout) { - stderr.writeln(message); - if (stackTrace != null) - stderr.writeln(stackTrace.toString().trimRight()); + if (parent != null) { + parent.printError(message, stackTrace: stackTrace, emphasis: emphasis); } else { if (stackTrace != null) { _sendLogEvent({ @@ -800,18 +805,25 @@ class _AppRunLogger extends Logger { @override void printStatus( - String message, - { bool emphasis: false, bool newline: true, String ansiAlternative, int indent } - ) { - if (logToStdout) { - print(message); + String message, { + bool emphasis: false, bool newline: true, String ansiAlternative, int indent + }) { + if (parent != null) { + parent.printStatus(message, emphasis: emphasis, newline: newline, + ansiAlternative: ansiAlternative, indent: indent); } else { _sendLogEvent({ 'log': message }); } } @override - void printTrace(String message) { } + void printTrace(String message) { + if (parent != null) { + parent.printTrace(message); + } else { + _sendLogEvent({ 'log': message, 'trace': true }); + } + } Status _status; From 38891a2f72e297e0aab2cb84d35c9b09ee349d4d Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 2 Jun 2017 15:23:36 -0700 Subject: [PATCH 017/110] IntelliJ and Flutter plugin version checks (#10454) * add min version checks for IntelliJ * validate the installed versions of IntelliJ and the flutter plugin * review comments --- .../lib/src/android/android_studio.dart | 2 +- .../src/android/android_studio_validator.dart | 4 +- .../lib/src/android/android_workflow.dart | 3 +- .../flutter_tools/lib/src/base/version.dart | 2 +- packages/flutter_tools/lib/src/doctor.dart | 77 +++++++++++++------ .../test/commands/doctor_test.dart | 7 +- 6 files changed, 64 insertions(+), 31 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart index 42fbfb79e8764..7517a23a8a0d6 100644 --- a/packages/flutter_tools/lib/src/android/android_studio.dart +++ b/packages/flutter_tools/lib/src/android/android_studio.dart @@ -291,7 +291,7 @@ class AndroidStudio implements Comparable { if (result.exitCode == 0) { final List versionLines = result.stderr.split('\n'); final String javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; - _validationMessages.add('Java version: $javaVersion'); + _validationMessages.add('Java version $javaVersion'); _javaPath = javaPath; } else { _validationMessages.add('Unable to determine bundled Java version.'); diff --git a/packages/flutter_tools/lib/src/android/android_studio_validator.dart b/packages/flutter_tools/lib/src/android/android_studio_validator.dart index d141b29e832a4..5a8da162a7a72 100644 --- a/packages/flutter_tools/lib/src/android/android_studio_validator.dart +++ b/packages/flutter_tools/lib/src/android/android_studio_validator.dart @@ -38,7 +38,9 @@ class AndroidStudioValidator extends DoctorValidator { Future validate() async { final List messages = []; ValidationType type = ValidationType.missing; - final String studioVersionText = 'version ${_studio.version}'; + final String studioVersionText = _studio.version == Version.unknown + ? null + : 'version ${_studio.version}'; messages .add(new ValidationMessage('Android Studio at ${_studio.directory}')); if (_studio.isValid) { diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart index 01ca9f15a4b3a..aedfca7cd06ca 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart @@ -83,12 +83,11 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { messages.add(new ValidationMessage.error('Could not determine java version')); return false; } - messages.add(new ValidationMessage('Java version: $javaVersion')); + messages.add(new ValidationMessage('Java version $javaVersion')); // TODO(johnmccutchan): Validate version. return true; } - @override Future validate() async { final List messages = []; diff --git a/packages/flutter_tools/lib/src/base/version.dart b/packages/flutter_tools/lib/src/base/version.dart index 09008e9f3fd8c..02aa239fa91dc 100644 --- a/packages/flutter_tools/lib/src/base/version.dart +++ b/packages/flutter_tools/lib/src/base/version.dart @@ -45,7 +45,7 @@ class Version implements Comparable { /// Creates a new [Version] by parsing [text]. factory Version.parse(String text) { - final Match match = versionPattern.firstMatch(text); + final Match match = versionPattern.firstMatch(text ?? ''); if (match == null) { return null; } diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 2805ee67aca91..f3ec99aabe661 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -16,6 +16,7 @@ import 'base/file_system.dart'; import 'base/os.dart'; import 'base/platform.dart'; import 'base/process_manager.dart'; +import 'base/version.dart'; import 'cache.dart'; import 'device.dart'; import 'globals.dart'; @@ -261,6 +262,10 @@ abstract class IntelliJValidator extends DoctorValidator { 'WebStorm': 'WebStorm', }; + static final Version kMinIdeaVersion = new Version(2017, 1, 0); + static final Version kMinWebStormVersion = new Version(2017, 1, 0); + static final Version kMinFlutterPluginVersion = new Version(14, 0, 0); + static Iterable get installedValidators { if (platform.isLinux || platform.isWindows) return IntelliJValidatorOnLinuxAndWindows.installed; @@ -273,46 +278,72 @@ abstract class IntelliJValidator extends DoctorValidator { Future validate() async { final List messages = []; - int installCount = 0; + _validatePackage(messages, 'flutter-intellij.jar', 'Flutter', + minVersion: kMinFlutterPluginVersion); - if (isWebStorm) { - // Dart is bundled with WebStorm. - installCount++; - } else { - if (_validateHasPackage(messages, 'Dart', 'Dart')) - installCount++; + // Dart is bundled with WebStorm. + if (!isWebStorm) { + _validatePackage(messages, 'Dart', 'Dart'); } - if (_validateHasPackage(messages, 'flutter-intellij.jar', 'Flutter')) - installCount++; - - if (installCount < 2) { + if (_hasIssues(messages)) { messages.add(new ValidationMessage( - 'For information about managing plugins, see\n' - 'https://www.jetbrains.com/help/idea/managing-plugins.html' + 'For information about managing plugins, see\n' + 'https://www.jetbrains.com/help/idea/managing-plugins.html' )); } + _validateIntelliJVersion(messages, isWebStorm ? kMinWebStormVersion : kMinIdeaVersion); + return new ValidationResult( - installCount == 2 ? ValidationType.installed : ValidationType.partial, - messages, - statusInfo: 'version $version' + _hasIssues(messages) ? ValidationType.partial : ValidationType.installed, + messages, + statusInfo: 'version $version' ); } + bool _hasIssues(List messages) { + return messages.any((ValidationMessage message) => message.isError); + } + bool get isWebStorm => title == 'WebStorm'; - bool _validateHasPackage(List messages, String packageName, String title) { + void _validateIntelliJVersion(List messages, Version minVersion) { + // Ignore unknown versions. + if (minVersion == Version.unknown) + return; + + final Version installedVersion = new Version.parse(version); + if (installedVersion == null) + return; + + if (installedVersion < minVersion) { + messages.add(new ValidationMessage.error( + 'This install is older than the minimum recommended version of $minVersion.' + )); + } + } + + void _validatePackage(List messages, String packageName, String title, { + Version minVersion + }) { if (!hasPackage(packageName)) { - messages.add(new ValidationMessage( + messages.add(new ValidationMessage.error( '$title plugin not installed; this adds $title specific functionality.' )); - return false; + return; + } + final String versionText = _readPackageVersion(packageName); + final Version version = new Version.parse(versionText); + if (version != null && minVersion != null && version < minVersion) { + messages.add(new ValidationMessage.error( + '$title plugin version $versionText - the recommended minimum version is $minVersion' + )); + } else { + messages.add(new ValidationMessage( + '$title plugin ${version != null ? "version $version" : "installed"}' + )); } - final String version = _readPackageVersion(packageName); - messages.add(new ValidationMessage('$title plugin ' - '${version != null ? "version $version" : "installed"}')); - return true; } String _readPackageVersion(String packageName) { diff --git a/packages/flutter_tools/test/commands/doctor_test.dart b/packages/flutter_tools/test/commands/doctor_test.dart index 369aaeae19cd1..8140c65e15fee 100644 --- a/packages/flutter_tools/test/commands/doctor_test.dart +++ b/packages/flutter_tools/test/commands/doctor_test.dart @@ -12,9 +12,9 @@ void main() { group('doctor', () { testUsingContext('intellij validator', () async { final ValidationResult result = await new IntelliJValidatorTestTarget('Test').validate(); - expect(result.type, ValidationType.installed); + expect(result.type, ValidationType.partial); expect(result.statusInfo, 'version test.test.test'); - expect(result.messages, hasLength(2)); + expect(result.messages, hasLength(3)); ValidationMessage message = result.messages .firstWhere((ValidationMessage m) => m.message.startsWith('Dart ')); @@ -22,7 +22,8 @@ void main() { message = result.messages .firstWhere((ValidationMessage m) => m.message.startsWith('Flutter ')); - expect(message.message, 'Flutter plugin version 0.1.3'); + expect(message.message, contains('Flutter plugin version 0.1.3')); + expect(message.message, contains('recommended minimum version')); }); }); } From 9eae8b83ff6722c5fe028d0a1e28b9f813df6980 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Fri, 2 Jun 2017 15:54:22 -0700 Subject: [PATCH 018/110] Sprinkle some mixin magic incantations (#10442) This prevents some of our mixins from being subclassed. Also, move mixins to using 'extends' instead of 'implements' for future compatibility with Dart changes. Also, rename a class that had Mixin in the name but was not a mixin. --- .../flutter/lib/src/animation/animations.dart | 4 +++ .../lib/src/animation/listener_helpers.dart | 28 ++++++++++++++++--- .../foundation/tree_diagnostics_mixin.dart | 4 +++ .../flutter/lib/src/gestures/binding.dart | 5 +++- .../flutter/lib/src/gestures/hit_test.dart | 12 ++++++++ .../flutter/lib/src/rendering/binding.dart | 6 +++- packages/flutter/lib/src/rendering/block.dart | 2 +- packages/flutter/lib/src/rendering/box.dart | 14 +++++++--- .../lib/src/rendering/custom_layout.dart | 2 +- packages/flutter/lib/src/rendering/flex.dart | 4 +-- packages/flutter/lib/src/rendering/flow.dart | 2 +- .../flutter/lib/src/rendering/object.dart | 17 +++++++++-- .../flutter/lib/src/rendering/proxy_box.dart | 6 +++- packages/flutter/lib/src/rendering/stack.dart | 2 +- .../flutter/lib/src/rendering/viewport.dart | 6 +++- packages/flutter/lib/src/rendering/wrap.dart | 2 +- .../flutter/lib/src/scheduler/binding.dart | 3 ++ .../flutter/lib/src/services/binding.dart | 4 +++ packages/flutter/lib/src/widgets/binding.dart | 11 +++++++- .../lib/src/widgets/scroll_notification.dart | 4 +++ .../src/widgets/sliver_persistent_header.dart | 6 +++- .../lib/src/widgets/ticker_provider.dart | 10 +++++-- 22 files changed, 128 insertions(+), 26 deletions(-) diff --git a/packages/flutter/lib/src/animation/animations.dart b/packages/flutter/lib/src/animation/animations.dart index f0fdcd90dd63f..9401108c9208a 100644 --- a/packages/flutter/lib/src/animation/animations.dart +++ b/packages/flutter/lib/src/animation/animations.dart @@ -115,6 +115,10 @@ class AlwaysStoppedAnimation extends Animation { /// given [parent] Animation. To implement an [Animation] that proxies to a /// parent, this class plus implementing "T get value" is all that is necessary. abstract class AnimationWithParentMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory AnimationWithParentMixin._() => null; + /// The animation whose value this animation will proxy. /// /// This animation must remain the same for the lifetime of this object. If diff --git a/packages/flutter/lib/src/animation/listener_helpers.dart b/packages/flutter/lib/src/animation/listener_helpers.dart index 570c83a584b9d..d75d99ad0007b 100644 --- a/packages/flutter/lib/src/animation/listener_helpers.dart +++ b/packages/flutter/lib/src/animation/listener_helpers.dart @@ -9,12 +9,20 @@ import 'package:flutter/foundation.dart'; import 'animation.dart'; abstract class _ListenerMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory _ListenerMixin._() => null; + void didRegisterListener(); void didUnregisterListener(); } /// A mixin that helps listen to another object only when this object has registered listeners. -abstract class AnimationLazyListenerMixin implements _ListenerMixin { +abstract class AnimationLazyListenerMixin extends _ListenerMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory AnimationLazyListenerMixin._() => null; + int _listenerCounter = 0; @override @@ -47,7 +55,11 @@ abstract class AnimationLazyListenerMixin implements _ListenerMixin { /// A mixin that replaces the didRegisterListener/didUnregisterListener contract /// with a dispose contract. -abstract class AnimationEagerListenerMixin implements _ListenerMixin { +abstract class AnimationEagerListenerMixin extends _ListenerMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory AnimationEagerListenerMixin._() => null; + @override void didRegisterListener() { } @@ -60,9 +72,13 @@ abstract class AnimationEagerListenerMixin implements _ListenerMixin { void dispose() { } } -/// A mixin that implements the addListener/removeListener protocol and notifies -/// all the registered listeners when notifyListeners is called. +/// A mixin that implements the [addListener]/[removeListener] protocol and notifies +/// all the registered listeners when [notifyListeners] is called. abstract class AnimationLocalListenersMixin extends _ListenerMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory AnimationLocalListenersMixin._() => null; + final ObserverList _listeners = new ObserverList(); /// Calls the listener every time the value of the animation changes. @@ -111,6 +127,10 @@ abstract class AnimationLocalListenersMixin extends _ListenerMixin { /// and notifies all the registered listeners when notifyStatusListeners is /// called. abstract class AnimationLocalStatusListenersMixin extends _ListenerMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory AnimationLocalStatusListenersMixin._() => null; + final ObserverList _statusListeners = new ObserverList(); /// Calls listener every time the status of the animation changes. diff --git a/packages/flutter/lib/src/foundation/tree_diagnostics_mixin.dart b/packages/flutter/lib/src/foundation/tree_diagnostics_mixin.dart index d1054277c3c37..3c634144d308e 100644 --- a/packages/flutter/lib/src/foundation/tree_diagnostics_mixin.dart +++ b/packages/flutter/lib/src/foundation/tree_diagnostics_mixin.dart @@ -6,6 +6,10 @@ import 'package:meta/meta.dart'; /// A mixin that helps dump string representations of trees. abstract class TreeDiagnosticsMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory TreeDiagnosticsMixin._() => null; + @override String toString() => '$runtimeType#$hashCode'; diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart index 849b999487eed..09945d7d3adc8 100644 --- a/packages/flutter/lib/src/gestures/binding.dart +++ b/packages/flutter/lib/src/gestures/binding.dart @@ -15,7 +15,10 @@ import 'hit_test.dart'; import 'pointer_router.dart'; /// A binding for the gesture subsystem. -abstract class GestureBinding extends BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget { +abstract class GestureBinding extends BindingBase with HitTestable, HitTestDispatcher, HitTestTarget { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory GestureBinding._() => null; @override void initInstances() { diff --git a/packages/flutter/lib/src/gestures/hit_test.dart b/packages/flutter/lib/src/gestures/hit_test.dart index 4cbd64bdaaa5d..1c380bef1ffb6 100644 --- a/packages/flutter/lib/src/gestures/hit_test.dart +++ b/packages/flutter/lib/src/gestures/hit_test.dart @@ -6,6 +6,10 @@ import 'events.dart'; /// An object that can hit-test pointers. abstract class HitTestable { // ignore: one_member_abstracts + // This class is intended to be used as an interface with the implements + // keyword, and should not be extended directly. + factory HitTestable._() => null; + /// Check whether the given position hits this object. /// /// If this given position hits this object, consider adding a [HitTestEntry] @@ -15,12 +19,20 @@ abstract class HitTestable { // ignore: one_member_abstracts /// An object that can dispatch events. abstract class HitTestDispatcher { // ignore: one_member_abstracts + // This class is intended to be used as an interface with the implements + // keyword, and should not be extended directly. + factory HitTestDispatcher._() => null; + /// Override this method to dispatch events. void dispatchEvent(PointerEvent event, HitTestResult result); } /// An object that can handle events. abstract class HitTestTarget { // ignore: one_member_abstracts + // This class is intended to be used as an interface with the implements + // keyword, and should not be extended directly. + factory HitTestTarget._() => null; + /// Override this method to receive events. void handleEvent(PointerEvent event, HitTestEntry entry); } diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index aad9dd228d951..43018d414faff 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -20,7 +20,11 @@ import 'view.dart'; export 'package:flutter/gestures.dart' show HitTestResult; /// The glue between the render tree and the Flutter engine. -abstract class RendererBinding extends BindingBase implements SchedulerBinding, ServicesBinding, HitTestable { +abstract class RendererBinding extends BindingBase with SchedulerBinding, ServicesBinding, HitTestable { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory RendererBinding._() => null; + @override void initInstances() { super.initInstances(); diff --git a/packages/flutter/lib/src/rendering/block.dart b/packages/flutter/lib/src/rendering/block.dart index 6521f0d3a213c..98c4bda2e4de0 100644 --- a/packages/flutter/lib/src/rendering/block.dart +++ b/packages/flutter/lib/src/rendering/block.dart @@ -8,7 +8,7 @@ import 'box.dart'; import 'object.dart'; /// Parent data for use with [RenderListBody]. -class ListBodyParentData extends ContainerBoxParentDataMixin { } +class ListBodyParentData extends ContainerBoxParentData { } typedef double _ChildSizingFunction(RenderBox child); diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 7806abdecbf5c..f4b869ca8f738 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -530,7 +530,10 @@ class BoxParentData extends ParentData { /// Abstract ParentData subclass for RenderBox subclasses that want the /// ContainerRenderObjectMixin. -abstract class ContainerBoxParentDataMixin extends BoxParentData with ContainerParentDataMixin { } +/// +/// This is a convenience class that mixes in the relevant classes with +/// the relevant type arguments. +abstract class ContainerBoxParentData extends BoxParentData with ContainerParentDataMixin { } enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight } @@ -714,13 +717,13 @@ class _IntrinsicDimensionsCacheEntry { /// [parentData]. The class used for [parentData] must itself have the /// [ContainerParentDataMixin] class mixed into it; this is where /// [ContainerRenderObjectMixin] stores the linked list. A [ParentData] class -/// can extend [ContainerBoxParentDataMixin]; this is essentially +/// can extend [ContainerBoxParentData]; this is essentially /// [BoxParentData] mixed with [ContainerParentDataMixin]. For example, if a /// `RenderFoo` class wanted to have a linked list of [RenderBox] children, one /// might create a `FooParentData` class as follows: /// /// ```dart -/// class FooParentData extends ContainerBoxParentDataMixin { +/// class FooParentData extends ContainerBoxParentData { /// // (any fields you might need for these children) /// } /// ``` @@ -2024,7 +2027,10 @@ abstract class RenderBox extends RenderObject { /// By convention, this class doesn't override any members of the superclass. /// Instead, it provides helpful functions that subclasses can call as /// appropriate. -abstract class RenderBoxContainerDefaultsMixin> implements ContainerRenderObjectMixin { +abstract class RenderBoxContainerDefaultsMixin> implements ContainerRenderObjectMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory RenderBoxContainerDefaultsMixin._() => null; /// Returns the baseline of the first child with a baseline. /// diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart index 7b90806259189..bcb2dd8cef526 100644 --- a/packages/flutter/lib/src/rendering/custom_layout.dart +++ b/packages/flutter/lib/src/rendering/custom_layout.dart @@ -10,7 +10,7 @@ import 'object.dart'; // For SingleChildLayoutDelegate and RenderCustomSingleChildLayoutBox, see shifted_box.dart /// [ParentData] used by [RenderCustomMultiChildLayoutBox]. -class MultiChildLayoutParentData extends ContainerBoxParentDataMixin { +class MultiChildLayoutParentData extends ContainerBoxParentData { /// An object representing the identity of this child. Object id; diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart index db5e15658208d..78c1aa6dc065e 100644 --- a/packages/flutter/lib/src/rendering/flex.dart +++ b/packages/flutter/lib/src/rendering/flex.dart @@ -29,7 +29,7 @@ enum FlexFit { } /// Parent data for use with [RenderFlex]. -class FlexParentData extends ContainerBoxParentDataMixin { +class FlexParentData extends ContainerBoxParentData { /// The flex factor to use for this child /// /// If null or zero, the child is inflexible and determines its own size. If @@ -48,7 +48,7 @@ class FlexParentData extends ContainerBoxParentDataMixin { FlexFit fit; @override - String toString() => '${super.toString()}; flex=$flex'; + String toString() => '${super.toString()}; flex=$flex; fit=$fit'; } /// How much space should be occupied in the main axis. diff --git a/packages/flutter/lib/src/rendering/flow.dart b/packages/flutter/lib/src/rendering/flow.dart index dfd38b1005f17..f2b645fe71d95 100644 --- a/packages/flutter/lib/src/rendering/flow.dart +++ b/packages/flutter/lib/src/rendering/flow.dart @@ -143,7 +143,7 @@ int _getAlphaFromOpacity(double opacity) => (opacity * 255).round(); /// transformation matrix, which is private to the [RenderFlow]. To set the /// matrix, use the [FlowPaintingContext.paintChild] function from an override /// of the [FlowDelegate.paintChildren] function. -class FlowParentData extends ContainerBoxParentDataMixin { +class FlowParentData extends ContainerBoxParentData { Matrix4 _transform; } diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 3880119661459..ccf30cd65e9ff 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2739,7 +2739,11 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// Generic mixin for render objects with one child. /// /// Provides a child model for a render object subclass that has a unique child. -abstract class RenderObjectWithChildMixin implements RenderObject { +abstract class RenderObjectWithChildMixin extends RenderObject { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory RenderObjectWithChildMixin._() => null; + /// Checks whether the given render object has the correct [runtimeType] to be /// a child of this render object. /// @@ -2816,7 +2820,11 @@ abstract class RenderObjectWithChildMixin implem } /// Parent data to support a doubly-linked list of children. -abstract class ContainerParentDataMixin implements ParentData { +abstract class ContainerParentDataMixin extends ParentData { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory ContainerParentDataMixin._() => null; + /// The previous sibling in the parent's child list. ChildType previousSibling; /// The next sibling in the parent's child list. @@ -2847,7 +2855,10 @@ abstract class ContainerParentDataMixin implemen /// /// Provides a child model for a render object subclass that has a doubly-linked /// list of children. -abstract class ContainerRenderObjectMixin> implements RenderObject { +abstract class ContainerRenderObjectMixin> extends RenderObject { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory ContainerRenderObjectMixin._() => null; bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { ParentDataType childParentData = child.parentData; diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 3cae3cd91f5a1..b4c4446d596f5 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -50,7 +50,11 @@ class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin { +abstract class RenderProxyBoxMixin extends RenderBox with RenderObjectWithChildMixin { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory RenderProxyBoxMixin._() => null; + @override void setupParentData(RenderObject child) { // We don't actually use the offset argument in BoxParentData, so let's diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart index eef07a5a41064..2fb0b8284ba70 100644 --- a/packages/flutter/lib/src/rendering/stack.dart +++ b/packages/flutter/lib/src/rendering/stack.dart @@ -138,7 +138,7 @@ class RelativeRect { } /// Parent data for use with [RenderStack]. -class StackParentData extends ContainerBoxParentDataMixin { +class StackParentData extends ContainerBoxParentData { /// The distance by which the child's top edge is inset from the top of the stack. double top; diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 662a93b50258f..37461eacc743e 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -20,7 +20,11 @@ import 'viewport_offset.dart'; /// content, which can be controlled by a [ViewportOffset]. This interface lets /// the framework recognize such render objects and interact with them without /// having specific knowledge of all the various types of viewports. -abstract class RenderAbstractViewport implements RenderObject { +abstract class RenderAbstractViewport extends RenderObject { + // This class is intended to be used as an interface with the implements + // keyword, and should not be extended directly. + factory RenderAbstractViewport._() => null; + /// Returns the [RenderAbstractViewport] that most tightly encloses the given /// render object. /// diff --git a/packages/flutter/lib/src/rendering/wrap.dart b/packages/flutter/lib/src/rendering/wrap.dart index c365bc4289628..7b1e8c51d919e 100644 --- a/packages/flutter/lib/src/rendering/wrap.dart +++ b/packages/flutter/lib/src/rendering/wrap.dart @@ -59,7 +59,7 @@ class _RunMetrics { } /// Parent data for use with [RenderWrap]. -class WrapParentData extends ContainerBoxParentDataMixin { +class WrapParentData extends ContainerBoxParentData { int _runIndex = 0; } diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index fb8ae90a972b0..e4e8d2ddf1848 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -159,6 +159,9 @@ enum SchedulerPhase { /// priority and are executed in priority order according to a /// [schedulingStrategy]. abstract class SchedulerBinding extends BindingBase { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory SchedulerBinding._() => null; @override void initInstances() { diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index 13eaa6703c170..03df44384797d 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -17,6 +17,10 @@ import 'platform_messages.dart'; /// the licenses found in the `LICENSE` file stored at the root of the asset /// bundle. abstract class ServicesBinding extends BindingBase { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory ServicesBinding._() => null; + @override void initInstances() { super.initInstances(); diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 6af1ed7175a12..f6649822e1845 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -22,6 +22,11 @@ export 'dart:ui' show AppLifecycleState, Locale; /// Interface for classes that register with the Widgets layer binding. /// /// See [WidgetsBinding.addObserver] and [WidgetsBinding.removeObserver]. +/// +/// This class can be extended directly, to get default behaviors for all of the +/// handlers, or can used with the `implements` keyword, in which case all the +/// handlers must be implemented (and the analyzer will list those that have +/// been omitted). abstract class WidgetsBindingObserver { /// Called when the system tells the app to pop the current route. /// For example, on Android, this is called when the user presses @@ -63,7 +68,11 @@ abstract class WidgetsBindingObserver { } /// The glue between the widgets layer and the Flutter engine. -abstract class WidgetsBinding extends BindingBase implements GestureBinding, RendererBinding { +abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererBinding { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory WidgetsBinding._() => null; + @override void initInstances() { super.initInstances(); diff --git a/packages/flutter/lib/src/widgets/scroll_notification.dart b/packages/flutter/lib/src/widgets/scroll_notification.dart index 6879803413f91..aa216c9f452d3 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification.dart @@ -15,6 +15,10 @@ import 'scroll_metrics.dart'; /// /// This is used by [ScrollNotification] and [OverscrollIndicatorNotification]. abstract class ViewportNotificationMixin extends Notification { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory ViewportNotificationMixin._() => null; + /// The number of viewports that this notification has bubbled through. /// /// Typically listeners only respond to notifications with a [depth] of zero. diff --git a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart index 8e90bedffca28..e4e19a35bef0c 100644 --- a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart @@ -166,7 +166,11 @@ abstract class _SliverPersistentHeaderRenderObjectWidget extends RenderObjectWid } } -abstract class _RenderSliverPersistentHeaderForWidgetsMixin implements RenderSliverPersistentHeader { +abstract class _RenderSliverPersistentHeaderForWidgetsMixin extends RenderSliverPersistentHeader { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory _RenderSliverPersistentHeaderForWidgetsMixin._() => null; + _SliverPersistentHeaderElement _element; @override diff --git a/packages/flutter/lib/src/widgets/ticker_provider.dart b/packages/flutter/lib/src/widgets/ticker_provider.dart index 4850c37fe12be..190ce3d404a92 100644 --- a/packages/flutter/lib/src/widgets/ticker_provider.dart +++ b/packages/flutter/lib/src/widgets/ticker_provider.dart @@ -73,7 +73,10 @@ class TickerMode extends InheritedWidget { /// This mixin only supports vending a single ticker. If you might have multiple /// [AnimationController] objects over the lifetime of the [State], use a full /// [TickerProviderStateMixin] instead. -abstract class SingleTickerProviderStateMixin implements State, TickerProvider { // ignore: TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, https://github.com/dart-lang/sdk/issues/25232 +abstract class SingleTickerProviderStateMixin extends State implements TickerProvider { // ignore: TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, https://github.com/dart-lang/sdk/issues/25232 + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory SingleTickerProviderStateMixin._() => null; Ticker _ticker; @@ -150,7 +153,10 @@ abstract class SingleTickerProviderStateMixin implements State, TickerP /// If you only have a single [Ticker] (for example only a single /// [AnimationController]) for the lifetime of your [State], then using a /// [SingleTickerProviderStateMixin] is more efficient. This is the common case. -abstract class TickerProviderStateMixin implements State, TickerProvider { // ignore: TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, https://github.com/dart-lang/sdk/issues/25232 +abstract class TickerProviderStateMixin extends State implements TickerProvider { // ignore: TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, https://github.com/dart-lang/sdk/issues/25232 + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory TickerProviderStateMixin._() => null; Set _tickers; From cb959724cbe98461252505159aa467aaab5395b7 Mon Sep 17 00:00:00 2001 From: xster Date: Fri, 2 Jun 2017 16:01:20 -0700 Subject: [PATCH 019/110] Small setup doc for the app store option (#10418) * Small setup doc for the app store option * review notes --- packages/flutter_tools/lib/src/ios/ios_workflow.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart index 4fe9d24d3fade..696048b221c86 100644 --- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart +++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart @@ -132,7 +132,9 @@ class IOSWorkflow extends DoctorValidator implements Workflow { messages.add(new ValidationMessage.error( 'Xcode installation is incomplete; a full installation is necessary for iOS development.\n' 'Download at: https://developer.apple.com/xcode/download/\n' - 'Once installed, run \'sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer\'.' + 'Or install Xcode via the App Store.\n' + 'Once installed, run:\n' + ' sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer' )); } } @@ -153,10 +155,10 @@ class IOSWorkflow extends DoctorValidator implements Workflow { brewStatus = ValidationType.partial; messages.add(new ValidationMessage.error( 'libimobiledevice and ideviceinstaller are not installed or require updating. To update, run:\n' - ' brew update\n' - ' brew uninstall --ignore-dependencies libimobiledevice\n' - ' brew install --HEAD libimobiledevice\n' - ' brew install ideviceinstaller' + ' brew update\n' + ' brew uninstall --ignore-dependencies libimobiledevice\n' + ' brew install --HEAD libimobiledevice\n' + ' brew install ideviceinstaller' )); } else if (!await hasIDeviceInstaller) { brewStatus = ValidationType.partial; From 07275405f8347d9fb9d1e8e7cf64b220e60c26a2 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 2 Jun 2017 16:09:51 -0700 Subject: [PATCH 020/110] Apply the paint offset to the bounds rectangle for shader masks (#10458) Fixes https://github.com/flutter/flutter/issues/10424 --- .../flutter/lib/src/rendering/object.dart | 2 +- .../flutter/lib/src/rendering/proxy_box.dart | 4 ++-- .../test/widgets/shader_mask_test.dart | 24 +++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index ccf30cd65e9ff..4e4bae3fad1da 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -416,7 +416,7 @@ class PaintingContext { _stopRecordingIfNeeded(); final ShaderMaskLayer shaderLayer = new ShaderMaskLayer( shader: shader, - maskRect: maskRect, + maskRect: maskRect.shift(offset), blendMode: blendMode, ); _appendLayer(shaderLayer); diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index b4c4446d596f5..1dfd41691b887 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -834,8 +834,8 @@ class RenderShaderMask extends RenderProxyBox { void paint(PaintingContext context, Offset offset) { if (child != null) { assert(needsCompositing); - final Rect rect = Offset.zero & size; - context.pushShaderMask(offset, _shaderCallback(rect), rect, _blendMode, super.paint); + final Shader shader = _shaderCallback(offset & size); + context.pushShaderMask(offset, shader, Offset.zero & size, _blendMode, super.paint); } } } diff --git a/packages/flutter/test/widgets/shader_mask_test.dart b/packages/flutter/test/widgets/shader_mask_test.dart index e79ec9c2b018b..7987139e31e99 100644 --- a/packages/flutter/test/widgets/shader_mask_test.dart +++ b/packages/flutter/test/widgets/shader_mask_test.dart @@ -22,4 +22,28 @@ void main() { final Widget child = new Container(width: 100.0, height: 100.0); await tester.pumpWidget(new ShaderMask(child: child, shaderCallback: createShader)); }); + + testWidgets('Bounds rect includes offset', (WidgetTester tester) async { + Rect shaderBounds; + Shader recordShaderBounds(Rect bounds) { + shaderBounds = bounds; + return createShader(bounds); + } + + final Widget widget = new Align( + alignment: FractionalOffset.center, + child: new SizedBox( + width: 400.0, + height: 400.0, + child: new ShaderMask( + shaderCallback: recordShaderBounds, + child: new Container(width: 100.0, height: 100.0) + ), + ), + ); + await tester.pumpWidget(widget); + + // The shader bounds rectangle should reflect the position of the centered SizedBox. + expect(shaderBounds, equals(new Rect.fromLTWH(200.0, 100.0, 400.0, 400.0))); + }); } From 7c68cf50bc3c5c1c3ef80487ba14b43fc327732b Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 2 Jun 2017 16:15:21 -0700 Subject: [PATCH 021/110] Roll the engine to bd09286e4aec422a1f77eac9de84274f22484846 (#10467) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 06d1529d74f05..7c18b5e650fbf 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -9af413ca8b44ede5dd961dca099149ead4ac8358 +bd09286e4aec422a1f77eac9de84274f22484846 From 2cd2a74e5d65d7217da6ec656ac2746ccd635c2f Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 2 Jun 2017 16:36:15 -0700 Subject: [PATCH 022/110] Add emailAddress, url TextInputTypes (#10471) Adds support for requesting keyboards optimised for email address and URL entry. --- packages/flutter/lib/src/services/text_input.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index ce1cbf8a988b0..8aa7289c45d0f 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -26,6 +26,12 @@ enum TextInputType { /// Optimize for date and time information. datetime, + + /// Optimize for email addresses. + emailAddress, + + /// Optimize for URLs. + url, } /// An action the user has requested the text input control to perform. From 417df36b4534c2ce997e947beb3895aa758a3d18 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 2 Jun 2017 17:48:07 -0700 Subject: [PATCH 023/110] Add more detail to TextInputType documentation (#10474) --- .../flutter/lib/src/services/text_input.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 8aa7289c45d0f..e1c4ca87554f4 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -14,23 +14,41 @@ import 'text_editing.dart'; export 'dart:ui' show TextAffinity; /// The type of information for which to optimize the text input control. +/// +/// On Android, behavior may vary across device and keyboard provider. enum TextInputType { /// Optimize for textual information. + /// + /// Requests the default platform keyboard. text, /// Optimize for numerical information. + /// + /// Requests a keyboard with ready access to the decimal point and number + /// keys. number, /// Optimize for telephone numbers. + /// + /// Requests a keyboard with ready access to the number keys, "*", and "#". phone, /// Optimize for date and time information. + /// + /// On iOS, requests the default keyboard. + /// + /// On Android, requests a keyboard with ready access to the number keys, + /// ":", and "-". datetime, /// Optimize for email addresses. + /// + /// Requests a keyboard with ready access to the "@" and "." keys. emailAddress, /// Optimize for URLs. + /// + /// Requests a keyboard with ready access to the "/" and "." keys. url, } From 104725f384755831a7e605e8795b3935bc34a67f Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 2 Jun 2017 18:14:27 -0700 Subject: [PATCH 024/110] Adds a widgets that blocks all semantics of widgets below it in paint order within the same container (#10425) --- packages/flutter/lib/src/material/drawer.dart | 10 +- .../flutter/lib/src/rendering/object.dart | 98 +++++++++--- .../flutter/lib/src/rendering/proxy_box.dart | 12 ++ packages/flutter/lib/src/widgets/basic.dart | 25 +++ .../lib/src/widgets/modal_barrier.dart | 32 ++-- .../flutter/test/material/dialog_test.dart | 37 +++++ .../flutter/test/material/scaffold_test.dart | 38 +++++ .../test/widgets/semantics_9_test.dart | 149 ++++++++++++++++++ .../test/widgets/semantics_tester.dart | 55 +++++-- 9 files changed, 407 insertions(+), 49 deletions(-) create mode 100644 packages/flutter/test/widgets/semantics_9_test.dart diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index dde0a504e1fe1..b9f32ec699f2f 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -270,10 +270,12 @@ class DrawerControllerState extends State with SingleTickerPro child: new RepaintBoundary( child: new Stack( children: [ - new GestureDetector( - onTap: close, - child: new Container( - color: _color.evaluate(_controller) + new BlockSemantics( + child: new GestureDetector( + onTap: close, + child: new Container( + color: _color.evaluate(_controller) + ), ), ), new Align( diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 4e4bae3fad1da..1e2a4771c3672 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -662,7 +662,8 @@ abstract class _SemanticsFragment { _SemanticsFragment({ @required RenderObject renderObjectOwner, this.annotator, - List<_SemanticsFragment> children + List<_SemanticsFragment> children, + this.dropSemanticsOfPreviousSiblings, }) { assert(renderObjectOwner != null); _ancestorChain = [renderObjectOwner]; @@ -678,6 +679,9 @@ abstract class _SemanticsFragment { } final SemanticsAnnotator annotator; + bool dropSemanticsOfPreviousSiblings; + + bool get producesSemanticNodes => true; List _ancestorChain; void addAncestor(RenderObject ancestor) { @@ -695,6 +699,20 @@ abstract class _SemanticsFragment { String toString() => '$runtimeType#$hashCode'; } +/// A SemanticsFragment that doesn't produce any [SemanticsNode]s when compiled. +class _EmptySemanticsFragment extends _SemanticsFragment { + _EmptySemanticsFragment({ + @required RenderObject renderObjectOwner, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); + + @override + Iterable compile({ _SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics }) sync* { } + + @override + bool get producesSemanticNodes => false; +} + /// Represents a [RenderObject] which is in no way dirty. /// /// This class has no children and no annotators, and when compiled, it returns @@ -702,8 +720,9 @@ abstract class _SemanticsFragment { /// the matrix, since that comes from the (dirty) ancestors.) class _CleanSemanticsFragment extends _SemanticsFragment { _CleanSemanticsFragment({ - @required RenderObject renderObjectOwner - }) : super(renderObjectOwner: renderObjectOwner) { + @required RenderObject renderObjectOwner, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings) { assert(renderObjectOwner != null); assert(renderObjectOwner._semantics != null); } @@ -728,8 +747,9 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { _InterestingSemanticsFragment({ RenderObject renderObjectOwner, SemanticsAnnotator annotator, - Iterable<_SemanticsFragment> children - }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children); + Iterable<_SemanticsFragment> children, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); bool get haveConcreteNode => true; @@ -765,8 +785,9 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { _RootSemanticsFragment({ RenderObject renderObjectOwner, SemanticsAnnotator annotator, - Iterable<_SemanticsFragment> children - }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children); + Iterable<_SemanticsFragment> children, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); @override SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) { @@ -798,8 +819,9 @@ class _ConcreteSemanticsFragment extends _InterestingSemanticsFragment { _ConcreteSemanticsFragment({ RenderObject renderObjectOwner, SemanticsAnnotator annotator, - Iterable<_SemanticsFragment> children - }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children); + Iterable<_SemanticsFragment> children, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); @override SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) { @@ -833,8 +855,9 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment { _ImplicitSemanticsFragment({ RenderObject renderObjectOwner, SemanticsAnnotator annotator, - Iterable<_SemanticsFragment> children - }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children); + Iterable<_SemanticsFragment> children, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); @override bool get haveConcreteNode => _haveConcreteNode; @@ -878,8 +901,9 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment { class _ForkingSemanticsFragment extends _SemanticsFragment { _ForkingSemanticsFragment({ RenderObject renderObjectOwner, - @required Iterable<_SemanticsFragment> children - }) : super(renderObjectOwner: renderObjectOwner, children: children) { + @required Iterable<_SemanticsFragment> children, + bool dropSemanticsOfPreviousSiblings, + }) : super(renderObjectOwner: renderObjectOwner, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings) { assert(children != null); assert(children.length > 1); } @@ -1414,6 +1438,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { super.adoptChild(child); markNeedsLayout(); markNeedsCompositingBitsUpdate(); + markNeedsSemanticsUpdate(); } /// Called by subclasses when they decide a render object is no longer a child. @@ -1431,6 +1456,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { super.dropChild(child); markNeedsLayout(); markNeedsCompositingBitsUpdate(); + markNeedsSemanticsUpdate(); } /// Calls visitor for each immediate child of this render object. @@ -2408,6 +2434,22 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// setting [isSemanticBoundary] to true. bool get isSemanticBoundary => false; + /// Whether this [RenderObject] makes other [RenderObject]s previously painted + /// within the same semantic boundary unreachable for accessibility purposes. + /// + /// If `true` is returned, the [SemanticsNode]s for all siblings and cousins + /// of this node, that are earlier in a depth-first pre-order traversal, are + /// dropped from the semantics tree up until a semantic boundary (as defined + /// by [isSemanticBoundary]) is reached. + /// + /// If [isSemanticBoundary] and [isBlockingSemanticsOfPreviouslyPaintedNodes] + /// is set on the same node, all previously painted siblings and cousins + /// up until the next ancestor that is a semantic boundary are dropped. + /// + /// Paint order as established by [visitChildrenForSemantics] is used to + /// determine if a node is previous to this one. + bool get isBlockingSemanticsOfPreviouslyPaintedNodes => false; + /// The bounding box, in the local coordinate system, of this /// object, for accessibility purposes. Rect get semanticBounds; @@ -2545,9 +2587,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { // early-exit if we're not dirty and have our own semantics if (!_needsSemanticsUpdate && isSemanticBoundary) { assert(_semantics != null); - return new _CleanSemanticsFragment(renderObjectOwner: this); + return new _CleanSemanticsFragment(renderObjectOwner: this, dropSemanticsOfPreviousSiblings: isBlockingSemanticsOfPreviouslyPaintedNodes); } List<_SemanticsFragment> children; + bool dropSemanticsOfPreviousSiblings = isBlockingSemanticsOfPreviouslyPaintedNodes; visitChildrenForSemantics((RenderObject child) { if (_needsSemanticsGeometryUpdate) { // If our geometry changed, make sure the child also does a @@ -2557,31 +2600,40 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { child._needsSemanticsGeometryUpdate = true; } final _SemanticsFragment fragment = child._getSemanticsFragment(); - if (fragment != null) { + assert(fragment != null); + if (fragment.dropSemanticsOfPreviousSiblings) { + children = null; // throw away all left siblings of [child]. + dropSemanticsOfPreviousSiblings = true; + } + if (fragment.producesSemanticNodes) { fragment.addAncestor(this); children ??= <_SemanticsFragment>[]; assert(!children.contains(fragment)); children.add(fragment); } }); + if (isSemanticBoundary && !isBlockingSemanticsOfPreviouslyPaintedNodes) { + // Don't propagate [dropSemanticsOfPreviousSiblings] up through a semantic boundary. + dropSemanticsOfPreviousSiblings = false; + } _needsSemanticsUpdate = false; _needsSemanticsGeometryUpdate = false; final SemanticsAnnotator annotator = semanticsAnnotator; if (parent is! RenderObject) - return new _RootSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children); + return new _RootSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); if (isSemanticBoundary) - return new _ConcreteSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children); + return new _ConcreteSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); if (annotator != null) - return new _ImplicitSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children); + return new _ImplicitSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); _semantics = null; if (children == null) { // Introduces no semantics and has no descendants that introduce semantics. - return null; + return new _EmptySemanticsFragment(renderObjectOwner: this, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); } if (children.length > 1) - return new _ForkingSemanticsFragment(renderObjectOwner: this, children: children); + return new _ForkingSemanticsFragment(renderObjectOwner: this, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings); assert(children.length == 1); - return children.single; + return children.single..dropSemanticsOfPreviousSiblings = dropSemanticsOfPreviousSiblings; } /// Called when collecting the semantics of this node. @@ -2727,6 +2779,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { description.add('layer: $_layer'); if (_semantics != null) description.add('semantics: $_semantics'); + if (isBlockingSemanticsOfPreviouslyPaintedNodes) + description.add('blocks semantics of earlier render objects below the common boundary'); + if (isSemanticBoundary) + description.add('semantic boundary'); } /// Returns a string describing the current node's descendants. Each line of diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 1dfd41691b887..1fadc45316daa 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2891,6 +2891,18 @@ class RenderSemanticsAnnotations extends RenderProxyBox { } } +/// Causes the semantics of all siblings and cousins painted before it in the +/// same semantic container to be dropped. +/// +/// This is useful in a stack where an overlay should prevent interactions +/// with the underlying layers. +class RenderBlockSemantics extends RenderProxyBox { + RenderBlockSemantics({ RenderBox child }) : super(child); + + @override + bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true; +} + /// Causes the semantics of all descendants to be merged into this /// node such that the entire subtree becomes a single leaf in the /// semantics tree. diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 11d4c3ad9de8f..ce45f9cc13b73 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -3613,6 +3613,27 @@ class MergeSemantics extends SingleChildRenderObjectWidget { RenderMergeSemantics createRenderObject(BuildContext context) => new RenderMergeSemantics(); } +/// A widget that drops the semantics of all widget that were painted before it +/// in the same semantic container. +/// +/// This is useful to hide widgets from accessibility tools that are painted +/// behind a certain widget, e.g. an alert should usually disallow interaction +/// with any widget located "behind" the alert (even when they are still +/// partially visible). Similarly, an open [Drawer] blocks interactions with +/// any widget outside the drawer. +/// +/// See also: +/// +/// * [ExcludeSemantics] which drops all semantics of its descendants. +class BlockSemantics extends SingleChildRenderObjectWidget { + /// Creates a widget that excludes the semantics of all widgets painted before + /// it in the same semantic container. + const BlockSemantics({ Key key, Widget child }) : super(key: key, child: child); + + @override + RenderBlockSemantics createRenderObject(BuildContext context) => new RenderBlockSemantics(); +} + /// A widget that drops all the semantics of its descendants. /// /// When [excluding] is true, this widget (and its subtree) is excluded from @@ -3622,6 +3643,10 @@ class MergeSemantics extends SingleChildRenderObjectWidget { /// reported but that would only be confusing. For example, the /// material library's [Chip] widget hides the avatar since it is /// redundant with the chip label. +/// +/// See also: +/// +/// * [BlockSemantics] which drops semantics of widgets earlier in the tree. class ExcludeSemantics extends SingleChildRenderObjectWidget { /// Creates a widget that drops all the semantics of its descendants. const ExcludeSemantics({ diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart index 0dbb979c4074c..76b79c3215537 100644 --- a/packages/flutter/lib/src/widgets/modal_barrier.dart +++ b/packages/flutter/lib/src/widgets/modal_barrier.dart @@ -26,21 +26,23 @@ class ModalBarrier extends StatelessWidget { @override Widget build(BuildContext context) { - return new ExcludeSemantics( - excluding: !dismissible, - child: new Semantics( - container: true, - child: new GestureDetector( - onTapDown: (TapDownDetails details) { - if (dismissible) - Navigator.pop(context); - }, - behavior: HitTestBehavior.opaque, - child: new ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: color == null ? null : new DecoratedBox( - decoration: new BoxDecoration( - color: color + return new BlockSemantics( + child: new ExcludeSemantics( + excluding: !dismissible, + child: new Semantics( + container: true, + child: new GestureDetector( + onTapDown: (TapDownDetails details) { + if (dismissible) + Navigator.pop(context); + }, + behavior: HitTestBehavior.opaque, + child: new ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: color == null ? null : new DecoratedBox( + decoration: new BoxDecoration( + color: color + ) ) ) ) diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart index 83a8f28b3961f..d2e4f8dd74a72 100644 --- a/packages/flutter/test/material/dialog_test.dart +++ b/packages/flutter/test/material/dialog_test.dart @@ -4,6 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:matcher/matcher.dart'; + +import '../widgets/semantics_tester.dart'; void main() { testWidgets('Dialog is scrollable', (WidgetTester tester) async { @@ -192,4 +195,38 @@ void main() { expect(find.text('Dialog2'), findsOneWidget); }); + + testWidgets('Dialog hides underlying semantics tree', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + const String buttonText = 'A button covered by dialog overlay'; + await tester.pumpWidget( + new MaterialApp( + home: const Material( + child: const Center( + child: const RaisedButton( + onPressed: null, + child: const Text(buttonText), + ), + ), + ), + ), + ); + + expect(semantics, includesNodeWithLabel(buttonText)); + + final BuildContext context = tester.element(find.text(buttonText)); + + const String alertText = 'A button in an overlay alert'; + showDialog( + context: context, + child: const AlertDialog(title: const Text(alertText)), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(semantics, includesNodeWithLabel(alertText)); + expect(semantics, isNot(includesNodeWithLabel(buttonText))); + + semantics.dispose(); + }); } diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 219abe6c43ec9..395589f030ca4 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import '../widgets/semantics_tester.dart'; + void main() { testWidgets('Scaffold control test', (WidgetTester tester) async { final Key bodyKey = new UniqueKey(); @@ -440,4 +442,40 @@ void main() { expect(tester.renderObject(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); }); }); + + testWidgets('Open drawer hides underlying semantics tree', (WidgetTester tester) async { + const String bodyLabel = 'I am the body'; + const String persistentFooterButtonLabel = 'a button on the bottom'; + const String bottomNavigationBarLabel = 'a bar in an app'; + const String floatingActionButtonLabel = 'I float in space'; + const String drawerLabel = 'I am the reason for this test'; + + final SemanticsTester semantics = new SemanticsTester(tester); + await tester.pumpWidget(new MaterialApp(home: new Scaffold( + body: new Semantics(label: bodyLabel, child: new Container()), + persistentFooterButtons: [new Semantics(label: persistentFooterButtonLabel, child: new Container())], + bottomNavigationBar: new Semantics(label: bottomNavigationBarLabel, child: new Container()), + floatingActionButton: new Semantics(label: floatingActionButtonLabel, child: new Container()), + drawer: new Drawer(child:new Semantics(label: drawerLabel, child: new Container())), + ))); + + expect(semantics, includesNodeWithLabel(bodyLabel)); + expect(semantics, includesNodeWithLabel(persistentFooterButtonLabel)); + expect(semantics, includesNodeWithLabel(bottomNavigationBarLabel)); + expect(semantics, includesNodeWithLabel(floatingActionButtonLabel)); + expect(semantics, isNot(includesNodeWithLabel(drawerLabel))); + + final ScaffoldState state = tester.firstState(find.byType(Scaffold)); + state.openDrawer(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(semantics, isNot(includesNodeWithLabel(bodyLabel))); + expect(semantics, isNot(includesNodeWithLabel(persistentFooterButtonLabel))); + expect(semantics, isNot(includesNodeWithLabel(bottomNavigationBarLabel))); + expect(semantics, isNot(includesNodeWithLabel(floatingActionButtonLabel))); + expect(semantics, includesNodeWithLabel(drawerLabel)); + + semantics.dispose(); + }); } diff --git a/packages/flutter/test/widgets/semantics_9_test.dart b/packages/flutter/test/widgets/semantics_9_test.dart new file mode 100644 index 0000000000000..17eccc0beff3f --- /dev/null +++ b/packages/flutter/test/widgets/semantics_9_test.dart @@ -0,0 +1,149 @@ +// Copyright 2017 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/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'semantics_tester.dart'; + +void main() { + group('BlockSemantics', () { + testWidgets('hides semantic nodes of siblings', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + await tester.pumpWidget(new Stack( + children: [ + new Semantics( + label: 'layer#1', + child: new Container(), + ), + const BlockSemantics(), + new Semantics( + label: 'layer#2', + child: new Container(), + ), + ], + )); + + expect(semantics, isNot(includesNodeWithLabel('layer#1'))); + + await tester.pumpWidget(new Stack( + children: [ + new Semantics( + label: 'layer#1', + child: new Container(), + ), + ], + )); + + expect(semantics, includesNodeWithLabel('layer#1')); + + semantics.dispose(); + }); + + testWidgets('does not hides semantic nodes of siblings outside the current semantic boundary', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + await tester.pumpWidget(new Stack( + children: [ + new Semantics( + label: '#1', + child: new Container(), + ), + new Semantics( + label: '#2', + container: true, + child: new Stack( + children: [ + new Semantics( + label: 'NOT#2.1', + child: new Container(), + ), + new Semantics( + label: '#2.2', + child: new BlockSemantics( + child: new Semantics( + container: true, + label: '#2.2.1', + child: new Container(), + ), + ), + ), + new Semantics( + label: '#2.3', + child: new Container(), + ), + ], + ), + ), + new Semantics( + label: '#3', + child: new Container(), + ), + ], + )); + + expect(semantics, includesNodeWithLabel('#1')); + expect(semantics, includesNodeWithLabel('#2')); + expect(semantics, isNot(includesNodeWithLabel('NOT#2.1'))); + expect(semantics, includesNodeWithLabel('#2.2')); + expect(semantics, includesNodeWithLabel('#2.2.1')); + expect(semantics, includesNodeWithLabel('#2.3')); + expect(semantics, includesNodeWithLabel('#3')); + + semantics.dispose(); + }); + + testWidgets('node is semantic boundary and blocking previously painted nodes', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + final GlobalKey stackKey = new GlobalKey(); + + await tester.pumpWidget(new Stack( + key: stackKey, + children: [ + new Semantics( + label: 'NOT#1', + child: new Container(), + ), + new BoundaryBlockSemantics( + child: new Semantics( + label: '#2.1', + child: new Container(), + ) + ), + new Semantics( + label: '#3', + child: new Container(), + ), + ], + )); + + expect(semantics, isNot(includesNodeWithLabel('NOT#1'))); + expect(semantics, includesNodeWithLabel('#2.1')); + expect(semantics, includesNodeWithLabel('#3')); + + semantics.dispose(); + }); + }); +} + +class BoundaryBlockSemantics extends SingleChildRenderObjectWidget { + const BoundaryBlockSemantics({ Key key, Widget child }) : super(key: key, child: child); + + @override + RenderBoundaryBlockSemantics createRenderObject(BuildContext context) => new RenderBoundaryBlockSemantics(); +} + +class RenderBoundaryBlockSemantics extends RenderProxyBox { + RenderBoundaryBlockSemantics({ RenderBox child }) : super(child); + + @override + bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true; + + @override + bool get isSemanticBoundary => true; +} + diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index eca1c757bd2e8..e538cceaaf125 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -194,6 +194,8 @@ class SemanticsTester { String toString() => 'SemanticsTester'; } +const String _matcherHelp = 'Try dumping the semantics with debugDumpSemanticsTree() from the rendering library to see what the semantics tree looks like.'; + class _HasSemantics extends Matcher { const _HasSemantics(this._semantics) : assert(_semantics != null); @@ -211,30 +213,65 @@ class _HasSemantics extends Matcher { @override Description describeMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) { - const String help = 'Try dumping the semantics with debugDumpSemanticsTree() from the rendering library to see what the semantics tree looks like.'; final TestSemantics testNode = matchState[TestSemantics]; final SemanticsNode node = matchState[SemanticsNode]; if (node == null) - return mismatchDescription.add('could not find node with id ${testNode.id}.\n$help'); + return mismatchDescription.add('could not find node with id ${testNode.id}.\n$_matcherHelp'); if (testNode.id != node.id) - return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}.\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}.\n$_matcherHelp'); final SemanticsData data = node.getSemanticsData(); if (testNode.flags != data.flags) - return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}.\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}.\n$_matcherHelp'); if (testNode.actions != data.actions) - return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}.\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}.\n$_matcherHelp'); if (testNode.label != data.label) - return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}".\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}".\n$_matcherHelp'); if (testNode.rect != data.rect) - return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}.\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}.\n$_matcherHelp'); if (testNode.transform != data.transform) - return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:.\n${data.transform}.\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:.\n${data.transform}.\n$_matcherHelp'); final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount; if (testNode.children.length != childrenCount) - return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} children but found $childrenCount.\n$help'); + return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} children but found $childrenCount.\n$_matcherHelp'); return mismatchDescription; } } /// Asserts that a [SemanticsTester] has a semantics tree that exactly matches the given semantics. Matcher hasSemantics(TestSemantics semantics) => new _HasSemantics(semantics); + +class _IncludesNodeWithLabel extends Matcher { + const _IncludesNodeWithLabel(this._label) : assert(_label != null); + + final String _label; + + @override + bool matches(covariant SemanticsTester item, Map matchState) { + bool result = false; + SemanticsNodeVisitor visitor; + visitor = (SemanticsNode node) { + if (node.label == _label) { + result = true; + } else { + node.visitChildren(visitor); + } + return !result; + }; + final SemanticsNode root = item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode; + visitor(root); + return result; + } + + @override + Description describe(Description description) { + return description.add('includes node with label "$_label"'); + } + + @override + Description describeMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) { + return mismatchDescription.add('could not find node with label "$_label".\n$_matcherHelp'); + } +} + +/// Asserts that a node in the semantics tree of [SemanticsTester] has [label]. +Matcher includesNodeWithLabel(String label) => new _IncludesNodeWithLabel(label); From fe520201b829591175f7287c06d970b537caf0d8 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 2 Jun 2017 18:49:32 -0700 Subject: [PATCH 025/110] Emit doctor error if Flutter SDK path contains spaces (#10477) Should be reverted when https://github.com/flutter/flutter/issues/6577 is fixed. --- packages/flutter_tools/lib/src/doctor.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index f3ec99aabe661..a4b7e48e7c8de 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -210,6 +210,10 @@ class _FlutterValidator extends DoctorValidator { final FlutterVersion version = FlutterVersion.instance; messages.add(new ValidationMessage('Flutter at ${Cache.flutterRoot}')); + if (Cache.flutterRoot.contains(' ')) + messages.add(new ValidationMessage.error( + 'Flutter SDK install paths with spaces are not yet supported. (https://github.com/flutter/flutter/issues/10461)\n' + 'Please move the SDK to a path that does not include spaces.')); messages.add(new ValidationMessage( 'Framework revision ${version.frameworkRevisionShort} ' '(${version.frameworkAge}), ${version.frameworkDate}' From c55097da7d0d3b0165c5988ff7adb3eaed614a61 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Sat, 3 Jun 2017 06:05:09 +0200 Subject: [PATCH 026/110] apply prefer_asserts_in_initializer_list lint (#10463) --- packages/flutter/lib/src/material/app.dart | 11 +++--- .../flutter/lib/src/material/app_bar.dart | 18 +++++----- .../src/material/bottom_navigation_bar.dart | 23 ++++++------ .../flutter/lib/src/material/data_table.dart | 16 ++++----- .../flutter/lib/src/material/date_picker.dart | 35 +++++++++---------- packages/flutter/lib/src/material/dialog.dart | 5 ++- .../flutter/lib/src/material/dropdown.dart | 12 +++---- .../lib/src/material/expansion_panel.dart | 8 ++--- .../lib/src/material/ink_highlight.dart | 6 ++-- .../flutter/lib/src/material/material.dart | 7 ++-- packages/flutter/lib/src/material/page.dart | 7 ++-- .../src/material/paginated_data_table.dart | 29 ++++++++------- .../flutter/lib/src/material/scrollbar.dart | 4 +-- packages/flutter/lib/src/material/slider.dart | 4 +-- .../flutter/lib/src/material/stepper.dart | 11 +++--- .../lib/src/material/tab_controller.dart | 9 +++-- packages/flutter/lib/src/material/tabs.dart | 31 ++++++++-------- .../flutter/lib/src/material/toggleable.dart | 10 +++--- 18 files changed, 111 insertions(+), 135 deletions(-) diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index 17b34c1697894..425944da8516e 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -63,12 +63,11 @@ class MaterialApp extends StatefulWidget { this.checkerboardOffscreenLayers: 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)); - } + }) : assert(debugShowMaterialGrid != null), + assert(routes != null), + assert(!routes.containsKey(Navigator.defaultRouteName) || (home == null)), + assert(routes.containsKey(Navigator.defaultRouteName) || (home != null) || (onGenerateRoute != null)), + super(key: key); /// A one-line description of this app for use in the window manager. final String title; diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index c11f6e437fdd7..09fd27d2586ef 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -138,13 +138,12 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { this.centerTitle, this.toolbarOpacity: 1.0, this.bottomOpacity: 1.0, - }) : preferredSize = new Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), - super(key: key) { - assert(elevation != null); - assert(primary != null); - assert(toolbarOpacity != null); - assert(bottomOpacity != null); - } + }) : assert(elevation != null), + assert(primary != null), + assert(toolbarOpacity != null), + assert(bottomOpacity != null), + preferredSize = new Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), + super(key: key); /// A widget to display before the [title]. /// @@ -513,9 +512,8 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { @required this.floating, @required this.pinned, @required this.snapConfiguration, - }) : _bottomHeight = bottom?.preferredSize?.height ?? 0.0 { - assert(primary || topPadding == 0.0); - } + }) : assert(primary || topPadding == 0.0), + _bottomHeight = bottom?.preferredSize?.height ?? 0.0; final Widget leading; final Widget title; diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index 5fba48526818e..011848688fcc8 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -69,14 +69,13 @@ class BottomNavigationBar extends StatefulWidget { this.type: BottomNavigationBarType.fixed, this.fixedColor, this.iconSize: 24.0, - }) : super(key: key) { - assert(items != null); - assert(items.length >= 2); - assert(0 <= currentIndex && currentIndex < items.length); - assert(type != null); - assert(type == BottomNavigationBarType.fixed || fixedColor == null); - assert(iconSize != null); - } + }) : assert(items != null), + assert(items.length >= 2), + assert(0 <= currentIndex && currentIndex < items.length), + assert(type != null), + assert(type == BottomNavigationBarType.fixed || fixedColor == null), + assert(iconSize != null), + super(key: key); /// The interactive items laid out within the bottom navigation bar. final List items; @@ -450,11 +449,9 @@ class _Circle { @required this.index, @required this.color, @required TickerProvider vsync, - }) { - assert(state != null); - assert(index != null); - assert(color != null); - + }) : assert(state != null), + assert(index != null), + assert(color != null) { controller = new AnimationController( duration: kThemeAnimationDuration, vsync: vsync, diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index 2730acb78e79a..040d0dcfcf12d 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -259,14 +259,14 @@ class DataTable extends StatelessWidget { this.sortAscending: true, this.onSelectAll, @required this.rows - }) : _onlyTextColumn = _initOnlyTextColumn(columns), super(key: key) { - assert(columns != null); - assert(columns.isNotEmpty); - assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)); - assert(sortAscending != null); - assert(rows != null); - assert(!rows.any((DataRow row) => row.cells.length != columns.length)); - } + }) : assert(columns != null), + assert(columns.isNotEmpty), + assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), + assert(sortAscending != null), + assert(rows != null), + assert(!rows.any((DataRow row) => row.cells.length != columns.length)), + _onlyTextColumn = _initOnlyTextColumn(columns), + super(key: key); /// The configuration and labels for the columns in the table. final List columns; diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index c60a738c6dd03..14cb4552770dc 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -181,14 +181,13 @@ class DayPicker extends StatelessWidget { @required this.lastDate, @required this.displayedMonth, this.selectableDayPredicate, - }) : super(key: key) { - assert(selectedDate != null); - assert(currentDate != null); - assert(onChanged != null); - assert(displayedMonth != null); - assert(!firstDate.isAfter(lastDate)); - assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)); - } + }) : assert(selectedDate != null), + assert(currentDate != null), + assert(onChanged != null), + assert(displayedMonth != null), + assert(!firstDate.isAfter(lastDate)), + assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)), + super(key: key); /// The currently selected date. /// @@ -331,12 +330,11 @@ class MonthPicker extends StatefulWidget { @required this.firstDate, @required this.lastDate, this.selectableDayPredicate, - }) : super(key: key) { - assert(selectedDate != null); - assert(onChanged != null); - assert(!firstDate.isAfter(lastDate)); - assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)); - } + }) : assert(selectedDate != null), + assert(onChanged != null), + assert(!firstDate.isAfter(lastDate)), + assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)), + super(key: key); /// The currently selected date. /// @@ -519,11 +517,10 @@ class YearPicker extends StatefulWidget { @required this.onChanged, @required this.firstDate, @required this.lastDate, - }) : super(key: key) { - assert(selectedDate != null); - assert(onChanged != null); - assert(!firstDate.isAfter(lastDate)); - } + }) : assert(selectedDate != null), + assert(onChanged != null), + assert(!firstDate.isAfter(lastDate)), + super(key: key); /// The currently selected date. /// diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 33b568d802e33..c4bc60fc0116e 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -319,9 +319,8 @@ class _DialogRoute extends PopupRoute { @required this.theme, bool barrierDismissible: true, @required this.child, - }) : _barrierDismissible = barrierDismissible { - assert(barrierDismissible != null); - } + }) : assert(barrierDismissible != null), + _barrierDismissible = barrierDismissible; final Widget child; final ThemeData theme; diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 627f8dcd2adb8..b0ff98253e949 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -266,9 +266,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { this.elevation: 8, this.theme, @required this.style, - }) { - assert(style != null); - } + }) : assert(style != null); final List> items; final Rect buttonRect; @@ -425,11 +423,9 @@ class DropdownButton extends StatefulWidget { this.style, this.iconSize: 24.0, this.isDense: false, - }) : super(key: key) { - assert(items != null); - assert(value == null || - items.where((DropdownMenuItem item) => item.value == value).length == 1); - } + }) : assert(items != null), + assert(value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1), + super(key: key); /// The list of possible items to select among. final List> items; diff --git a/packages/flutter/lib/src/material/expansion_panel.dart b/packages/flutter/lib/src/material/expansion_panel.dart index 2b8a40aa57708..39ace458818d5 100644 --- a/packages/flutter/lib/src/material/expansion_panel.dart +++ b/packages/flutter/lib/src/material/expansion_panel.dart @@ -43,11 +43,9 @@ class ExpansionPanel { @required this.headerBuilder, @required this.body, this.isExpanded: false - }) { - assert(headerBuilder != null); - assert(body != null); - assert(isExpanded != null); - } + }) : assert(headerBuilder != null), + assert(body != null), + assert(isExpanded != null); /// The widget builder that builds the expansion panels' header. final ExpansionPanelHeaderBuilder headerBuilder; diff --git a/packages/flutter/lib/src/material/ink_highlight.dart b/packages/flutter/lib/src/material/ink_highlight.dart index 29cb6912f1b99..3130b3d8a94c3 100644 --- a/packages/flutter/lib/src/material/ink_highlight.dart +++ b/packages/flutter/lib/src/material/ink_highlight.dart @@ -43,13 +43,13 @@ class InkHighlight extends InkFeature { BorderRadius borderRadius, RectCallback rectCallback, VoidCallback onRemoved, - }) : _color = color, + }) : assert(color != null), + assert(shape != null), + _color = color, _shape = shape, _borderRadius = borderRadius ?? BorderRadius.zero, _rectCallback = rectCallback, super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) { - assert(color != null); - assert(shape != null); _alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: controller.vsync) ..addListener(controller.markNeedsPaint) ..addStatusListener(_handleAlphaStatusChanged) diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 283b5550f0cd1..a03aa5ff6ee6e 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -366,10 +366,9 @@ abstract class InkFeature { @required MaterialInkController controller, @required this.referenceBox, this.onRemoved - }) : _controller = controller { - assert(_controller != null); - assert(referenceBox != null); - } + }) : assert(controller != null), + assert(referenceBox != null), + _controller = controller; /// The [MaterialInkController] associated with this [InkFeature]. /// diff --git a/packages/flutter/lib/src/material/page.dart b/packages/flutter/lib/src/material/page.dart index 2a2fb7d442f77..6ff54e23c9e13 100644 --- a/packages/flutter/lib/src/material/page.dart +++ b/packages/flutter/lib/src/material/page.dart @@ -61,10 +61,9 @@ class MaterialPageRoute extends PageRoute { RouteSettings settings: const RouteSettings(), this.maintainState: true, this.fullscreenDialog: false, - }) : super(settings: settings) { - assert(builder != null); - assert(opaque); - } + }) : assert(builder != null), + assert(opaque), + super(settings: settings); /// Builds the primary contents of the route. final WidgetBuilder builder; diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index c2bee851eb299..1c260627abc36 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -73,21 +73,20 @@ class PaginatedDataTable extends StatefulWidget { this.availableRowsPerPage: const [defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10], this.onRowsPerPageChanged, @required this.source - }) : super(key: key) { - assert(header != null); - assert(columns != null); - assert(columns.isNotEmpty); - assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)); - assert(sortAscending != null); - assert(rowsPerPage != null); - assert(rowsPerPage > 0); - assert(() { - if (onRowsPerPageChanged != null) - assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage)); - return true; - }); - assert(source != null); - } + }) : assert(header != null), + assert(columns != null), + assert(columns.isNotEmpty), + assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), + assert(sortAscending != null), + assert(rowsPerPage != null), + assert(rowsPerPage > 0), + assert(() { + if (onRowsPerPageChanged != null) + assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage)); + return true; + }), + assert(source != null), + super(key: key); /// The table card's header. /// diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart index 6c0859f170027..748030761ddab 100644 --- a/packages/flutter/lib/src/material/scrollbar.dart +++ b/packages/flutter/lib/src/material/scrollbar.dart @@ -84,8 +84,8 @@ class _ScrollbarState extends State with TickerProviderStateMixin { } class _ScrollbarPainter extends ChangeNotifier implements CustomPainter { - _ScrollbarPainter(TickerProvider vsync) { - assert(vsync != null); + _ScrollbarPainter(TickerProvider vsync) + : assert(vsync != null) { _fadeController = new AnimationController(duration: _kThumbFadeDuration, vsync: vsync); _opacity = new CurvedAnimation(parent: _fadeController, curve: Curves.fastOutSlowIn) ..addListener(notifyListeners); diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index e6a8a6826aede..213884cdd7bff 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -280,12 +280,12 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { TextTheme textTheme, this.onChanged, TickerProvider vsync, - }) : _value = value, + }) : assert(value != null && value >= 0.0 && value <= 1.0), + _value = value, _divisions = divisions, _activeColor = activeColor, _thumbOpenAtMin = thumbOpenAtMin, _textTheme = textTheme { - assert(value != null && value >= 0.0 && value <= 1.0); this.label = label; final GestureArenaTeam team = new GestureArenaTeam(); _drag = new HorizontalDragGestureRecognizer() diff --git a/packages/flutter/lib/src/material/stepper.dart b/packages/flutter/lib/src/material/stepper.dart index c2e964c5f5f88..e83c47e91429d 100644 --- a/packages/flutter/lib/src/material/stepper.dart +++ b/packages/flutter/lib/src/material/stepper.dart @@ -140,12 +140,11 @@ class Stepper extends StatefulWidget { this.onStepTapped, this.onStepContinue, this.onStepCancel, - }) : super(key: key) { - assert(steps != null); - assert(type != null); - assert(currentStep != null); - assert(0 <= currentStep && currentStep < steps.length); - } + }) : assert(steps != null), + assert(type != null), + assert(currentStep != null), + assert(0 <= currentStep && currentStep < steps.length), + super(key: key); /// The steps of the stepper whose titles, subtitles, icons always get shown. /// diff --git a/packages/flutter/lib/src/material/tab_controller.dart b/packages/flutter/lib/src/material/tab_controller.dart index beafdd72995e9..ebe89d408239a 100644 --- a/packages/flutter/lib/src/material/tab_controller.dart +++ b/packages/flutter/lib/src/material/tab_controller.dart @@ -63,16 +63,15 @@ import 'constants.dart'; class TabController extends ChangeNotifier { /// Creates an object that manages the state required by [TabBar] and a [TabBarView]. TabController({ int initialIndex: 0, @required this.length, @required TickerProvider vsync }) - : _index = initialIndex, + : assert(length != null && length > 1), + assert(initialIndex != null && initialIndex >= 0 && initialIndex < length), + _index = initialIndex, _previousIndex = initialIndex, _animationController = new AnimationController( value: initialIndex.toDouble(), upperBound: (length - 1).toDouble(), vsync: vsync - ) { - assert(length != null && length > 1); - assert(initialIndex != null && initialIndex >= 0 && initialIndex < length); - } + ); /// An animation whose value represents the current position of the [TabBar]'s /// selected tab indicator as well as the scrollOffsets of the [TabBar] diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index c6ed44bb6ea09..3b850f41a3cd1 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -155,16 +155,15 @@ class _TabLabelBarRenderer extends RenderFlex { CrossAxisAlignment crossAxisAlignment, TextBaseline textBaseline, @required this.onPerformLayout, - }) : super( - children: children, - direction: direction, - mainAxisSize: mainAxisSize, - mainAxisAlignment: mainAxisAlignment, - crossAxisAlignment: crossAxisAlignment, - textBaseline: textBaseline, - ) { - assert(onPerformLayout != null); - } + }) : assert(onPerformLayout != null), + super( + children: children, + direction: direction, + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + textBaseline: textBaseline, + ); ValueChanged> onPerformLayout; @@ -411,10 +410,9 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { this.labelStyle, this.unselectedLabelColor, this.unselectedLabelStyle, - }) : super(key: key) { - assert(tabs != null && tabs.length > 1); - assert(isScrollable != null); - } + }) : assert(tabs != null && tabs.length > 1), + assert(isScrollable != null), + super(key: key); /// Typically a list of [Tab] widgets. final List tabs; @@ -741,9 +739,8 @@ class TabBarView extends StatefulWidget { Key key, @required this.children, this.controller, - }) : super(key: key) { - assert(children != null && children.length > 1); - } + }) : assert(children != null && children.length > 1), + super(key: key); /// This widget's selection and animation state. /// diff --git a/packages/flutter/lib/src/material/toggleable.dart b/packages/flutter/lib/src/material/toggleable.dart index 08f17461693a2..6d65025eebf2d 100644 --- a/packages/flutter/lib/src/material/toggleable.dart +++ b/packages/flutter/lib/src/material/toggleable.dart @@ -30,16 +30,16 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic @required Color inactiveColor, ValueChanged onChanged, @required TickerProvider vsync, - }) : _value = value, + }) : assert(value != null), + assert(activeColor != null), + assert(inactiveColor != null), + assert(vsync != null), + _value = value, _activeColor = activeColor, _inactiveColor = inactiveColor, _onChanged = onChanged, _vsync = vsync, super(additionalConstraints: new BoxConstraints.tight(size)) { - assert(value != null); - assert(activeColor != null); - assert(inactiveColor != null); - assert(vsync != null); _tap = new TapGestureRecognizer() ..onTapDown = _handleTapDown ..onTap = _handleTap From abb7669704eb533fb4dd8f9be229bd6ad75eeeef Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Mon, 5 Jun 2017 09:28:25 +0200 Subject: [PATCH 027/110] apply prefer_asserts_in_initializer_list lint (#10489) --- .../lib/src/rendering/animated_size.dart | 9 +- .../lib/src/rendering/custom_layout.dart | 4 +- .../flutter/lib/src/rendering/editable.dart | 8 +- packages/flutter/lib/src/rendering/flex.dart | 10 +- packages/flutter/lib/src/rendering/flow.dart | 4 +- packages/flutter/lib/src/rendering/layer.dart | 8 +- .../flutter/lib/src/rendering/object.dart | 56 +++++---- .../flutter/lib/src/rendering/paragraph.dart | 15 ++- .../src/rendering/performance_overlay.dart | 17 ++- .../flutter/lib/src/rendering/proxy_box.dart | 119 +++++++++--------- .../lib/src/rendering/rotated_box.dart | 4 +- .../lib/src/rendering/shifted_box.dart | 45 ++++--- .../lib/src/rendering/sliver_fill.dart | 8 +- .../lib/src/rendering/sliver_grid.dart | 7 +- .../rendering/sliver_multi_box_adaptor.dart | 5 +- .../lib/src/rendering/sliver_padding.dart | 6 +- .../rendering/sliver_persistent_header.dart | 8 +- packages/flutter/lib/src/rendering/stack.dart | 8 +- packages/flutter/lib/src/rendering/table.dart | 11 +- .../flutter/lib/src/rendering/viewport.dart | 15 ++- packages/flutter/lib/src/rendering/wrap.dart | 14 +-- 21 files changed, 185 insertions(+), 196 deletions(-) diff --git a/packages/flutter/lib/src/rendering/animated_size.dart b/packages/flutter/lib/src/rendering/animated_size.dart index 2e707e8ca4d4c..788404dddca07 100644 --- a/packages/flutter/lib/src/rendering/animated_size.dart +++ b/packages/flutter/lib/src/rendering/animated_size.dart @@ -39,10 +39,11 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { Curve curve: Curves.linear, FractionalOffset alignment: FractionalOffset.center, RenderBox child, - }) : _vsync = vsync, super(child: child, alignment: alignment) { - assert(vsync != null); - assert(duration != null); - assert(curve != null); + }) : assert(vsync != null), + assert(duration != null), + assert(curve != null), + _vsync = vsync, + super(child: child, alignment: alignment) { _controller = new AnimationController( vsync: vsync, duration: duration, diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart index bcb2dd8cef526..c5a3e0eef6814 100644 --- a/packages/flutter/lib/src/rendering/custom_layout.dart +++ b/packages/flutter/lib/src/rendering/custom_layout.dart @@ -268,8 +268,8 @@ class RenderCustomMultiChildLayoutBox extends RenderBox RenderCustomMultiChildLayoutBox({ List children, @required MultiChildLayoutDelegate delegate - }) : _delegate = delegate { - assert(delegate != null); + }) : assert(delegate != null), + _delegate = delegate { addAll(children); } diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index d3d0922e1b67a..2e46c1914791a 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -71,16 +71,16 @@ class RenderEditable extends RenderBox { @required ViewportOffset offset, this.onSelectionChanged, this.onCaretChanged, - }) : _textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor), + }) : assert(maxLines != null), + assert(textScaleFactor != null), + assert(offset != null), + _textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor), _cursorColor = cursorColor, _showCursor = showCursor ?? new ValueNotifier(false), _maxLines = maxLines, _selection = selection, _offset = offset { assert(_showCursor != null); - assert(maxLines != null); - assert(textScaleFactor != null); - assert(offset != null); assert(!_showCursor.value || cursorColor != null); _tap = new TapGestureRecognizer() ..onTapDown = _handleTapDown diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart index 78c1aa6dc065e..e85650a5cdd9d 100644 --- a/packages/flutter/lib/src/rendering/flex.dart +++ b/packages/flutter/lib/src/rendering/flex.dart @@ -201,15 +201,15 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin children, @required FlowDelegate delegate - }) : _delegate = delegate { - assert(delegate != null); + }) : assert(delegate != null), + _delegate = delegate { addAll(children); } diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 582fb25315ed8..da25813b60800 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -643,11 +643,9 @@ class PhysicalModelLayer extends ContainerLayer { @required this.clipRRect, @required this.elevation, @required this.color, - }) { - assert(clipRRect != null); - assert(elevation != null); - assert(color != null); - } + }) : assert(clipRRect != null), + assert(elevation != null), + assert(color != null); /// The rounded-rect to clip in the parent's coordinate system. /// diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 1e2a4771c3672..0eed843453801 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -57,10 +57,9 @@ typedef void PaintingContextCallback(PaintingContext context, Offset offset); /// not hold a reference to the canvas across operations that might paint /// child render objects. class PaintingContext { - PaintingContext._(this._containerLayer, this._paintBounds) { - assert(_containerLayer != null); - assert(_paintBounds != null); - } + PaintingContext._(this._containerLayer, this._paintBounds) + : assert(_containerLayer != null), + assert(_paintBounds != null); final ContainerLayer _containerLayer; final Rect _paintBounds; @@ -664,19 +663,17 @@ abstract class _SemanticsFragment { this.annotator, List<_SemanticsFragment> children, this.dropSemanticsOfPreviousSiblings, - }) { - assert(renderObjectOwner != null); - _ancestorChain = [renderObjectOwner]; - assert(() { - if (children == null) - return true; - final Set<_SemanticsFragment> seenChildren = new Set<_SemanticsFragment>(); - for (_SemanticsFragment child in children) - assert(seenChildren.add(child)); // check for duplicate adds - return true; - }); - _children = children ?? const <_SemanticsFragment>[]; - } + }) : assert(renderObjectOwner != null), + assert(() { + if (children == null) + return true; + final Set<_SemanticsFragment> seenChildren = new Set<_SemanticsFragment>(); + for (_SemanticsFragment child in children) + assert(seenChildren.add(child)); // check for duplicate adds + return true; + }), + _ancestorChain = [renderObjectOwner], + _children = children ?? const <_SemanticsFragment>[]; final SemanticsAnnotator annotator; bool dropSemanticsOfPreviousSiblings; @@ -722,10 +719,12 @@ class _CleanSemanticsFragment extends _SemanticsFragment { _CleanSemanticsFragment({ @required RenderObject renderObjectOwner, bool dropSemanticsOfPreviousSiblings, - }) : super(renderObjectOwner: renderObjectOwner, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings) { - assert(renderObjectOwner != null); - assert(renderObjectOwner._semantics != null); - } + }) : assert(renderObjectOwner != null), + assert(renderObjectOwner._semantics != null), + super( + renderObjectOwner: renderObjectOwner, + dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings + ); @override Iterable compile({ _SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics }) sync* { @@ -903,10 +902,13 @@ class _ForkingSemanticsFragment extends _SemanticsFragment { RenderObject renderObjectOwner, @required Iterable<_SemanticsFragment> children, bool dropSemanticsOfPreviousSiblings, - }) : super(renderObjectOwner: renderObjectOwner, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings) { - assert(children != null); - assert(children.length > 1); - } + }) : assert(children != null), + assert(children.length > 1), + super( + renderObjectOwner: renderObjectOwner, + children: children, + dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings + ); @override Iterable compile({ @@ -946,8 +948,8 @@ class _ForkingSemanticsFragment extends _SemanticsFragment { /// [PipelineOwner] for the render tree from which you wish to read semantics. /// You can obtain the [PipelineOwner] using the [RenderObject.owner] property. class SemanticsHandle { - SemanticsHandle._(this._owner, this.listener) { - assert(_owner != null); + SemanticsHandle._(this._owner, this.listener) + : assert(_owner != null) { if (listener != null) _owner.semanticsOwner.addListener(listener); } diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 043f9540b0eb5..03d537005cd2f 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -38,7 +38,12 @@ class RenderParagraph extends RenderBox { TextOverflow overflow: TextOverflow.clip, double textScaleFactor: 1.0, int maxLines, - }) : _softWrap = softWrap, + }) : assert(text != null), + assert(text.debugAssertIsValid()), + assert(softWrap != null), + assert(overflow != null), + assert(textScaleFactor != null), + _softWrap = softWrap, _overflow = overflow, _textPainter = new TextPainter( text: text, @@ -46,13 +51,7 @@ class RenderParagraph extends RenderBox { textScaleFactor: textScaleFactor, maxLines: maxLines, ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, - ) { - assert(text != null); - assert(text.debugAssertIsValid()); - assert(softWrap != null); - assert(overflow != null); - assert(textScaleFactor != null); - } + ); final TextPainter _textPainter; diff --git a/packages/flutter/lib/src/rendering/performance_overlay.dart b/packages/flutter/lib/src/rendering/performance_overlay.dart index 22fd8cf66e49a..8d06080295c1a 100644 --- a/packages/flutter/lib/src/rendering/performance_overlay.dart +++ b/packages/flutter/lib/src/rendering/performance_overlay.dart @@ -66,15 +66,14 @@ class RenderPerformanceOverlay extends RenderBox { int rasterizerThreshold: 0, bool checkerboardRasterCacheImages: false, bool checkerboardOffscreenLayers: false, - }) : _optionsMask = optionsMask, - _rasterizerThreshold = rasterizerThreshold, - _checkerboardRasterCacheImages = checkerboardRasterCacheImages, - _checkerboardOffscreenLayers = checkerboardOffscreenLayers { - assert(optionsMask != null); - assert(rasterizerThreshold != null); - assert(checkerboardRasterCacheImages != null); - assert(checkerboardOffscreenLayers != null); - } + }) : assert(optionsMask != null), + assert(rasterizerThreshold != null), + assert(checkerboardRasterCacheImages != null), + assert(checkerboardOffscreenLayers != null), + _optionsMask = optionsMask, + _rasterizerThreshold = rasterizerThreshold, + _checkerboardRasterCacheImages = checkerboardRasterCacheImages, + _checkerboardOffscreenLayers = checkerboardOffscreenLayers; /// The mask is created by shifting 1 by the index of the specific /// [PerformanceOverlayOption] to enable. diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 1fadc45316daa..23474cf0bb965 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -201,10 +201,10 @@ class RenderConstrainedBox extends RenderProxyBox { RenderConstrainedBox({ RenderBox child, @required BoxConstraints additionalConstraints, - }) : _additionalConstraints = additionalConstraints, super(child) { - assert(additionalConstraints != null); - assert(additionalConstraints.debugAssertIsValid()); - } + }) : assert(additionalConstraints != null), + assert(additionalConstraints.debugAssertIsValid()), + _additionalConstraints = additionalConstraints, + super(child); /// Additional constraints to apply to [child] during layout BoxConstraints get additionalConstraints => _additionalConstraints; @@ -311,10 +311,11 @@ class RenderLimitedBox extends RenderProxyBox { RenderBox child, double maxWidth: double.INFINITY, double maxHeight: double.INFINITY - }) : _maxWidth = maxWidth, _maxHeight = maxHeight, super(child) { - assert(maxWidth != null && maxWidth >= 0.0); - assert(maxHeight != null && maxHeight >= 0.0); - } + }) : assert(maxWidth != null && maxWidth >= 0.0), + assert(maxHeight != null && maxHeight >= 0.0), + _maxWidth = maxWidth, + _maxHeight = maxHeight, + super(child); /// The value to use for maxWidth if the incoming maxWidth constraint is infinite. double get maxWidth => _maxWidth; @@ -400,11 +401,11 @@ class RenderAspectRatio extends RenderProxyBox { RenderAspectRatio({ RenderBox child, @required double aspectRatio, - }) : _aspectRatio = aspectRatio, super(child) { - assert(aspectRatio != null); - assert(aspectRatio > 0.0); - assert(aspectRatio.isFinite); - } + }) : assert(aspectRatio != null), + assert(aspectRatio > 0.0), + assert(aspectRatio.isFinite), + _aspectRatio = aspectRatio, + super(child); /// The aspect ratio to attempt to use. /// @@ -714,10 +715,11 @@ class RenderOpacity extends RenderProxyBox { /// /// The [opacity] argument must be between 0.0 and 1.0, inclusive. RenderOpacity({ double opacity: 1.0, RenderBox child }) - : _opacity = opacity, _alpha = _getAlphaFromOpacity(opacity), super(child) { - assert(opacity != null); - assert(opacity >= 0.0 && opacity <= 1.0); - } + : assert(opacity != null), + assert(opacity >= 0.0 && opacity <= 1.0), + _opacity = opacity, + _alpha = _getAlphaFromOpacity(opacity), + super(child); @override bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255); @@ -792,10 +794,11 @@ class RenderShaderMask extends RenderProxyBox { RenderBox child, @required ShaderCallback shaderCallback, BlendMode blendMode: BlendMode.modulate, - }) : _shaderCallback = shaderCallback, _blendMode = blendMode, super(child) { - assert(shaderCallback != null); - assert(blendMode != null); - } + }) : assert(shaderCallback != null), + assert(blendMode != null), + _shaderCallback = shaderCallback, + _blendMode = blendMode, + super(child); /// Called to creates the [Shader] that generates the mask. /// @@ -849,9 +852,9 @@ class RenderBackdropFilter extends RenderProxyBox { /// /// The [filter] argument must not be null. RenderBackdropFilter({ RenderBox child, @required ui.ImageFilter filter }) - : _filter = filter, super(child) { - assert(filter != null); - } + : assert(filter != null), + _filter = filter, + super(child); /// The image filter to apply to the existing painted content before painting /// the child. @@ -1221,15 +1224,14 @@ class RenderPhysicalModel extends _RenderCustomClip { BorderRadius borderRadius, double elevation: 0.0, @required Color color, - }) : _shape = shape, + }) : assert(shape != null), + assert(elevation != null), + assert(color != null), + _shape = shape, _borderRadius = borderRadius, _elevation = elevation, _color = color, - super(child: child) { - assert(shape != null); - assert(elevation != null); - assert(color != null); - } + super(child: child); /// The shape of the layer. /// @@ -1347,14 +1349,13 @@ class RenderDecoratedBox extends RenderProxyBox { DecorationPosition position: DecorationPosition.background, ImageConfiguration configuration: ImageConfiguration.empty, RenderBox child - }) : _decoration = decoration, + }) : assert(decoration != null), + assert(position != null), + assert(configuration != null), + _decoration = decoration, _position = position, _configuration = configuration, - super(child) { - assert(decoration != null); - assert(position != null); - assert(configuration != null); - } + super(child); BoxPainter _painter; @@ -1468,9 +1469,9 @@ class RenderTransform extends RenderProxyBox { FractionalOffset alignment, this.transformHitTests: true, RenderBox child - }) : super(child) { - assert(transform != null); - assert(alignment == null || (alignment.dx != null && alignment.dy != null)); + }) : assert(transform != null), + assert(alignment == null || (alignment.dx != null && alignment.dy != null)), + super(child) { this.transform = transform; this.alignment = alignment; this.origin = origin; @@ -1633,10 +1634,11 @@ class RenderFittedBox extends RenderProxyBox { RenderBox child, BoxFit fit: BoxFit.contain, FractionalOffset alignment: FractionalOffset.center - }) : _fit = fit, _alignment = alignment, super(child) { - assert(fit != null); - assert(alignment != null && alignment.dx != null && alignment.dy != null); - } + }) : assert(fit != null), + assert(alignment != null && alignment.dx != null && alignment.dy != null), + _fit = fit, + _alignment = alignment, + super(child); /// How to inscribe the child into the space allocated during layout. BoxFit get fit => _fit; @@ -1771,9 +1773,9 @@ class RenderFractionalTranslation extends RenderProxyBox { FractionalOffset translation, this.transformHitTests: true, RenderBox child - }) : _translation = translation, super(child) { - assert(translation == null || (translation.dx != null && translation.dy != null)); - } + }) : assert(translation == null || (translation.dx != null && translation.dy != null)), + _translation = translation, + super(child); /// The translation to apply to the child, as a multiple of the size. FractionalOffset get translation => _translation; @@ -2020,12 +2022,11 @@ class RenderCustomPaint extends RenderProxyBox { CustomPainter foregroundPainter, Size preferredSize: Size.zero, RenderBox child, - }) : _painter = painter, + }) : assert(preferredSize != null), + _painter = painter, _foregroundPainter = foregroundPainter, _preferredSize = preferredSize, - super(child) { - assert(preferredSize != null); - } + super(child); /// The background custom paint delegate. /// @@ -2490,9 +2491,9 @@ class RenderOffstage extends RenderProxyBox { RenderOffstage({ bool offstage: true, RenderBox child - }) : _offstage = offstage, super(child) { - assert(offstage != null); - } + }) : assert(offstage != null), + _offstage = offstage, + super(child); /// Whether the child is hidden from the rest of the tree. /// @@ -2609,9 +2610,8 @@ class RenderAbsorbPointer extends RenderProxyBox { RenderAbsorbPointer({ RenderBox child, this.absorbing: true - }) : super(child) { - assert(absorbing != null); - } + }) : assert(absorbing != null), + super(child); /// Whether this render object absorbs pointers during hit testing. /// @@ -2824,12 +2824,11 @@ class RenderSemanticsAnnotations extends RenderProxyBox { bool container: false, bool checked, String label - }) : _container = container, + }) : assert(container != null), + _container = container, _checked = checked, _label = label, - super(child) { - assert(container != null); - } + super(child); /// If 'container' is true, this RenderObject will introduce a new /// node in the semantics tree. Otherwise, the semantics will be diff --git a/packages/flutter/lib/src/rendering/rotated_box.dart b/packages/flutter/lib/src/rendering/rotated_box.dart index c52d8222da0af..3db3641319ded 100644 --- a/packages/flutter/lib/src/rendering/rotated_box.dart +++ b/packages/flutter/lib/src/rendering/rotated_box.dart @@ -26,8 +26,8 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin _padding; @@ -192,9 +192,9 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox { RenderAligningShiftedBox({ FractionalOffset alignment: FractionalOffset.center, RenderBox child - }) : _alignment = alignment, super(child) { - assert(alignment != null && alignment.dx != null && alignment.dy != null); - } + }) : assert(alignment != null && alignment.dx != null && alignment.dy != null), + _alignment = alignment, + super(child); /// How to align the child. /// @@ -259,12 +259,11 @@ class RenderPositionedBox extends RenderAligningShiftedBox { double widthFactor, double heightFactor, FractionalOffset alignment: FractionalOffset.center - }) : _widthFactor = widthFactor, + }) : assert(widthFactor == null || widthFactor >= 0.0), + assert(heightFactor == null || heightFactor >= 0.0), + _widthFactor = widthFactor, _heightFactor = heightFactor, - super(child: child, alignment: alignment) { - assert(widthFactor == null || widthFactor >= 0.0); - assert(heightFactor == null || heightFactor >= 0.0); - } + super(child: child, alignment: alignment); /// If non-null, sets its width to the child's width multipled by this factor. /// @@ -499,10 +498,9 @@ class RenderSizedOverflowBox extends RenderAligningShiftedBox { RenderBox child, @required Size requestedSize, FractionalOffset alignment: FractionalOffset.center - }) : _requestedSize = requestedSize, - super(child: child, alignment: alignment) { - assert(requestedSize != null); - } + }) : assert(requestedSize != null), + _requestedSize = requestedSize, + super(child: child, alignment: alignment); /// The size this render box should attempt to be. Size get requestedSize => _requestedSize; @@ -791,9 +789,9 @@ class RenderCustomSingleChildLayoutBox extends RenderShiftedBox { RenderCustomSingleChildLayoutBox({ RenderBox child, @required SingleChildLayoutDelegate delegate - }) : _delegate = delegate, super(child) { - assert(delegate != null); - } + }) : assert(delegate != null), + _delegate = delegate, + super(child); /// A delegate that controls this object's layout. SingleChildLayoutDelegate get delegate => _delegate; @@ -901,12 +899,11 @@ class RenderBaseline extends RenderShiftedBox { RenderBox child, @required double baseline, @required TextBaseline baselineType - }) : _baseline = baseline, + }) : assert(baseline != null), + assert(baselineType != null), + _baseline = baseline, _baselineType = baselineType, - super(child) { - assert(baseline != null); - assert(baselineType != null); - } + super(child); /// The number of logical pixels from the top of this box at which to position /// the child's baseline. diff --git a/packages/flutter/lib/src/rendering/sliver_fill.dart b/packages/flutter/lib/src/rendering/sliver_fill.dart index dd3031e71c724..1b3c689714be9 100644 --- a/packages/flutter/lib/src/rendering/sliver_fill.dart +++ b/packages/flutter/lib/src/rendering/sliver_fill.dart @@ -34,10 +34,10 @@ class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor { RenderSliverFillViewport({ @required RenderSliverBoxChildManager childManager, double viewportFraction: 1.0, - }) : _viewportFraction = viewportFraction, super(childManager: childManager) { - assert(viewportFraction != null); - assert(viewportFraction > 0.0); - } + }) : assert(viewportFraction != null), + assert(viewportFraction > 0.0), + _viewportFraction = viewportFraction, + super(childManager: childManager); @override double get itemExtent => constraints.viewportMainAxisExtent * viewportFraction; diff --git a/packages/flutter/lib/src/rendering/sliver_grid.dart b/packages/flutter/lib/src/rendering/sliver_grid.dart index 039ca12b70c2b..259bc9e47ae40 100644 --- a/packages/flutter/lib/src/rendering/sliver_grid.dart +++ b/packages/flutter/lib/src/rendering/sliver_grid.dart @@ -456,10 +456,9 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { RenderSliverGrid({ @required RenderSliverBoxChildManager childManager, @required SliverGridDelegate gridDelegate, - }) : _gridDelegate = gridDelegate, - super(childManager: childManager) { - assert(gridDelegate != null); - } + }) : assert(gridDelegate != null), + _gridDelegate = gridDelegate, + super(childManager: childManager); @override void setupParentData(RenderObject child) { diff --git a/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart b/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart index 642f04b685d6e..09a791ee5c1a0 100644 --- a/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart +++ b/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart @@ -149,9 +149,8 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver /// The [childManager] argument must not be null. RenderSliverMultiBoxAdaptor({ @required RenderSliverBoxChildManager childManager - }) : _childManager = childManager { - assert(childManager != null); - } + }) : assert(childManager != null), + _childManager = childManager; @override void setupParentData(RenderObject child) { diff --git a/packages/flutter/lib/src/rendering/sliver_padding.dart b/packages/flutter/lib/src/rendering/sliver_padding.dart index 00ce8923e656e..c602a59f28a2f 100644 --- a/packages/flutter/lib/src/rendering/sliver_padding.dart +++ b/packages/flutter/lib/src/rendering/sliver_padding.dart @@ -31,9 +31,9 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin> children - }) { - assert(columns == null || columns >= 0); - assert(rows == null || rows >= 0); - assert(rows == null || children == null); - assert(defaultColumnWidth != null); - assert(configuration != null); + }) : assert(columns == null || columns >= 0), + assert(rows == null || rows >= 0), + assert(rows == null || children == null), + assert(defaultColumnWidth != null), + assert(configuration != null) { _columns = columns ?? (children != null && children.isNotEmpty ? children.first.length : 0); _rows = rows ?? 0; _children = []..length = _columns * _rows; diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 37461eacc743e..96eb971ddc52a 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -80,11 +80,10 @@ abstract class RenderViewportBase children, RenderSliver center, - }) : _anchor = anchor, + }) : assert(anchor != null), + assert(anchor >= 0.0 && anchor <= 1.0), + _anchor = anchor, _center = center, super(axisDirection: axisDirection, offset: offset) { - assert(anchor != null); - assert(anchor >= 0.0 && anchor <= 1.0); addAll(children); if (center == null && firstChild != null) _center = firstChild; diff --git a/packages/flutter/lib/src/rendering/wrap.dart b/packages/flutter/lib/src/rendering/wrap.dart index 7b1e8c51d919e..f1ec7b178e09b 100644 --- a/packages/flutter/lib/src/rendering/wrap.dart +++ b/packages/flutter/lib/src/rendering/wrap.dart @@ -91,18 +91,18 @@ class RenderWrap extends RenderBox with ContainerRenderObjectMixin Date: Mon, 5 Jun 2017 09:28:51 +0200 Subject: [PATCH 028/110] apply prefer_asserts_in_initializer_list lint (#10482) --- .../flutter/lib/src/foundation/serialization.dart | 5 ++--- packages/flutter/lib/src/material/material.dart | 9 ++++++--- .../flutter/lib/src/painting/box_painter.dart | 6 +++--- .../flutter/lib/src/painting/flutter_logo.dart | 7 ++++--- .../flutter/lib/src/painting/text_painter.dart | 11 +++++++---- .../lib/src/physics/clamped_simulation.dart | 8 +++----- .../lib/src/physics/friction_simulation.dart | 5 ++--- .../lib/src/physics/gravity_simulation.dart | 15 +++++++-------- .../flutter/lib/src/services/image_stream.dart | 4 ++-- .../flutter/lib/src/services/message_codec.dart | 9 +++------ packages/flutter/lib/src/services/text_input.dart | 6 +++--- 11 files changed, 42 insertions(+), 43 deletions(-) diff --git a/packages/flutter/lib/src/foundation/serialization.dart b/packages/flutter/lib/src/foundation/serialization.dart index 753de146e704d..822d916aa0184 100644 --- a/packages/flutter/lib/src/foundation/serialization.dart +++ b/packages/flutter/lib/src/foundation/serialization.dart @@ -103,9 +103,8 @@ class WriteBuffer { /// The byte order used is [Endianness.HOST_ENDIAN] throughout. class ReadBuffer { /// Creates a [ReadBuffer] for reading from the specified [data]. - ReadBuffer(this.data) { - assert(data != null); - } + ReadBuffer(this.data) + : assert(data != null); /// The underlying data being read. final ByteData data; diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index a03aa5ff6ee6e..fc92a7e14255c 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -275,9 +275,12 @@ class _MaterialState extends State with TickerProviderStateMixin { const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200); class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { - _RenderInkFeatures({ RenderBox child, @required this.vsync, this.color }) : super(child) { - assert(vsync != null); - } + _RenderInkFeatures({ + RenderBox child, + @required this.vsync, + this.color, + }) : assert(vsync != null), + super(child); // This class should exist in a 1:1 relationship with a MaterialState object, // since there's no current support for dynamically changing the ticker diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index 4600841df914b..d176a87ad4daa 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -1659,9 +1659,9 @@ class BoxDecoration extends Decoration { /// An object that paints a [BoxDecoration] into a canvas. class _BoxDecorationPainter extends BoxPainter { - _BoxDecorationPainter(@required this._decoration, VoidCallback onChange) : super(onChange) { - assert(_decoration != null); - } + _BoxDecorationPainter(@required this._decoration, VoidCallback onChange) + : assert(_decoration != null), + super(onChange); final BoxDecoration _decoration; diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index 97ba120a0fb3f..3652febb51ca2 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -219,9 +219,10 @@ class FlutterLogoDecoration extends Decoration { /// An object that paints a [BoxDecoration] into a canvas. class _FlutterLogoPainter extends BoxPainter { - _FlutterLogoPainter(this._config) : super(null) { - assert(_config != null); - assert(_config.debugAssertIsValid()); + _FlutterLogoPainter(this._config) + : assert(_config != null), + assert(_config.debugAssertIsValid()), + super(null) { _prepareText(); } diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index a3e77426e42c8..8c7e458f6c4eb 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -41,10 +41,13 @@ class TextPainter { double textScaleFactor: 1.0, int maxLines, String ellipsis, - }) : _text = text, _textAlign = textAlign, _textScaleFactor = textScaleFactor, _maxLines = maxLines, _ellipsis = ellipsis { - assert(text == null || text.debugAssertIsValid()); - assert(textScaleFactor != null); - } + }) : assert(text == null || text.debugAssertIsValid()), + assert(textScaleFactor != null), + _text = text, + _textAlign = textAlign, + _textScaleFactor = textScaleFactor, + _maxLines = maxLines, + _ellipsis = ellipsis; ui.Paragraph _paragraph; bool _needsLayout = true; diff --git a/packages/flutter/lib/src/physics/clamped_simulation.dart b/packages/flutter/lib/src/physics/clamped_simulation.dart index 82fe080310fe1..854936f5ccbb7 100644 --- a/packages/flutter/lib/src/physics/clamped_simulation.dart +++ b/packages/flutter/lib/src/physics/clamped_simulation.dart @@ -26,11 +26,9 @@ class ClampedSimulation extends Simulation { this.xMax: double.INFINITY, this.dxMin: double.NEGATIVE_INFINITY, this.dxMax: double.INFINITY - }) { - assert(simulation != null); - assert(xMax >= xMin); - assert(dxMax >= dxMin); - } + }) : assert(simulation != null), + assert(xMax >= xMin), + assert(dxMax >= dxMin); /// The simulation being clamped. Calls to [x], [dx], and [isDone] are /// forwarded to the simulation. diff --git a/packages/flutter/lib/src/physics/friction_simulation.dart b/packages/flutter/lib/src/physics/friction_simulation.dart index 10cf766e22c10..8fa5cf18d8f66 100644 --- a/packages/flutter/lib/src/physics/friction_simulation.dart +++ b/packages/flutter/lib/src/physics/friction_simulation.dart @@ -105,9 +105,8 @@ class BoundedFrictionSimulation extends FrictionSimulation { double velocity, this._minX, this._maxX - ) : super(drag, position, velocity) { - assert(position.clamp(_minX, _maxX) == position); - } + ) : assert(position.clamp(_minX, _maxX) == position), + super(drag, position, velocity); final double _minX; final double _maxX; diff --git a/packages/flutter/lib/src/physics/gravity_simulation.dart b/packages/flutter/lib/src/physics/gravity_simulation.dart index b75eb93bded24..5e75c4b4aa881 100644 --- a/packages/flutter/lib/src/physics/gravity_simulation.dart +++ b/packages/flutter/lib/src/physics/gravity_simulation.dart @@ -28,16 +28,15 @@ class GravitySimulation extends Simulation { double distance, double endDistance, double velocity - ) : _a = acceleration, + ) : assert(acceleration != null), + assert(distance != null), + assert(velocity != null), + assert(endDistance != null), + assert(endDistance >= 0), + _a = acceleration, _x = distance, _v = velocity, - _end = endDistance { - assert(acceleration != null); - assert(distance != null); - assert(velocity != null); - assert(endDistance != null); - assert(endDistance >= 0); - } + _end = endDistance; final double _x; final double _v; diff --git a/packages/flutter/lib/src/services/image_stream.dart b/packages/flutter/lib/src/services/image_stream.dart index baa013e04617c..f6a1ca57253a7 100644 --- a/packages/flutter/lib/src/services/image_stream.dart +++ b/packages/flutter/lib/src/services/image_stream.dart @@ -237,8 +237,8 @@ class OneFrameImageStreamCompleter extends ImageStreamCompleter { /// argument on [FlutterErrorDetails] set to true, meaning that by default the /// message is only dumped to the console in debug mode (see [new /// FlutterErrorDetails]). - OneFrameImageStreamCompleter(Future image, { InformationCollector informationCollector }) { - assert(image != null); + OneFrameImageStreamCompleter(Future image, { InformationCollector informationCollector }) + : assert(image != null) { image.then(setImage, onError: (dynamic error, StackTrace stack) { FlutterError.reportError(new FlutterErrorDetails( exception: error, diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index dec6db4c5c85d..22691f44e11f5 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -35,9 +35,8 @@ abstract class MessageCodec { class MethodCall { /// Creates a [MethodCall] representing the invocation of [method] with the /// specified [arguments]. - MethodCall(this.method, [this.arguments]) { - assert(method != null); - } + MethodCall(this.method, [this.arguments]) + : assert(method != null); /// The name of the method to be called. final String method; @@ -148,9 +147,7 @@ class PlatformException implements Exception { @required this.code, this.message, this.details, - }) { - assert(code != null); - } + }) : assert(code != null); /// An error code. final String code; diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index e1c4ca87554f4..a982d9bd80e15 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -224,9 +224,9 @@ abstract class TextInputClient { /// /// * [TextInput.attach] class TextInputConnection { - TextInputConnection._(this._client) : _id = _nextId++ { - assert(_client != null); - } + TextInputConnection._(this._client) + : assert(_client != null), + _id = _nextId++; static int _nextId = 1; final int _id; From c63be2af8bb79e70e93750ae4ade89ff35e29fa7 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Mon, 5 Jun 2017 09:33:38 +0200 Subject: [PATCH 029/110] apply prefer_asserts_in_initializer_list lint (#10483) --- .../src/animation/animation_controller.dart | 30 +++++++++--------- .../flutter/lib/src/animation/animations.dart | 20 +++++------- packages/flutter/lib/src/animation/tween.dart | 5 ++- .../lib/src/cupertino/bottom_tab_bar.dart | 11 +++---- packages/flutter/lib/src/cupertino/page.dart | 31 +++++++++---------- .../flutter/lib/src/cupertino/slider.dart | 4 +-- .../flutter/lib/src/cupertino/switch.dart | 16 +++++----- .../lib/src/gestures/drag_details.dart | 30 +++++++----------- .../flutter/lib/src/gestures/lsq_solver.dart | 7 ++--- .../flutter/lib/src/gestures/multidrag.dart | 14 ++++----- packages/flutter/lib/src/gestures/scale.dart | 19 ++++++------ packages/flutter/lib/src/gestures/tap.dart | 10 +++--- 12 files changed, 88 insertions(+), 109 deletions(-) diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index 9619ed4faf9c2..6e04b18326dc6 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -115,12 +115,11 @@ class AnimationController extends Animation this.lowerBound: 0.0, this.upperBound: 1.0, @required TickerProvider vsync, - }) { - assert(lowerBound != null); - assert(upperBound != null); - assert(upperBound >= lowerBound); - assert(vsync != null); - _direction = _AnimationDirection.forward; + }) : assert(lowerBound != null), + assert(upperBound != null), + assert(upperBound >= lowerBound), + assert(vsync != null), + _direction = _AnimationDirection.forward { _ticker = vsync.createTicker(_tick); _internalSetValue(value ?? lowerBound); } @@ -146,11 +145,11 @@ class AnimationController extends Animation this.duration, this.debugLabel, @required TickerProvider vsync, - }) : lowerBound = double.NEGATIVE_INFINITY, - upperBound = double.INFINITY { - assert(value != null); - assert(vsync != null); - _direction = _AnimationDirection.forward; + }) : assert(value != null), + assert(vsync != null), + lowerBound = double.NEGATIVE_INFINITY, + upperBound = double.INFINITY, + _direction = _AnimationDirection.forward { _ticker = vsync.createTicker(_tick); _internalSetValue(value); } @@ -496,11 +495,10 @@ class AnimationController extends Animation class _InterpolationSimulation extends Simulation { _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve) - : _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND { - assert(_durationInSeconds > 0.0); - assert(_begin != null); - assert(_end != null); - } + : assert(_begin != null), + assert(_end != null), + assert(duration != null && duration.inMicroseconds > 0), + _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND; final double _durationInSeconds; final double _begin; diff --git a/packages/flutter/lib/src/animation/animations.dart b/packages/flutter/lib/src/animation/animations.dart index 9401108c9208a..b763b792cdc6a 100644 --- a/packages/flutter/lib/src/animation/animations.dart +++ b/packages/flutter/lib/src/animation/animations.dart @@ -247,9 +247,8 @@ class ReverseAnimation extends Animation /// Creates a reverse animation. /// /// The parent argument must not be null. - ReverseAnimation(this.parent) { - assert(parent != null); - } + ReverseAnimation(this.parent) + : assert(parent != null); /// The animation whose value and direction this animation is reversing. final Animation parent; @@ -331,9 +330,8 @@ class CurvedAnimation extends Animation with AnimationWithParentMixin /// /// The current train argument must not be null but the next train argument /// can be null. - TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain }) { - assert(_currentTrain != null); + TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain }) + : assert(_currentTrain != null) { if (_nextTrain != null) { if (_currentTrain.value > _nextTrain.value) { _mode = _TrainHoppingMode.maximize; @@ -552,10 +550,8 @@ abstract class CompoundAnimation extends Animation CompoundAnimation({ @required this.first, @required this.next, - }) { - assert(first != null); - assert(next != null); - } + }) : assert(first != null), + assert(next != null); /// The first sub-animation. Its status takes precedence if neither are /// animating. diff --git a/packages/flutter/lib/src/animation/tween.dart b/packages/flutter/lib/src/animation/tween.dart index e993a003db2dd..1db4175c6ac10 100644 --- a/packages/flutter/lib/src/animation/tween.dart +++ b/packages/flutter/lib/src/animation/tween.dart @@ -291,9 +291,8 @@ class CurveTween extends Animatable { /// Creates a curve tween. /// /// The [curve] argument must not be null. - CurveTween({ @required this.curve }) { - assert(curve != null); - } + CurveTween({ @required this.curve }) + : assert(curve != null); /// The curve to use when transforming the value of the animation. Curve curve; diff --git a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart index cd4cc55f9872d..59472f35320a4 100644 --- a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart +++ b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart @@ -41,12 +41,11 @@ class CupertinoTabBar extends StatelessWidget { this.activeColor: CupertinoColors.activeBlue, this.inactiveColor: CupertinoColors.inactiveGray, this.iconSize: 24.0, - }) : super(key: key) { - assert(items != null); - assert(items.length >= 2); - assert(0 <= currentIndex && currentIndex < items.length); - assert(iconSize != null); - } + }) : assert(items != null), + assert(items.length >= 2), + assert(0 <= currentIndex && currentIndex < items.length), + assert(iconSize != null), + super(key: key); /// The interactive items laid out within the bottom navigation bar. final List items; diff --git a/packages/flutter/lib/src/cupertino/page.dart b/packages/flutter/lib/src/cupertino/page.dart index 59c27d347a641..a25ef52827c15 100644 --- a/packages/flutter/lib/src/cupertino/page.dart +++ b/packages/flutter/lib/src/cupertino/page.dart @@ -25,7 +25,7 @@ final FractionalOffsetTween _kBottomUpTween = new FractionalOffsetTween( end: FractionalOffset.topLeft, ); -// Custom decoration from no shadow to page shadow mimicking iOS page +// Custom decoration from no shadow to page shadow mimicking iOS page // transitions using gradients. final DecorationTween _kGradientShadowTween = new DecorationTween( begin: _CupertinoEdgeShadowDecoration.none, // No decoration initially. @@ -36,27 +36,27 @@ final DecorationTween _kGradientShadowTween = new DecorationTween( end: FractionalOffset.topRight, // Eyeballed gradient used to mimic a drop shadow on the left side only. colors: const [ - const Color(0x00000000), + const Color(0x00000000), const Color(0x04000000), const Color(0x12000000), const Color(0x38000000) ], stops: const [0.0, 0.3, 0.6, 1.0], - ), + ), ), ); -/// A custom [Decoration] used to paint an extra shadow on the left edge of the -/// box it's decorating. It's like a [BoxDecoration] with only a gradient except +/// A custom [Decoration] used to paint an extra shadow on the left edge of the +/// box it's decorating. It's like a [BoxDecoration] with only a gradient except /// it paints to the left of the box instead of behind the box. class _CupertinoEdgeShadowDecoration extends Decoration { const _CupertinoEdgeShadowDecoration({ this.edgeGradient }); /// A Decoration with no decorating properties. - static const _CupertinoEdgeShadowDecoration none = + static const _CupertinoEdgeShadowDecoration none = const _CupertinoEdgeShadowDecoration(); - /// A gradient to draw to the left of the box being decorated. + /// A gradient to draw to the left of the box being decorated. /// FractionalOffsets are relative to the original box translated one box /// width to the left. final LinearGradient edgeGradient; @@ -65,8 +65,8 @@ class _CupertinoEdgeShadowDecoration extends Decoration { /// /// See also [Decoration.lerp]. static _CupertinoEdgeShadowDecoration lerp( - _CupertinoEdgeShadowDecoration a, - _CupertinoEdgeShadowDecoration b, + _CupertinoEdgeShadowDecoration a, + _CupertinoEdgeShadowDecoration b, double t ) { if (a == null && b == null) @@ -89,7 +89,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration { return _CupertinoEdgeShadowDecoration.lerp(this, null, t); return _CupertinoEdgeShadowDecoration.lerp(this, b, t); } - + @override _CupertinoEdgeShadowPainter createBoxPainter([VoidCallback onChanged]) { return new _CupertinoEdgeShadowPainter(this, onChanged); @@ -114,7 +114,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration { /// A [BoxPainter] used to draw the page transition shadow using gradients. class _CupertinoEdgeShadowPainter extends BoxPainter { _CupertinoEdgeShadowPainter( - @required this._decoration, + @required this._decoration, VoidCallback onChange ) : assert(_decoration != null), super(onChange); @@ -126,9 +126,9 @@ class _CupertinoEdgeShadowPainter extends BoxPainter { final LinearGradient gradient = _decoration.edgeGradient; if (gradient == null) return; - // The drawable space for the gradient is a rect with the same size as + // The drawable space for the gradient is a rect with the same size as // its parent box one box width to the left of the box. - final Rect rect = + final Rect rect = (offset & configuration.size).translate(-configuration.size.width, 0.0); final Paint paint = new Paint() ..shader = gradient.createShader(rect); @@ -249,9 +249,8 @@ class CupertinoBackGestureController extends NavigationGestureController { CupertinoBackGestureController({ @required NavigatorState navigator, @required this.controller, - }) : super(navigator) { - assert(controller != null); - } + }) : assert(controller != null), + super(navigator); /// The animation controller that the route uses to drive its transition /// animation. diff --git a/packages/flutter/lib/src/cupertino/slider.dart b/packages/flutter/lib/src/cupertino/slider.dart index e23ea3ab33075..8ed550ad23b19 100644 --- a/packages/flutter/lib/src/cupertino/slider.dart +++ b/packages/flutter/lib/src/cupertino/slider.dart @@ -192,11 +192,11 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc Color activeColor, this.onChanged, TickerProvider vsync, - }) : _value = value, + }) : assert(value != null && value >= 0.0 && value <= 1.0), + _value = value, _divisions = divisions, _activeColor = activeColor, super(additionalConstraints: const BoxConstraints.tightFor(width: _kSliderWidth, height: _kSliderHeight)) { - assert(value != null && value >= 0.0 && value <= 1.0); _drag = new HorizontalDragGestureRecognizer() ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 28d1da9339f8f..8c91132ea8a6f 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -141,14 +141,14 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc @required Color activeColor, ValueChanged onChanged, @required TickerProvider vsync, - }) : _value = value, - _activeColor = activeColor, - _onChanged = onChanged, - _vsync = vsync, - super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) { - assert(value != null); - assert(activeColor != null); - assert(vsync != null); + }) : assert(value != null), + assert(activeColor != null), + assert(vsync != null), + _value = value, + _activeColor = activeColor, + _onChanged = onChanged, + _vsync = vsync, + super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) { _tap = new TapGestureRecognizer() ..onTapDown = _handleTapDown ..onTap = _handleTap diff --git a/packages/flutter/lib/src/gestures/drag_details.dart b/packages/flutter/lib/src/gestures/drag_details.dart index 9cdfdb0131be6..7cd2ef2ce36f1 100644 --- a/packages/flutter/lib/src/gestures/drag_details.dart +++ b/packages/flutter/lib/src/gestures/drag_details.dart @@ -20,9 +20,8 @@ class DragDownDetails { /// Creates details for a [GestureDragDownCallback]. /// /// The [globalPosition] argument must not be null. - DragDownDetails({ this.globalPosition: Offset.zero }) { - assert(globalPosition != null); - } + DragDownDetails({ this.globalPosition: Offset.zero }) + : assert(globalPosition != null); /// The global position at which the pointer contacted the screen. /// @@ -53,9 +52,8 @@ class DragStartDetails { /// Creates details for a [GestureDragStartCallback]. /// /// The [globalPosition] argument must not be null. - DragStartDetails({ this.globalPosition: Offset.zero }) { - assert(globalPosition != null); - } + DragStartDetails({ this.globalPosition: Offset.zero }) + : assert(globalPosition != null); /// The global position at which the pointer contacted the screen. /// @@ -99,12 +97,10 @@ class DragUpdateDetails { this.delta: Offset.zero, this.primaryDelta, @required this.globalPosition - }) { - assert(delta != null); - assert(primaryDelta == null - || (primaryDelta == delta.dx && delta.dy == 0.0) - || (primaryDelta == delta.dy && delta.dx == 0.0)); - } + }) : assert(delta != null), + assert(primaryDelta == null + || (primaryDelta == delta.dx && delta.dy == 0.0) + || (primaryDelta == delta.dy && delta.dx == 0.0)); /// The amount the pointer has moved since the previous update. /// @@ -158,12 +154,10 @@ class DragEndDetails { DragEndDetails({ this.velocity: Velocity.zero, this.primaryVelocity, - }) { - assert(velocity != null); - assert(primaryVelocity == null - || primaryVelocity == velocity.pixelsPerSecond.dx - || primaryVelocity == velocity.pixelsPerSecond.dy); - } + }) : assert(velocity != null), + assert(primaryVelocity == null + || primaryVelocity == velocity.pixelsPerSecond.dx + || primaryVelocity == velocity.pixelsPerSecond.dy); /// The velocity the pointer was moving when it stopped contacting the screen. /// diff --git a/packages/flutter/lib/src/gestures/lsq_solver.dart b/packages/flutter/lib/src/gestures/lsq_solver.dart index 71ffe7af77196..904263b4397db 100644 --- a/packages/flutter/lib/src/gestures/lsq_solver.dart +++ b/packages/flutter/lib/src/gestures/lsq_solver.dart @@ -75,10 +75,9 @@ class LeastSquaresSolver { /// Creates a least-squares solver. /// /// The [x], [y], and [w] arguments must not be null. - LeastSquaresSolver(this.x, this.y, this.w) { - assert(x.length == y.length); - assert(y.length == w.length); - } + LeastSquaresSolver(this.x, this.y, this.w) + : assert(x.length == y.length), + assert(y.length == w.length); /// The x-coordinates of each data point. final List x; diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index d40b81e673356..57f2ac0949501 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -27,9 +27,8 @@ abstract class MultiDragPointerState { /// Creates per-pointer state for a [MultiDragGestureRecognizer]. /// /// The [initialPosition] argument must not be null. - MultiDragPointerState(this.initialPosition) { - assert(initialPosition != null); - } + MultiDragPointerState(this.initialPosition) + : assert(initialPosition != null); /// The global coordinates of the pointer when the pointer contacted the screen. final Offset initialPosition; @@ -396,8 +395,9 @@ class VerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Ver } class _DelayedPointerState extends MultiDragPointerState { - _DelayedPointerState(Offset initialPosition, Duration delay) : super(initialPosition) { - assert(delay != null); + _DelayedPointerState(Offset initialPosition, Duration delay) + : assert(delay != null), + super(initialPosition) { _timer = new Timer(delay, _delayPassed); } @@ -481,9 +481,7 @@ class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Dela /// can be changed for specific behaviors. DelayedMultiDragGestureRecognizer({ this.delay: kLongPressTimeout - }) { - assert(delay != null); - } + }) : assert(delay != null); /// The amount of time the pointer must remain in the same place for the drag /// to be recognized. diff --git a/packages/flutter/lib/src/gestures/scale.dart b/packages/flutter/lib/src/gestures/scale.dart index 2c459a0647c30..5540cd11e4842 100644 --- a/packages/flutter/lib/src/gestures/scale.dart +++ b/packages/flutter/lib/src/gestures/scale.dart @@ -32,9 +32,8 @@ class ScaleStartDetails { /// Creates details for [GestureScaleStartCallback]. /// /// The [focalPoint] argument must not be null. - ScaleStartDetails({ this.focalPoint: Offset.zero }) { - assert(focalPoint != null); - } + ScaleStartDetails({ this.focalPoint: Offset.zero }) + : assert(focalPoint != null); /// The initial focal point of the pointers in contact with the screen. /// Reported in global coordinates. @@ -50,10 +49,11 @@ class ScaleUpdateDetails { /// /// The [focalPoint] and [scale] arguments must not be null. The [scale] /// argument must be greater than or equal to zero. - ScaleUpdateDetails({ this.focalPoint: Offset.zero, this.scale: 1.0 }) { - assert(focalPoint != null); - assert(scale != null && scale >= 0.0); - } + ScaleUpdateDetails({ + this.focalPoint: Offset.zero, + this.scale: 1.0, + }) : assert(focalPoint != null), + assert(scale != null && scale >= 0.0); /// The focal point of the pointers in contact with the screen. Reported in /// global coordinates. @@ -72,9 +72,8 @@ class ScaleEndDetails { /// Creates details for [GestureScaleEndCallback]. /// /// The [velocity] argument must not be null. - ScaleEndDetails({ this.velocity: Velocity.zero }) { - assert(velocity != null); - } + ScaleEndDetails({ this.velocity: Velocity.zero }) + : assert(velocity != null); /// The velocity of the last pointer to be lifted off of the screen. final Velocity velocity; diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index 97fbf79f52a3a..bbe170ae39b4f 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -12,9 +12,8 @@ class TapDownDetails { /// Creates details for a [GestureTapDownCallback]. /// /// The [globalPosition] argument must not be null. - TapDownDetails({ this.globalPosition: Offset.zero }) { - assert(globalPosition != null); - } + TapDownDetails({ this.globalPosition: Offset.zero }) + : assert(globalPosition != null); /// The global position at which the pointer contacted the screen. final Offset globalPosition; @@ -32,9 +31,8 @@ class TapUpDetails { /// Creates details for a [GestureTapUpCallback]. /// /// The [globalPosition] argument must not be null. - TapUpDetails({ this.globalPosition: Offset.zero }) { - assert(globalPosition != null); - } + TapUpDetails({ this.globalPosition: Offset.zero }) + : assert(globalPosition != null); /// The global position at which the pointer contacted the screen. final Offset globalPosition; From d65b9fb6dc45f0f9f74be08a1dbf12eb33234ea2 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Mon, 5 Jun 2017 09:42:45 -0700 Subject: [PATCH 030/110] Update tracking bug for SDK spaces in doctor (#10504) --- packages/flutter_tools/lib/src/doctor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index a4b7e48e7c8de..d01e42f8b4288 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -212,7 +212,7 @@ class _FlutterValidator extends DoctorValidator { messages.add(new ValidationMessage('Flutter at ${Cache.flutterRoot}')); if (Cache.flutterRoot.contains(' ')) messages.add(new ValidationMessage.error( - 'Flutter SDK install paths with spaces are not yet supported. (https://github.com/flutter/flutter/issues/10461)\n' + 'Flutter SDK install paths with spaces are not yet supported. (https://github.com/flutter/flutter/issues/6577)\n' 'Please move the SDK to a path that does not include spaces.')); messages.add(new ValidationMessage( 'Framework revision ${version.frameworkRevisionShort} ' From dd6aab2e07cda4e5fe566d5d7fa60415847f6573 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 5 Jun 2017 09:50:37 -0700 Subject: [PATCH 031/110] Add a Column to the template to address some of what we learnt from usability studies. (#10473) --- .../templates/create/lib/main.dart.tmpl | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/flutter_tools/templates/create/lib/main.dart.tmpl b/packages/flutter_tools/templates/create/lib/main.dart.tmpl index 3687440761129..0038a09c43981 100644 --- a/packages/flutter_tools/templates/create/lib/main.dart.tmpl +++ b/packages/flutter_tools/templates/create/lib/main.dart.tmpl @@ -29,9 +29,9 @@ class MyApp extends StatelessWidget { // the application has a blue toolbar. Then, without quitting // the app, try changing the primarySwatch below to Colors.green // and then invoke "hot reload" (press "r" in the console where - // you ran "flutter run", or press Run > Hot Reload App in IntelliJ). - // Notice that the counter didn't reset back to zero -- the application - // is not restarted. + // you ran "flutter run", or press Run > Hot Reload App in + // IntelliJ). Notice that the counter didn't reset back to zero; + // the application is not restarted. primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), @@ -89,8 +89,34 @@ class _MyHomePageState extends State { title: new Text(widget.title), ), body: new Center( - child: new Text( - 'Button tapped $_counter time${ _counter == 1 ? '' : 's' }.', + // Center is a layout widget. It takes a single child and + // positions it in the middle of the parent. + child: new Column( + // Column is also layout widget. It takes a list of children + // and arranges them vertically. By default, it sizes itself + // to fit its children horizontally, and tries to be as tall + // as its parent. + // + // Invoke "debug paint" (press "p" in the console where you + // ran "flutter run", or select "Toggle Debug Paint" from the + // Flutter tool window in IntelliJ) to see the wireframe for + // each widget. + // + // Column has various properties to control how it sizes + // itself and how it positions its children. Here we use + // mainAxisAlignment to center the children vertically; the + // main axis here is the vertical axis because Columns are + // vertical (the cross axis would be horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Text( + 'You have pushed the button this many times:', + ), + new Text( + '${_counter}', + style: Theme.of(context).textTheme.display1, + ), + ], ), ), floatingActionButton: new FloatingActionButton( @@ -119,7 +145,7 @@ class _MyHomePageState extends State { try { platformVersion = await {{pluginDartClass}}.platformVersion; } on PlatformException { - platformVersion = "Failed to get platform version"; + platformVersion = 'Failed to get platform version.'; } // If the widget was removed from the tree while the asynchronous platform From 078b380d5168c83628e79ace27125441e2f7fbaf Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Mon, 5 Jun 2017 11:25:46 -0700 Subject: [PATCH 032/110] Remove underscores from Android identifier during `flutter create` (#10506) Fixes #10077 --- packages/flutter_tools/lib/src/commands/create.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 34b39153dd86a..cf8c3ec1bf2dc 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -275,7 +275,7 @@ To edit platform code in an IDE see https://flutter.io/platform-plugins/#edit-co } String _createAndroidIdentifier(String organization, String name) { - return '$organization.$name'; + return '$organization.$name'.replaceAll('_', ''); } String _createPluginClassName(String name) { From d98d09d478efb668d2d8c7f9c91b2ce6d7b6aee1 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Mon, 5 Jun 2017 21:18:43 +0200 Subject: [PATCH 033/110] remove unnecessary @required (#10501) --- packages/flutter/lib/src/cupertino/page.dart | 2 +- packages/flutter/lib/src/painting/box_painter.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/page.dart b/packages/flutter/lib/src/cupertino/page.dart index a25ef52827c15..2e5ab53a6ac25 100644 --- a/packages/flutter/lib/src/cupertino/page.dart +++ b/packages/flutter/lib/src/cupertino/page.dart @@ -114,7 +114,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration { /// A [BoxPainter] used to draw the page transition shadow using gradients. class _CupertinoEdgeShadowPainter extends BoxPainter { _CupertinoEdgeShadowPainter( - @required this._decoration, + this._decoration, VoidCallback onChange ) : assert(_decoration != null), super(onChange); diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index d176a87ad4daa..5b4a47aceb6f0 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -1659,7 +1659,7 @@ class BoxDecoration extends Decoration { /// An object that paints a [BoxDecoration] into a canvas. class _BoxDecorationPainter extends BoxPainter { - _BoxDecorationPainter(@required this._decoration, VoidCallback onChange) + _BoxDecorationPainter(this._decoration, VoidCallback onChange) : assert(_decoration != null), super(onChange); From 1b29312ad271921b5ddd7012f7f37a566890df78 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Mon, 5 Jun 2017 12:53:27 -0700 Subject: [PATCH 034/110] Upload sample catalog screenshots to cloud storage (#10462) --- .../android_sample_catalog_generator.dart | 5 +- .../tasks/ios_sample_catalog_generator.dart | 5 +- dev/devicelab/lib/framework/utils.dart | 21 +++ .../lib/tasks/sample_catalog_generator.dart | 8 +- examples/catalog/animated_list.iml | 16 --- examples/catalog/bin/sample_page.dart | 15 +- examples/catalog/bin/save_screenshots.dart | 135 ++++++++++++++++++ .../catalog/bin/screenshot_test.dart.template | 2 +- examples/catalog/pubspec.yaml | 6 +- 9 files changed, 180 insertions(+), 33 deletions(-) delete mode 100644 examples/catalog/animated_list.iml create mode 100644 examples/catalog/bin/save_screenshots.dart diff --git a/dev/devicelab/bin/tasks/android_sample_catalog_generator.dart b/dev/devicelab/bin/tasks/android_sample_catalog_generator.dart index b9550b36c6447..0fa9d4fb26bb3 100644 --- a/dev/devicelab/bin/tasks/android_sample_catalog_generator.dart +++ b/dev/devicelab/bin/tasks/android_sample_catalog_generator.dart @@ -6,9 +6,10 @@ import 'dart:async'; import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/tasks/sample_catalog_generator.dart'; -Future main() async { +Future main(List args) async { deviceOperatingSystem = DeviceOperatingSystem.android; - await task(samplePageCatalogGenerator); + await task(() => samplePageCatalogGenerator(extractCloudAuthTokenArg(args))); } diff --git a/dev/devicelab/bin/tasks/ios_sample_catalog_generator.dart b/dev/devicelab/bin/tasks/ios_sample_catalog_generator.dart index 94f14641c1935..20efb83307cdb 100644 --- a/dev/devicelab/bin/tasks/ios_sample_catalog_generator.dart +++ b/dev/devicelab/bin/tasks/ios_sample_catalog_generator.dart @@ -6,9 +6,10 @@ import 'dart:async'; import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/tasks/sample_catalog_generator.dart'; -Future main() async { +Future main(List args) async { deviceOperatingSystem = DeviceOperatingSystem.ios; - await task(samplePageCatalogGenerator); + await task(() => samplePageCatalogGenerator(extractCloudAuthTokenArg(args))); } diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index 06de108b5f9b0..08fa44e7ae7cb 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:args/args.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'package:process/process.dart'; @@ -452,3 +453,23 @@ Future findAvailablePort() async { } bool canRun(String path) => _processManager.canRun(path); + +String extractCloudAuthTokenArg(List rawArgs) { + final ArgParser argParser = new ArgParser()..addOption('cloud-auth-token'); + ArgResults args; + try { + args = argParser.parse(rawArgs); + } on FormatException catch(error) { + stderr.writeln('${error.message}\n'); + stderr.writeln('Usage:\n'); + stderr.writeln(argParser.usage); + return null; + } + + final String token = args['cloud-auth-token']; + if (token == null) { + stderr.writeln('Required option --cloud-auth-token not found'); + return null; + } + return token; +} diff --git a/dev/devicelab/lib/tasks/sample_catalog_generator.dart b/dev/devicelab/lib/tasks/sample_catalog_generator.dart index ecd4ce78c6b20..8b005d11794a7 100644 --- a/dev/devicelab/lib/tasks/sample_catalog_generator.dart +++ b/dev/devicelab/lib/tasks/sample_catalog_generator.dart @@ -10,7 +10,7 @@ import '../framework/framework.dart'; import '../framework/ios.dart'; import '../framework/utils.dart'; -Future samplePageCatalogGenerator() async { +Future samplePageCatalogGenerator(String authorizationToken) async { final Device device = await devices.workingDevice; await device.unlock(); final String deviceId = device.deviceId; @@ -30,6 +30,12 @@ Future samplePageCatalogGenerator() async { '--device-id', deviceId, ]); + + await dart([ + 'bin/save_screenshots.dart', + await getCurrentFlutterRepoCommit(), + authorizationToken, + ]); }); return new TaskResult.success(null); diff --git a/examples/catalog/animated_list.iml b/examples/catalog/animated_list.iml deleted file mode 100644 index f2b096d125108..0000000000000 --- a/examples/catalog/animated_list.iml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/catalog/bin/sample_page.dart b/examples/catalog/bin/sample_page.dart index 2ebbb471e1f38..c9f1ac14cd9bd 100644 --- a/examples/catalog/bin/sample_page.dart +++ b/examples/catalog/bin/sample_page.dart @@ -7,11 +7,13 @@ import 'dart:io'; +import 'package:path/path.dart'; + class SampleError extends Error { SampleError(this.message); final String message; @override - String toString() => message; + String toString() => 'SampleError($message)'; } // Sample apps are .dart files in the lib directory which contain a block @@ -83,14 +85,7 @@ class SampleGenerator { // If sourceFile is lib/foo.dart then sourceName is foo. The sourceName // is used to create derived filenames like foo.md or foo.png. - String get sourceName { - // In /foo/bar/baz.dart, matches baz.dart, match[1] == 'baz' - final RegExp nameRE = new RegExp(r'(\w+)\.dart$'); - final Match nameMatch = nameRE.firstMatch(sourceFile.path); - if (nameMatch.groupCount != 1) - throw new SampleError('bad source file name ${sourceFile.path}'); - return nameMatch[1]; - } + String get sourceName => basenameWithoutExtension(sourceFile.path); // The name of the widget class that defines this sample app, like 'FooSample'. String get sampleClass => commentValues["sample"]; @@ -161,6 +156,8 @@ void generate() { } }); + // Causes the generated imports to appear in alphabetical order. + // Avoid complaints from flutter lint. samples.sort((SampleGenerator a, SampleGenerator b) { return a.sourceName.compareTo(b.sourceName); }); diff --git a/examples/catalog/bin/save_screenshots.dart b/examples/catalog/bin/save_screenshots.dart new file mode 100644 index 0000000000000..fd78f02c0784e --- /dev/null +++ b/examples/catalog/bin/save_screenshots.dart @@ -0,0 +1,135 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math' as math; + +import 'package:image/image.dart'; +import 'package:path/path.dart'; + +String authorizationToken; + +class UploadError extends Error { + UploadError(this.message); + final String message; + @override + String toString() => 'UploadError($message)'; +} + +void logMessage(String s) { print(s); } + +class Upload { + Upload(this.fromPath, this.largeName, this.smallName); + + static math.Random random; + static const String uriAuthority = 'www.googleapis.com'; + static const String uriPath = 'upload/storage/v1/b/flutter-catalog/o'; + + final String fromPath; + final String largeName; + final String smallName; + + List largeImage; + List smallImage; + bool largeImageSaved; + int retryCount = 0; + bool isComplete = false; + + // Exponential backoff per https://cloud.google.com/storage/docs/exponential-backoff + Duration get timeLimit { + if (retryCount == 0) + return const Duration(milliseconds: 1000); + random ??= new math.Random(); + return new Duration(milliseconds: random.nextInt(1000) + math.pow(2, retryCount) * 1000); + } + + Future save(HttpClient client, String name, List content) async { + try { + final Uri uri = new Uri.https(uriAuthority, uriPath, { + 'uploadType': 'media', + 'name': name, + }); + final HttpClientRequest request = await client.postUrl(uri); + request + ..headers.contentType = new ContentType('image', 'png') + ..headers.add('Authorization', 'Bearer $authorizationToken') + ..add(content); + + final HttpClientResponse response = await request.close().timeout(timeLimit); + if (response.statusCode == HttpStatus.OK) { + await response.drain(); + } else { + // TODO(hansmuller): only retry on 5xx and 429 responses + logMessage('Request to save "$name" (length ${content.length}) failed with status ${response.statusCode}, will retry'); + logMessage(await response.transform(UTF8.decoder).join()); + } + return response.statusCode == HttpStatus.OK; + } on TimeoutException catch (_) { + logMessage('Request to save "$name" (length ${content.length}) timed out, will retry'); + return false; + } + } + + Future run(HttpClient client) async { + assert(!isComplete); + if (retryCount > 2) + throw new UploadError('upload of "$fromPath" to "$largeName" and "$smallName" failed after 2 retries'); + + largeImage ??= await new File(fromPath).readAsBytes(); + smallImage ??= encodePng(copyResize(decodePng(largeImage), 400)); + + if (!largeImageSaved) + largeImageSaved = await save(client, largeName, largeImage); + isComplete = largeImageSaved && await save(client, smallName, smallImage); + + retryCount += 1; + return isComplete; + } + + static bool isNotComplete(Upload upload) => !upload.isComplete; +} + +Future saveScreenshots(List fromPaths, List largeNames, List smallNames) async { + assert(fromPaths.length == largeNames.length); + assert(fromPaths.length == smallNames.length); + + List uploads = new List(fromPaths.length); + for (int index = 0; index < uploads.length; index += 1) + uploads[index] = new Upload(fromPaths[index], largeNames[index], smallNames[index]); + + final HttpClient client = new HttpClient(); + while(uploads.any(Upload.isNotComplete)) { + uploads = uploads.where(Upload.isNotComplete).toList(); + await Future.wait(uploads.map((Upload upload) => upload.run(client))); + } + client.close(); +} + + +// If path is lib/foo.png then screenshotName is foo. +String screenshotName(String path) => basenameWithoutExtension(path); + +Future main(List args) async { + if (args.length != 2) + throw new UploadError('Usage: dart bin/save_screenshots.dart commit authorization'); + + final Directory outputDirectory = new Directory('.generated'); + final List screenshots = []; + outputDirectory.listSync().forEach((FileSystemEntity entity) { + if (entity is File && entity.path.endsWith('.png')) { + final File file = entity; + screenshots.add(file.path); + } + }); + + final String commit = args[0]; + final List largeNames = []; // Cloud storage names for the full res screenshots. + final List smallNames = []; // Likewise for the scaled down screenshots. + for (String path in screenshots) { + final String name = screenshotName(path); + largeNames.add('$commit/$name.png'); + smallNames.add('$commit/${name}_small.png'); + } + + authorizationToken = args[1]; + await saveScreenshots(screenshots, largeNames, smallNames); +} diff --git a/examples/catalog/bin/screenshot_test.dart.template b/examples/catalog/bin/screenshot_test.dart.template index 93f5f73f8f54e..4457b0617e4b3 100644 --- a/examples/catalog/bin/screenshot_test.dart.template +++ b/examples/catalog/bin/screenshot_test.dart.template @@ -8,6 +8,7 @@ import 'package:test/test.dart'; void main() { group('sample screenshots', () { + final String prefix = Platform.isMacOS ? 'ios_' : ""; FlutterDriver driver; setUpAll(() async { @@ -19,7 +20,6 @@ void main() { }); test('take sample screenshots', () async { - final String prefix = Platform.isMacOS ? 'ios_' : ""; final List paths = [ @(paths) ]; diff --git a/examples/catalog/pubspec.yaml b/examples/catalog/pubspec.yaml index b8219d79a554d..3c2aec5e80b64 100644 --- a/examples/catalog/pubspec.yaml +++ b/examples/catalog/pubspec.yaml @@ -1,8 +1,10 @@ -name: animated_list -description: A sample app for AnimatedList +name: sample_catalog +description: A collection of Flutter sample apps dependencies: flutter: sdk: flutter + image: + path: ^1.4.0 dev_dependencies: flutter_test: From 2ff2274cd36f1a63a9a40fa40b8ba733993dab20 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 5 Jun 2017 13:14:11 -0700 Subject: [PATCH 035/110] Make ColorSwatch more general, and test it (#10505) --- packages/flutter/lib/src/material/colors.dart | 72 ++++++------------- packages/flutter/lib/src/painting/colors.dart | 36 ++++++++++ .../flutter/test/painting/colors_test.dart | 24 +++++++ 3 files changed, 80 insertions(+), 52 deletions(-) diff --git a/packages/flutter/lib/src/material/colors.dart b/packages/flutter/lib/src/material/colors.dart index 7186c0b567570..45d4b89395222 100644 --- a/packages/flutter/lib/src/material/colors.dart +++ b/packages/flutter/lib/src/material/colors.dart @@ -2,41 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show Color, hashValues; +import 'dart:ui' show Color; -/// A color that has a small table of related colors called a "swatch". -/// -/// See also: -/// -/// * [MaterialColor] and [MaterialAccentColor], which define material design -/// primary and accent color swatches. -/// * [Colors], which defines all of the standard material design colors. -class ColorSwatch extends Color { - /// Creates a color that has a small table of related colors called a "swatch". - const ColorSwatch(int primary, this._swatch) : super(primary); - - final Map _swatch; - - /// Returns an element of the swatch table. - Color operator [](int index) => _swatch[index]; - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other.runtimeType != runtimeType) - return false; - final ColorSwatch typedOther = other; - return super==(other) && _swatch == typedOther._swatch; - } - - @override - int get hashCode => hashValues(runtimeType, value, _swatch); - - @override - String toString() => '$runtimeType(primary value: ${super.toString()})'; - -} +import 'package:flutter/painting.dart'; /// Defines a single color as well a color swatch with ten shades of the color. /// @@ -47,39 +15,39 @@ class ColorSwatch extends Color { /// See also: /// /// * [Colors], which defines all of the standard material colors. -class MaterialColor extends ColorSwatch { +class MaterialColor extends ColorSwatch { /// Creates a color swatch with a variety of shades. const MaterialColor(int primary, Map swatch) : super(primary, swatch); /// The lightest shade. - Color get shade50 => _swatch[50]; + Color get shade50 => this[50]; /// The second lightest shade. - Color get shade100 => _swatch[100]; + Color get shade100 => this[100]; /// The third lightest shade. - Color get shade200 => _swatch[200]; + Color get shade200 => this[200]; /// The fourth lightest shade. - Color get shade300 => _swatch[300]; + Color get shade300 => this[300]; /// The fifth lightest shade. - Color get shade400 => _swatch[400]; + Color get shade400 => this[400]; /// The default shade. - Color get shade500 => _swatch[500]; + Color get shade500 => this[500]; /// The fourth darkest shade. - Color get shade600 => _swatch[600]; + Color get shade600 => this[600]; /// The third darkest shade. - Color get shade700 => _swatch[700]; + Color get shade700 => this[700]; /// The second darkest shade. - Color get shade800 => _swatch[800]; + Color get shade800 => this[800]; /// The darkest shade. - Color get shade900 => _swatch[900]; + Color get shade900 => this[900]; } /// Defines a single accent color as well a swatch of four shades of the @@ -94,25 +62,25 @@ class MaterialColor extends ColorSwatch { /// /// * [Colors], which defines all of the standard material colors. /// * -class MaterialAccentColor extends ColorSwatch { +class MaterialAccentColor extends ColorSwatch { /// Creates a color swatch with a variety of shades appropriate for accent /// colors. const MaterialAccentColor(int primary, Map swatch) : super(primary, swatch); /// The lightest shade. - Color get shade50 => _swatch[50]; + Color get shade50 => this[50]; /// The second lightest shade. - Color get shade100 => _swatch[100]; + Color get shade100 => this[100]; /// The default shade. - Color get shade200 => _swatch[200]; + Color get shade200 => this[200]; /// The second darkest shade. - Color get shade400 => _swatch[400]; + Color get shade400 => this[400]; /// The darkest shade. - Color get shade700 => _swatch[700]; + Color get shade700 => this[700]; } /// [Color] and [ColorSwatch] constants which represent Material design's @@ -130,7 +98,7 @@ class MaterialAccentColor extends ColorSwatch { /// Colors.green[400] // Selects a mid-range green. /// ``` /// -/// Each ColorSwatch constant is a color and can used directly. For example +/// Each [ColorSwatch] constant is a color and can used directly. For example /// /// ```dart /// new Container( diff --git a/packages/flutter/lib/src/painting/colors.dart b/packages/flutter/lib/src/painting/colors.dart index b76a4ae213366..be9748dc41fcd 100644 --- a/packages/flutter/lib/src/painting/colors.dart +++ b/packages/flutter/lib/src/painting/colors.dart @@ -172,3 +172,39 @@ class HSVColor { @override String toString() => "HSVColor($alpha, $hue, $saturation, $value)"; } + +/// A color that has a small table of related colors called a "swatch". +/// +/// The table is indexed by values of type `T`. +/// +/// See also: +/// +/// * [MaterialColor] and [MaterialAccentColor], which define material design +/// primary and accent color swatches. +/// * [Colors], which defines all of the standard material design colors. +class ColorSwatch extends Color { + /// Creates a color that has a small table of related colors called a "swatch". + const ColorSwatch(int primary, this._swatch) : super(primary); + + @protected + final Map _swatch; + + /// Returns an element of the swatch table. + Color operator [](T index) => _swatch[index]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + if (other.runtimeType != runtimeType) + return false; + final ColorSwatch typedOther = other; + return super==(other) && _swatch == typedOther._swatch; + } + + @override + int get hashCode => hashValues(runtimeType, value, _swatch); + + @override + String toString() => '$runtimeType(primary value: ${super.toString()})'; +} diff --git a/packages/flutter/test/painting/colors_test.dart b/packages/flutter/test/painting/colors_test.dart index a1a4f0ac0df6d..213b8ed7da2a0 100644 --- a/packages/flutter/test/painting/colors_test.dart +++ b/packages/flutter/test/painting/colors_test.dart @@ -38,4 +38,28 @@ void main() { expect(green.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0xFF, 0x00))); expect(blue.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF))); }); + + test('ColorSwatch test', () { + final int color = 0xFF027223; + final ColorSwatch greens1 = new ColorSwatch( + color, const { + '2259 C': const Color(0xFF027223), + '2273 C': const Color(0xFF257226), + '2426 XGC': const Color(0xFF00932F), + '7732 XGC': const Color(0xFF007940), + }, + ); + final ColorSwatch greens2 = new ColorSwatch( + color, const { + '2259 C': const Color(0xFF027223), + '2273 C': const Color(0xFF257226), + '2426 XGC': const Color(0xFF00932F), + '7732 XGC': const Color(0xFF007940), + }, + ); + expect(greens1, greens2); + expect(greens1.hashCode, greens2.hashCode); + expect(greens1['2259 C'], const Color(0xFF027223)); + expect(greens1.value, 0xFF027223); + }); } From ca4f4fc8d1be9e2bebc3c1c68ea5ba112035b9fa Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 5 Jun 2017 13:15:43 -0700 Subject: [PATCH 036/110] Make the Brightness estimator public (#10508) --- .../flutter/lib/src/material/theme_data.dart | 61 ++++++++++--------- .../test/material/theme_data_test.dart | 15 ++++- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index cc134fbb1c83b..591e9edcaf93a 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -43,32 +43,6 @@ const Color _kLightThemeSplashColor = const Color(0x66C8C8C8); const Color _kDarkThemeHighlightColor = const Color(0x40CCCCCC); const Color _kDarkThemeSplashColor = const Color(0x40CCCCCC); -// See -double _linearizeColorComponent(double component) { - if (component <= 0.03928) - return component / 12.92; - return math.pow((component + 0.055) / 1.055, 2.4); -} - -Brightness _estimateBrightnessForColor(Color color) { - // See - final double R = _linearizeColorComponent(color.red / 0xFF); - final double G = _linearizeColorComponent(color.green / 0xFF); - final double B = _linearizeColorComponent(color.blue / 0xFF); - final double L = 0.2126 * R + 0.7152 * G + 0.0722 * B; - - // See - // The spec says to use kThreshold=0.0525, but Material Design appears to bias - // more towards using light text than WCAG20 recommends. Material Design spec - // doesn't say what value to use, but 0.15 seemed close to what the Material - // Design spec shows for its color palette on - // . - const double kThreshold = 0.15; - if ((L + 0.05) * (L + 0.05) > kThreshold ) - return Brightness.light; - return Brightness.dark; -} - /// Holds the color and typography values for a material design theme. /// /// Use this class to configure a [Theme] widget. @@ -134,10 +108,10 @@ class ThemeData { final bool isDark = brightness == Brightness.dark; primarySwatch ??= Colors.blue; primaryColor ??= isDark ? Colors.grey[900] : primarySwatch[500]; - primaryColorBrightness ??= _estimateBrightnessForColor(primaryColor); + primaryColorBrightness ??= estimateBrightnessForColor(primaryColor); final bool primaryIsDark = primaryColorBrightness == Brightness.dark; accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500]; - accentColorBrightness ??= _estimateBrightnessForColor(accentColor); + accentColorBrightness ??= estimateBrightnessForColor(accentColor); final bool accentIsDark = accentColorBrightness == Brightness.dark; canvasColor ??= isDark ? Colors.grey[850] : Colors.grey[50]; scaffoldBackgroundColor ??= canvasColor; @@ -467,6 +441,37 @@ class ThemeData { ); } + // See + static double _linearizeColorComponent(double component) { + if (component <= 0.03928) + return component / 12.92; + return math.pow((component + 0.055) / 1.055, 2.4); + } + + /// Determines whether the given [Color] is [Brightness.light] or + /// [Brightness.dark]. + /// + /// This compares the luminosity of the given color to a threshold value that + /// matches the material design specification. + static Brightness estimateBrightnessForColor(Color color) { + // See + final double R = _linearizeColorComponent(color.red / 0xFF); + final double G = _linearizeColorComponent(color.green / 0xFF); + final double B = _linearizeColorComponent(color.blue / 0xFF); + final double L = 0.2126 * R + 0.7152 * G + 0.0722 * B; + + // See + // The spec says to use kThreshold=0.0525, but Material Design appears to bias + // more towards using light text than WCAG20 recommends. Material Design spec + // doesn't say what value to use, but 0.15 seemed close to what the Material + // Design spec shows for its color palette on + // . + const double kThreshold = 0.15; + if ((L + 0.05) * (L + 0.05) > kThreshold ) + return Brightness.light; + return Brightness.dark; + } + /// Linearly interpolate between two themes. /// /// The arguments must not be null. diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index 57ea08665cb00..33b4f307c3994 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -90,7 +90,20 @@ void main() { expect(themeData.accentTextTheme.display4.fontFamily, equals('Ahem')); }); - test('Can estimate brightness', () { + test('Can estimate brightness - directly', () { + expect(ThemeData.estimateBrightnessForColor(Colors.white), equals(Brightness.light)); + expect(ThemeData.estimateBrightnessForColor(Colors.black), equals(Brightness.dark)); + expect(ThemeData.estimateBrightnessForColor(Colors.blue), equals(Brightness.dark)); + expect(ThemeData.estimateBrightnessForColor(Colors.yellow), equals(Brightness.light)); + expect(ThemeData.estimateBrightnessForColor(Colors.deepOrange), equals(Brightness.dark)); + expect(ThemeData.estimateBrightnessForColor(Colors.orange), equals(Brightness.light)); + expect(ThemeData.estimateBrightnessForColor(Colors.lime), equals(Brightness.light)); + expect(ThemeData.estimateBrightnessForColor(Colors.grey), equals(Brightness.light)); + expect(ThemeData.estimateBrightnessForColor(Colors.teal), equals(Brightness.dark)); + expect(ThemeData.estimateBrightnessForColor(Colors.indigo), equals(Brightness.dark)); + }); + + test('Can estimate brightness - indirectly', () { expect(new ThemeData(primaryColor: Colors.white).primaryColorBrightness, equals(Brightness.light)); expect(new ThemeData(primaryColor: Colors.black).primaryColorBrightness, equals(Brightness.dark)); expect(new ThemeData(primaryColor: Colors.blue).primaryColorBrightness, equals(Brightness.dark)); From 03e7ebe67d8acdc8ffa5dac1b7c9c7d81b5d2bde Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Mon, 5 Jun 2017 15:44:54 -0700 Subject: [PATCH 037/110] Roll engine to c3721a589b50e3d2c2e56befbfd6f860d0de12a1 (#10512) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 7c18b5e650fbf..fc3205bce7c76 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -bd09286e4aec422a1f77eac9de84274f22484846 +c3721a589b50e3d2c2e56befbfd6f860d0de12a1 From 4b7e3494dd7540bc34bfe5ea907f28e2c3f18e8f Mon Sep 17 00:00:00 2001 From: "P.Y. Laligand" Date: Mon, 5 Jun 2017 16:38:33 -0700 Subject: [PATCH 038/110] Add drag completion callback to Draggable. (#10455) Fixes #10350 --- .../flutter/lib/src/widgets/drag_target.dart | 15 +- .../flutter/test/widgets/draggable_test.dart | 131 +++++++++++++++++- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 2cba6ea3bc103..2b289fbea7536 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -94,7 +94,8 @@ class Draggable extends StatefulWidget { this.affinity, this.maxSimultaneousDrags, this.onDragStarted, - this.onDraggableCanceled + this.onDraggableCanceled, + this.onDragCompleted, }) : assert(child != null), assert(feedback != null), assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0), @@ -182,6 +183,16 @@ class Draggable extends StatefulWidget { /// callback is still in the tree. final DraggableCanceledCallback onDraggableCanceled; + /// Called when the draggable is dropped and accepted by a [DragTarget]. + /// + /// This function might be called after this widget has been removed from the + /// tree. For example, if a drag was in progress when this widget was removed + /// from the tree and the drag ended up completing, this callback will + /// still be called. For this reason, implementations of this callback might + /// need to check [State.mounted] to check whether the state receiving the + /// callback is still in the tree. + final VoidCallback onDragCompleted; + /// Creates a gesture recognizer that recognizes the start of the drag. /// /// Subclasses can override this function to customize when they start @@ -313,6 +324,8 @@ class _DraggableState extends State> { _activeCount -= 1; _disposeRecognizerIfInactive(); } + if (wasAccepted && widget.onDragCompleted != null) + widget.onDragCompleted(); if (!wasAccepted && widget.onDraggableCanceled != null) widget.onDraggableCanceled(velocity, offset); } diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index ace589150142b..4e6acb2666a5a 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -518,7 +518,7 @@ void main() { events.clear(); }); - testWidgets('Drag and drop - onDraggableDropped not called if dropped on accepting target', (WidgetTester tester) async { + testWidgets('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async { final List accepted = []; bool onDraggableCanceledCalled = false; @@ -579,7 +579,7 @@ void main() { expect(onDraggableCanceledCalled, isFalse); }); - testWidgets('Drag and drop - onDraggableDropped called if dropped on non-accepting target', (WidgetTester tester) async { + testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async { final List accepted = []; bool onDraggableCanceledCalled = false; Velocity onDraggableCanceledVelocity; @@ -649,7 +649,7 @@ void main() { expect(onDraggableCanceledOffset, equals(new Offset(secondLocation.dx, secondLocation.dy))); }); - testWidgets('Drag and drop - onDraggableDropped called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async { + testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async { final List accepted = []; bool onDraggableCanceledCalled = false; Velocity onDraggableCanceledVelocity; @@ -699,6 +699,131 @@ void main() { expect(onDraggableCanceledOffset, equals(new Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0))); }); + testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async { + final List accepted = []; + bool onDragCompletedCalled = false; + + await tester.pumpWidget(new MaterialApp( + home: new Column( + children: [ + new Draggable( + data: 1, + child: const Text('Source'), + feedback: const Text('Dragging'), + onDragCompleted: () { + onDragCompletedCalled = true; + } + ), + new DragTarget( + builder: (BuildContext context, List data, List rejects) { + return new Container( + height: 100.0, + child: const Text('Target') + ); + }, + onWillAccept: (int data) => false + ), + ] + ) + )); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + + final Offset firstLocation = tester.getTopLeft(find.text('Source')); + final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + + final Offset secondLocation = tester.getCenter(find.text('Target')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + + await gesture.up(); + await tester.pump(); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + }); + + testWidgets('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async { + final List accepted = []; + bool onDragCompletedCalled = false; + + await tester.pumpWidget(new MaterialApp( + home: new Column( + children: [ + new Draggable( + data: 1, + child: const Text('Source'), + feedback: const Text('Dragging'), + onDragCompleted: () { + onDragCompletedCalled = true; + } + ), + new DragTarget( + builder: (BuildContext context, List data, List rejects) { + return new Container(height: 100.0, child: const Text('Target')); + }, + onAccept: accepted.add + ), + ] + ) + )); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + + final Offset firstLocation = tester.getCenter(find.text('Source')); + final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + + final Offset secondLocation = tester.getCenter(find.text('Target')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(accepted, isEmpty); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isFalse); + + await gesture.up(); + await tester.pump(); + + expect(accepted, equals([1])); + expect(find.text('Source'), findsOneWidget); + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(onDragCompletedCalled, isTrue); + }); + testWidgets('Drag and drop - allow pass thru of unaccepted data test', (WidgetTester tester) async { final List acceptedInts = []; final List acceptedDoubles = []; From cee36e306a575fd5c2c14baf36efe0e5d5421b59 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Mon, 5 Jun 2017 18:06:55 -0700 Subject: [PATCH 039/110] Roll engine to 8686a458104628c4cd5e142e59cac53458724e31 (#10518) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index fc3205bce7c76..1904543b30301 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -c3721a589b50e3d2c2e56befbfd6f860d0de12a1 +8686a458104628c4cd5e142e59cac53458724e31 From 18d1be4a224af26d8ad6358e8d685d0669d923ec Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 5 Jun 2017 22:15:41 -0700 Subject: [PATCH 040/110] Make implicit animations work with hot reload (#10514) --- packages/flutter/lib/src/rendering/binding.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 43018d414faff..6faf2d72f52f8 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -262,8 +262,17 @@ abstract class RendererBinding extends BindingBase with SchedulerBinding, Servic /// likely to be quite expensive) gets a few extra milliseconds to run. void scheduleWarmUpFrame() { // We use timers here to ensure that microtasks flush in between. + // + // We call resetEpoch after this frame so that, in the hot reload case, the + // very next frame pretends to have occurred immediately after this warm-up + // frame. The warm-up frame's timestamp will typically be far in the past + // (the time of the last real frame), so if we didn't reset the epoch we + // would see a sudden jump from the old time in the warm-up frame to the new + // time in the "real" frame. The biggest problem with this is that implicit + // animations end up being triggered at the old time and then skipping every + // frame and finishing in the new time. Timer.run(() { handleBeginFrame(null); }); - Timer.run(() { handleDrawFrame(); }); + Timer.run(() { handleDrawFrame(); resetEpoch(); }); } @override From 6f824bcec9b68b3580a7b2687a47c1e98b21f664 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 5 Jun 2017 22:44:49 -0700 Subject: [PATCH 041/110] More documentation (#10519) --- .../flutter/lib/src/animation/curves.dart | 19 + .../lib/src/cupertino/bottom_tab_bar.dart | 5 +- .../flutter/lib/src/cupertino/colors.dart | 1 + .../flutter/lib/src/cupertino/nav_bar.dart | 5 +- .../flutter/lib/src/material/back_button.dart | 5 + .../lib/src/material/checkbox_list_tile.dart | 3 + packages/flutter/lib/src/material/colors.dart | 863 +++++++++++++++--- .../lib/src/material/radio_list_tile.dart | 3 + .../lib/src/material/switch_list_tile.dart | 3 + .../flutter/lib/src/painting/box_painter.dart | 4 +- .../flutter/lib/src/rendering/editable.dart | 25 + .../flutter/lib/src/rendering/object.dart | 4 +- .../flutter/lib/src/rendering/proxy_box.dart | 12 +- .../rendering/sliver_persistent_header.dart | 1 + .../lib/src/services/image_provider.dart | 81 ++ .../lib/src/services/image_stream.dart | 15 +- .../lib/src/widgets/animated_cross_fade.dart | 37 + packages/flutter/lib/src/widgets/basic.dart | 77 +- packages/flutter/lib/src/widgets/binding.dart | 107 +++ .../flutter/lib/src/widgets/container.dart | 2 +- .../flutter/lib/src/widgets/framework.dart | 2 +- packages/flutter/lib/src/widgets/image.dart | 9 + .../lib/src/widgets/navigation_toolbar.dart | 4 +- .../lib/src/widgets/nested_scroll_view.dart | 15 + .../flutter/test/widgets/transform_test.dart | 24 + packages/flutter_driver/lib/src/health.dart | 3 - .../flutter_test/lib/src/all_elements.dart | 2 +- packages/flutter_test/lib/src/binding.dart | 4 + packages/flutter_test/lib/src/finders.dart | 3 + 29 files changed, 1169 insertions(+), 169 deletions(-) diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index dcb7aa013e812..f0c74528d7342 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -448,6 +448,25 @@ class ElasticInOutCurve extends Curve { // PREDEFINED CURVES /// A collection of common animation curves. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_in.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_in_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_bounce_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_decelerate.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_in.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_in_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_ease_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_in.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_in_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_elastic_out.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_fast_out_slow_in.png) +/// ![](https://flutter.github.io/assets-for-api-docs/animation/curve_linear.png) +/// +/// See also: +/// +/// * [Curve], the interface implemented by the constants available from the +/// [Curves] class. class Curves { Curves._(); diff --git a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart index 59472f35320a4..c1e7efa392f14 100644 --- a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart +++ b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart @@ -15,7 +15,7 @@ const double _kTabBarHeight = 50.0; const Color _kDefaultTabBarBackgroundColor = const Color(0xCCF8F8F8); const Color _kDefaultTabBarBorderColor = const Color(0x4C000000); -/// An iOS styled bottom navigation tab bar. +/// An iOS-styled bottom navigation tab bar. /// /// Displays multiple tabs using [BottomNavigationBarItem] with one tab being /// active, the first tab by default. @@ -29,9 +29,10 @@ const Color _kDefaultTabBarBorderColor = const Color(0x4C000000); /// /// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by /// default), it will produce a blurring effect to the content behind it. -/// +// // TODO(xster): document using with a CupertinoScaffold. class CupertinoTabBar extends StatelessWidget { + /// Creates a tab bar in the iOS style. CupertinoTabBar({ Key key, @required this.items, diff --git a/packages/flutter/lib/src/cupertino/colors.dart b/packages/flutter/lib/src/cupertino/colors.dart index cb546cb56427e..17ab67e52fede 100644 --- a/packages/flutter/lib/src/cupertino/colors.dart +++ b/packages/flutter/lib/src/cupertino/colors.dart @@ -4,6 +4,7 @@ import 'dart:ui' show Color; +/// [Color] constants that describe colors commonly used in iOS applications. class CupertinoColors { CupertinoColors._(); diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index 0ee64fe912cd3..82ecc791d09c1 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -15,7 +15,7 @@ const double _kNavBarHeight = 44.0; const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8); const Color _kDefaultNavBarBorderColor = const Color(0x4C000000); -/// An iOS styled navigation bar. +/// An iOS-styled navigation bar. /// /// The navigation bar is a toolbar that minimally consists of a widget, normally /// a page title, in the [middle] of the toolbar. @@ -28,11 +28,12 @@ const Color _kDefaultNavBarBorderColor = const Color(0x4C000000); /// /// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by /// default), it will produce a blurring effect to the content behind it. -/// +// // TODO(xster): document automatic addition of a CupertinoBackButton. // TODO(xster): add sample code using icons. // TODO(xster): document integration into a CupertinoScaffold. class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWidget { + /// Creates a navigation bar in the iOS style. const CupertinoNavigationBar({ Key key, this.leading, diff --git a/packages/flutter/lib/src/material/back_button.dart b/packages/flutter/lib/src/material/back_button.dart index d6326a1d0fd86..671bfeeb8721f 100644 --- a/packages/flutter/lib/src/material/back_button.dart +++ b/packages/flutter/lib/src/material/back_button.dart @@ -10,6 +10,8 @@ import 'theme.dart'; /// A "back" icon that's appropriate for the current [TargetPlatform]. /// +/// The current platform is determined by querying for the ambient [Theme]. +/// /// See also: /// /// * [BackButton], an [IconButton] with a [BackButtonIcon] that calls @@ -17,7 +19,10 @@ import 'theme.dart'; /// * [IconButton], which is a more general widget for creating buttons /// with icons. /// * [Icon], a material design icon. +/// * [ThemeData.platform], which specifies the current platform. class BackButtonIcon extends StatelessWidget { + /// Creates an icon that shows the appropriate "back" image for + /// the current platform (as obtained from the [Theme]). const BackButtonIcon({ Key key }) : super(key: key); /// Returns tha appropriate "back" icon for the given `platform`. diff --git a/packages/flutter/lib/src/material/checkbox_list_tile.dart b/packages/flutter/lib/src/material/checkbox_list_tile.dart index 02b217b7e0476..25ec516c4d2d2 100644 --- a/packages/flutter/lib/src/material/checkbox_list_tile.dart +++ b/packages/flutter/lib/src/material/checkbox_list_tile.dart @@ -31,6 +31,9 @@ import 'theme.dart'; /// [secondary] widget is placed on the opposite side. This maps to the /// [ListTile.leading] and [ListTile.trailing] properties of [ListTile]. /// +/// To show the [CheckboxListTile] as disabled, pass null as the [onChanged] +/// callback. +/// /// ## Sample code /// /// This widget shows a checkbox that, when checked, slows down all animations diff --git a/packages/flutter/lib/src/material/colors.dart b/packages/flutter/lib/src/material/colors.dart index 45d4b89395222..14a1fa798768f 100644 --- a/packages/flutter/lib/src/material/colors.dart +++ b/packages/flutter/lib/src/material/colors.dart @@ -91,6 +91,16 @@ class MaterialAccentColor extends ColorSwatch { /// colors selected for the current theme, such as [ThemeData.primaryColor] and /// [ThemeData.accentColor] (among many others). /// +/// Most swatches have colors from 100 to 900 in increments of one hundred, plus +/// the color 50. The smaller the number, the more pale the color. The greater +/// the number, the darker the color. The accent swatches (e.g. [redAccent]) only +/// have the values 100, 200, 400, and 700. +/// +/// In addition, a series of blacks and whites with common opacities are +/// available. For example, [black54] is a pure black with 54% opacity. +/// +/// ## Sample code +/// /// To select a specific color from one of the swatches, index into the swatch /// using an integer for the specific color desired, as follows: /// @@ -98,7 +108,7 @@ class MaterialAccentColor extends ColorSwatch { /// Colors.green[400] // Selects a mid-range green. /// ``` /// -/// Each [ColorSwatch] constant is a color and can used directly. For example +/// Each [ColorSwatch] constant is a color and can used directly. For example: /// /// ```dart /// new Container( @@ -106,13 +116,73 @@ class MaterialAccentColor extends ColorSwatch { /// ) /// ``` /// -/// Most swatches have colors from 100 to 900 in increments of one hundred, plus -/// the color 50. The smaller the number, the more pale the color. The greater -/// the number, the darker the color. The accent swatches (e.g. [redAccent]) only -/// have the values 100, 200, 400, and 700. +/// ## Color palettes /// -/// In addition, a series of blacks and whites with common opacities are -/// available. For example, [black54] is a pure black with 54% opacity. +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueGrey.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.brown.png) +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.grey.png) +/// +/// ## Blacks and whites +/// +/// These colors are identified by their transparency. The low transparency +/// levels (e.g. [Colors.white12] and [Colors.white10]) are very hard to see and +/// should be avoided in general. They are intended for very subtle effects. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) +/// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.whites.png) +/// +/// The [Colors.transparent] color isn't shown here because it is entirely +/// invisible! class Colors { Colors._(); @@ -120,54 +190,97 @@ class Colors { static const Color transparent = const Color(0x00000000); /// Completely opaque black. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) + /// + /// See also: + /// + /// * [black87], [black54], [black45], [black38], [black26], [black12], which + /// are variants on this color but with different opacities. + /// * [white], a solid white color. + /// * [transparent], a fully-transparent color. static const Color black = const Color(0xFF000000); /// Black with 87% opacity. /// /// This is a good contrasting color for text in light themes. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) + /// /// See also: /// - /// * [Typography.black], which uses this color for its text styles. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [Typography.black], which uses this color for its text styles. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. + /// * [black], [black54], [black45], [black38], [black26], [black12], which + /// are variants on this color but with different opacities. static const Color black87 = const Color(0xDD000000); /// Black with 54% opacity. /// - /// This is a color commonly used for headings in light themes. + /// This is a color commonly used for headings in light themes. It's also used + /// as the mask color behind dialogs. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) /// /// See also: /// - /// * [Typography.black], which uses this color for its text styles. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [Typography.black], which uses this color for its text styles. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. + /// * [black], [black87], [black45], [black38], [black26], [black12], which + /// are variants on this color but with different opacities. static const Color black54 = const Color(0x8A000000); + /// Black with 45% opacity. + /// + /// Used for disabled icons. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) + /// + /// See also: + /// + /// * [black], [black87], [black54], [black38], [black26], [black12], which + /// are variants on this color but with different opacities. + static const Color black45 = const Color(0x73000000); + /// Black with 38% opacity. /// /// Used for the placeholder text in data tables in light themes. - static const Color black38 = const Color(0x61000000); - - /// Black with 45% opacity. /// - /// Used for modal barriers. - static const Color black45 = const Color(0x73000000); + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) + /// + /// See also: + /// + /// * [black], [black87], [black54], [black45], [black26], [black12], which + /// are variants on this color but with different opacities. + static const Color black38 = const Color(0x61000000); /// Black with 26% opacity. /// /// Used for disabled radio buttons and the text of disabled flat buttons in light themes. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) + /// /// See also: /// - /// * [ThemeData.disabledColor], which uses this color by default in light themes. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [ThemeData.disabledColor], which uses this color by default in light themes. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. + /// * [black], [black87], [black54], [black45], [black38], [black12], which + /// are variants on this color but with different opacities. static const Color black26 = const Color(0x42000000); /// Black with 12% opacity. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blacks.png) + /// /// Used for the background of disabled raised buttons in light themes. + /// + /// See also: + /// + /// * [black], [black87], [black54], [black45], [black38], [black26], which + /// are variants on this color but with different opacities. static const Color black12 = const Color(0x1F000000); /// Completely opaque white. @@ -175,45 +288,85 @@ class Colors { /// This is a good contrasting color for the [ThemeData.primaryColor] in the /// dark theme. See [ThemeData.brightness]. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.whites.png) + /// /// See also: /// - /// * [Typography.white], which uses this color for its text styles. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [Typography.white], which uses this color for its text styles. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. + /// * [white70, white30, white12, white10], which are variants on this color + /// but with different opacities. + /// * [black], a solid black color. + /// * [transparent], a fully-transparent color. static const Color white = const Color(0xFFFFFFFF); /// White with 70% opacity. /// /// This is a color commonly used for headings in dark themes. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.whites.png) + /// /// See also: /// - /// * [Typography.white], which uses this color for its text styles. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [Typography.white], which uses this color for its text styles. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. + /// * [white, white30, white12, white10], which are variants on this color + /// but with different opacities. static const Color white70 = const Color(0xB3FFFFFF); /// White with 32% opacity. /// /// Used for disabled radio buttons and the text of disabled flat buttons in dark themes. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.whites.png) + /// /// See also: /// - /// * [ThemeData.disabledColor], which uses this color by default in dark themes. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [ThemeData.disabledColor], which uses this color by default in dark themes. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. + /// * [white, white70, white12, white10], which are variants on this color + /// but with different opacities. static const Color white30 = const Color(0x4DFFFFFF); /// White with 12% opacity. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.whites.png) + /// /// Used for the background of disabled raised buttons in dark themes. + /// + /// See also: + /// + /// * [white, white70, white30, white10], which are variants on this color + /// but with different opacities. static const Color white12 = const Color(0x1FFFFFFF); /// White with 10% opacity. + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.whites.png) + /// + /// See also: + /// + /// * [white, white70, white30, white12], which are variants on this color + /// but with different opacities. + /// * [transparent], a fully-transparent color, not far from this one. static const Color white10 = const Color(0x1AFFFFFF); /// The red primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -223,9 +376,10 @@ class Colors { /// /// See also: /// - /// * [redAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [redAccent], the corresponding accent colors. + /// * [deepOrange] and [pink], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor red = const MaterialColor( _redPrimaryValue, const { @@ -245,6 +399,17 @@ class Colors { /// The red accent swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -254,9 +419,10 @@ class Colors { /// /// See also: /// - /// * [red], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [red], the corresponding primary colors. + /// * [deepOrangeAccent] and [pinkAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor redAccent = const MaterialAccentColor( _redAccentValue, const { @@ -270,6 +436,17 @@ class Colors { /// The pink primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -279,9 +456,10 @@ class Colors { /// /// See also: /// - /// * [pinkAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [pinkAccent], the corresponding accent colors. + /// * [red] and [purple], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor pink = const MaterialColor( _pinkPrimaryValue, const { @@ -301,6 +479,17 @@ class Colors { /// The pink accent color swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -310,9 +499,10 @@ class Colors { /// /// See also: /// - /// * [pink], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [pink], the corresponding primary colors. + /// * [redAccent] and [purpleAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor pinkAccent = const MaterialAccentColor( _pinkAccentPrimaryValue, const { @@ -326,6 +516,17 @@ class Colors { /// The purple primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -335,9 +536,10 @@ class Colors { /// /// See also: /// - /// * [purpleAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [purpleAccent], the corresponding accent colors. + /// * [deepPurple] and [pink], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor purple = const MaterialColor( _purplePrimaryValue, const { @@ -357,6 +559,17 @@ class Colors { /// The purple accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pink.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.pinkAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -366,9 +579,10 @@ class Colors { /// /// See also: /// - /// * [purple], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [purple], the corresponding primary colors. + /// * [deepPurpleAccent] and [pinkAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor purpleAccent = const MaterialAccentColor( _purpleAccentPrimaryValue, const { @@ -382,6 +596,17 @@ class Colors { /// The deep purple primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -391,9 +616,10 @@ class Colors { /// /// See also: /// - /// * [deepPurpleAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [deepPurpleAccent], the corresponding accent colors. + /// * [purple] and [indigo], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor deepPurple = const MaterialColor( _deepPurplePrimaryValue, const { @@ -413,6 +639,17 @@ class Colors { /// The deep purple accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.purpleAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -422,9 +659,10 @@ class Colors { /// /// See also: /// - /// * [deepPurple], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [deepPurple], the corresponding primary colors. + /// * [purpleAccent] and [indigoAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor deepPurpleAccent = const MaterialAccentColor( _deepPurpleAccentPrimaryValue, const { @@ -438,6 +676,17 @@ class Colors { /// The indigo primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -447,9 +696,10 @@ class Colors { /// /// See also: /// - /// * [indigoAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [indigoAccent], the corresponding accent colors. + /// * [blue] and [deepPurple], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor indigo = const MaterialColor( _indigoPrimaryValue, const { @@ -469,6 +719,17 @@ class Colors { /// The indigo accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurple.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepPurpleAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -478,9 +739,10 @@ class Colors { /// /// See also: /// - /// * [indigo], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [indigo], the corresponding primary colors. + /// * [blueAccent] and [deepPurpleAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor indigoAccent = const MaterialAccentColor( _indigoAccentPrimaryValue, const { @@ -494,6 +756,19 @@ class Colors { /// The blue primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueGrey.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -503,9 +778,10 @@ class Colors { /// /// See also: /// - /// * [blueAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [blueAccent], the corresponding accent colors. + /// * [indigo], [lightBlue], and [blueGrey], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor blue = const MaterialColor( _bluePrimaryValue, const { @@ -525,6 +801,17 @@ class Colors { /// The blue accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigo.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.indigoAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -534,9 +821,10 @@ class Colors { /// /// See also: /// - /// * [blue], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [blue], the corresponding primary colors. + /// * [indigoAccent] and [lightBlueAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor blueAccent = const MaterialAccentColor( _blueAccentPrimaryValue, const { @@ -550,6 +838,17 @@ class Colors { /// The light blue primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -559,9 +858,10 @@ class Colors { /// /// See also: /// - /// * [lightBlueAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [lightBlueAccent], the corresponding accent colors. + /// * [blue] and [cyan], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor lightBlue = const MaterialColor( _lightBluePrimaryValue, const { @@ -581,6 +881,17 @@ class Colors { /// The light blue accent swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -590,9 +901,10 @@ class Colors { /// /// See also: /// - /// * [lightBlue], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [lightBlue], the corresponding primary colors. + /// * [blueAccent] and [cyanAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor lightBlueAccent = const MaterialAccentColor( _lightBlueAccentPrimaryValue, const { @@ -606,6 +918,19 @@ class Colors { /// The cyan primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueGrey.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -615,9 +940,10 @@ class Colors { /// /// See also: /// - /// * [cyanAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [cyanAccent], the corresponding accent colors. + /// * [lightBlue], [teal], and [blueGrey], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor cyan = const MaterialColor( _cyanPrimaryValue, const { @@ -637,6 +963,17 @@ class Colors { /// The cyan accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlue.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightBlueAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -646,9 +983,10 @@ class Colors { /// /// See also: /// - /// * [cyan], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [cyan], the corresponding primary colors. + /// * [lightBlueAccent] and [tealAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor cyanAccent = const MaterialAccentColor( _cyanAccentPrimaryValue, const { @@ -662,6 +1000,17 @@ class Colors { /// The teal primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -671,9 +1020,10 @@ class Colors { /// /// See also: /// - /// * [tealAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [tealAccent], the corresponding accent colors. + /// * [green] and [cyan], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor teal = const MaterialColor( _tealPrimaryValue, const { @@ -693,6 +1043,17 @@ class Colors { /// The teal accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyanAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -702,9 +1063,10 @@ class Colors { /// /// See also: /// - /// * [teal], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [teal], the corresponding primary colors. + /// * [greenAccent] and [cyanAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor tealAccent = const MaterialAccentColor( _tealAccentPrimaryValue, const { @@ -718,6 +1080,20 @@ class Colors { /// The green primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -727,9 +1103,10 @@ class Colors { /// /// See also: /// - /// * [greenAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [greenAccent], the corresponding accent colors. + /// * [teal], [lightGreen], and [lime], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor green = const MaterialColor( _greenPrimaryValue, const { @@ -749,6 +1126,20 @@ class Colors { /// The green accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.teal.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.tealAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -758,9 +1149,10 @@ class Colors { /// /// See also: /// - /// * [green], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [green], the corresponding primary colors. + /// * [tealAccent], [lightGreenAccent], and [limeAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor greenAccent = const MaterialAccentColor( _greenAccentPrimaryValue, const { @@ -774,6 +1166,17 @@ class Colors { /// The light green primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -783,9 +1186,10 @@ class Colors { /// /// See also: /// - /// * [lightGreenAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [lightGreenAccent], the corresponding accent colors. + /// * [green] and [lime], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor lightGreen = const MaterialColor( _lightGreenPrimaryValue, const { @@ -805,6 +1209,17 @@ class Colors { /// The light green accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.green.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.greenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -814,9 +1229,10 @@ class Colors { /// /// See also: /// - /// * [lightGreen], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [lightGreen], the corresponding primary colors. + /// * [greenAccent] and [limeAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor lightGreenAccent = const MaterialAccentColor( _lightGreenAccentPrimaryValue, const { @@ -830,6 +1246,17 @@ class Colors { /// The lime primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -839,9 +1266,10 @@ class Colors { /// /// See also: /// - /// * [limeAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [limeAccent], the corresponding accent colors. + /// * [lightGreen] and [yellow], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor lime = const MaterialColor( _limePrimaryValue, const { @@ -861,6 +1289,17 @@ class Colors { /// The lime accent primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreen.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lightGreenAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -870,9 +1309,10 @@ class Colors { /// /// See also: /// - /// * [lime], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [lime], the corresponding primary colors. + /// * [lightGreenAccent] and [yellowAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor limeAccent = const MaterialAccentColor( _limeAccentPrimaryValue, const { @@ -886,6 +1326,17 @@ class Colors { /// The yellow primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -895,9 +1346,10 @@ class Colors { /// /// See also: /// - /// * [yellowAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [yellowAccent], the corresponding accent colors. + /// * [lime] and [amber], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor yellow = const MaterialColor( _yellowPrimaryValue, const { @@ -917,6 +1369,17 @@ class Colors { /// The yellow accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.lime.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.limeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -926,9 +1389,10 @@ class Colors { /// /// See also: /// - /// * [yellow], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [yellow], the corresponding primary colors. + /// * [limeAccent] and [amberAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor yellowAccent = const MaterialAccentColor( _yellowAccentPrimaryValue, const { @@ -942,6 +1406,17 @@ class Colors { /// The amber primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -951,9 +1426,10 @@ class Colors { /// /// See also: /// - /// * [amberAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [amberAccent], the corresponding accent colors. + /// * [yellow] and [orange], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor amber = const MaterialColor( _amberPrimaryValue, const { @@ -973,6 +1449,17 @@ class Colors { /// The amber accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellow.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.yellowAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -982,9 +1469,10 @@ class Colors { /// /// See also: /// - /// * [amber], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [amber], the corresponding primary colors. + /// * [yellowAccent] and [orangeAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor amberAccent = const MaterialAccentColor( _amberAccentPrimaryValue, const { @@ -998,6 +1486,19 @@ class Colors { /// The orange primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.brown.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -1007,9 +1508,10 @@ class Colors { /// /// See also: /// - /// * [orangeAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [orangeAccent], the corresponding accent colors. + /// * [amber], [deepOrange], and [brown], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor orange = const MaterialColor( _orangePrimaryValue, const { @@ -1029,6 +1531,17 @@ class Colors { /// The orange accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amber.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.amberAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -1038,9 +1551,10 @@ class Colors { /// /// See also: /// - /// * [orange], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [orange], the corresponding primary colors. + /// * [amberAccent] and [deepOrangeAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor orangeAccent = const MaterialAccentColor( _orangeAccentPrimaryValue, const { @@ -1054,6 +1568,19 @@ class Colors { /// The deep orange primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.brown.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -1063,9 +1590,10 @@ class Colors { /// /// See also: /// - /// * [deepOrangeAccent], the corresponding accent colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [deepOrangeAccent], the corresponding accent colors. + /// * [orange], [red], and [brown], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor deepOrange = const MaterialColor( _deepOrangePrimaryValue, const { @@ -1085,6 +1613,17 @@ class Colors { /// The deep orange accent color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.deepOrangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orangeAccent.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.red.png) + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.redAccent.png) + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -1094,9 +1633,10 @@ class Colors { /// /// See also: /// - /// * [deepOrange], the corresponding primary colors. - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [deepOrange], the corresponding primary colors. + /// * [orangeAccent] [redAccent], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialAccentColor deepOrangeAccent = const MaterialAccentColor( _deepOrangeAccentPrimaryValue, const { @@ -1110,6 +1650,16 @@ class Colors { /// The brown primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.brown.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.orange.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueGrey.png) + /// + /// This swatch has no corresponding accent color and swatch. + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -1117,12 +1667,11 @@ class Colors { /// ), /// ``` /// - /// This swatch has no corresponding accent color and swatch. - /// /// See also: /// - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [orange] and [blueGrey], vaguely similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor brown = const MaterialColor( _brownPrimaryValue, const { @@ -1142,12 +1691,11 @@ class Colors { /// The grey primary color and swatch. /// - /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.grey[400], - /// ), - /// ``` + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.grey.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueGrey.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.brown.png) /// /// This swatch has no corresponding accent swatch. /// @@ -1156,10 +1704,22 @@ class Colors { /// used for raised button while pressed in light themes, and 850 is used for /// the background color of the dark theme. See [ThemeData.brightness]. /// + /// ## Sample code + /// + /// ```dart + /// new Icon( + /// icon: Icons.widgets, + /// color: Colors.grey[400], + /// ), + /// ``` + /// /// See also: /// - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [blueGrey] and [brown], somewhat similar colors. + /// * [black], [black87], [black54], [black45], [black38], [black26], [black12], which + /// provide a different approach to showing shades of grey. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor grey = const MaterialColor( _greyPrimaryValue, const { @@ -1181,6 +1741,18 @@ class Colors { /// The blue-grey primary color and swatch. /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blueGrey.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.grey.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.cyan.png) + /// + /// ![](https://flutter.github.io/assets-for-api-docs/material/Colors.blue.png) + /// + /// This swatch has no corresponding accent swatch. + /// + /// ## Sample code + /// /// ```dart /// new Icon( /// icon: Icons.widgets, @@ -1188,12 +1760,11 @@ class Colors { /// ), /// ``` /// - /// This swatch has no corresponding accent swatch. - /// /// See also: /// - /// * [Theme.of], which allows you to select colors from the current theme - /// rather than hard-coding colors in your build methods. + /// * [grey], [cyan], and [blue], similar colors. + /// * [Theme.of], which allows you to select colors from the current theme + /// rather than hard-coding colors in your build methods. static const MaterialColor blueGrey = const MaterialColor( _blueGreyPrimaryValue, const { @@ -1211,7 +1782,7 @@ class Colors { ); static const int _blueGreyPrimaryValue = 0xFF607D8B; - /// The material design primary color swatches (except grey). + /// The material design primary color swatches, excluding grey. static const List primaries = const [ red, pink, @@ -1230,7 +1801,9 @@ class Colors { orange, deepOrange, brown, - // grey intentionally omitted + // The grey swatch is intentionally omitted because when picking a color + // randomly from this list to colorize an application, picking grey suddenly + // makes the app look disabled. blueGrey, ]; diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index 969d312283c01..b1a34844e67ab 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -34,6 +34,9 @@ import 'theme.dart'; /// [secondary] widget is placed on the opposite side. This maps to the /// [ListTile.leading] and [ListTile.trailing] properties of [ListTile]. /// +/// To show the [RadioListTile] as disabled, pass null as the [onChanged] +/// callback. +/// /// ## Sample code /// /// This widget shows a pair of radio buttons that control the `_character` diff --git a/packages/flutter/lib/src/material/switch_list_tile.dart b/packages/flutter/lib/src/material/switch_list_tile.dart index 1504f6d7a94a4..eb12eb61fe512 100644 --- a/packages/flutter/lib/src/material/switch_list_tile.dart +++ b/packages/flutter/lib/src/material/switch_list_tile.dart @@ -32,6 +32,9 @@ import 'theme.dart'; /// [ListTile.leading] slot. This cannot be changed; there is not sufficient /// space in a [ListTile]'s [ListTile.leading] slot for a [Switch]. /// +/// To show the [SwitchListTile] as disabled, pass null as the [onChanged] +/// callback. +/// /// ## Sample code /// /// This widget shows a switch that, when toggled, changes the state of a [bool] diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index 5b4a47aceb6f0..19af89bd2a94b 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -1411,7 +1411,7 @@ class DecorationImage { /// /// The [BoxDecoration] class provides a variety of ways to draw a box. /// -/// The box has a [border], a body, and may cast a [shadow]. +/// The box has a [border], a body, and may cast a [boxShadow]. /// /// The [shape] of the box can be a circle or a rectangle. If it is a rectangle, /// then the [borderRadius] property controls the roundness of the corners. @@ -1421,7 +1421,7 @@ class DecorationImage { /// the box. Finally there is the [image], the precise alignment of which is /// controlled by the [DecorationImage] class. /// -/// The [border] paints over the body; the [shadow], naturally, paints below it. +/// The [border] paints over the body; the [boxShadow], naturally, paints below it. /// /// ## Sample code /// diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 2e46c1914791a..8622994a44090 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -58,7 +58,32 @@ class TextSelectionPoint { } } +/// Displays some text in a scrollable container with a potentially blinking +/// cursor and with gesture recognizers. +/// +/// This is the renderer for an editable text field. It does not directly +/// provide affordances for editing the text, but it does handle text selection +/// and manipulation of the text cursor. +/// +/// The [text] is displayed, scrolled by the given [offset], aligned according +/// to [textAlign]. The [maxLines] property controls whether the text displays +/// on one line or many. The [selection], if it is not collapsed, is painted in +/// the [selectionColor]. If it _is_ collapsed, then it represents the cursor +/// position. The cursor is shown while [showCursor] is true. It is painted in +/// the [cursorColor]. +/// +/// If, when the render object paints, the caret is found to have changed +/// location, [onCaretChanged] is called. +/// +/// The user may interact with the render object by tapping or long-pressing. +/// When the user does so, the selection is updated, and [onSelectionChanged] is +/// called. +/// +/// Keyboard handling, IME handling, scrolling, toggling the [showCursor] value +/// to actually blink the cursor, and other features not mentioned above are the +/// responsibility of higher layers and not handled by this object. class RenderEditable extends RenderBox { + /// Creates a render object that implements the visual aspects of a text field. RenderEditable({ TextSpan text, TextAlign textAlign, diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 0eed843453801..b5fd189f3dde5 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -1233,8 +1233,8 @@ class PipelineOwner { /// Update the semantics for render objects marked as needing a semantics /// update. /// - /// Initially, only the root node, as scheduled by [scheduleInitialSemantics], - /// needs a semantics update. + /// Initially, only the root node, as scheduled by + /// [RenderObjectscheduleInitialSemantics], needs a semantics update. /// /// This function is one of the core stages of the rendering pipeline. The /// semantics are compiled after painting and only after diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 23474cf0bb965..757db32661aa2 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -1509,7 +1509,7 @@ class RenderTransform extends RenderProxyBox { /// child as it is painted. When set to false, hit tests are performed /// ignoring the transformation. /// - /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(), + /// [applyPaintTransform], and therefore [localToGlobal] and [globalToLocal], /// always honor the transformation, regardless of the value of this property. bool transformHitTests; @@ -2890,12 +2890,14 @@ class RenderSemanticsAnnotations extends RenderProxyBox { } } -/// Causes the semantics of all siblings and cousins painted before it in the -/// same semantic container to be dropped. +/// Causes the semantics of all earlier render objects below the same semantic +/// boundary to be dropped. /// -/// This is useful in a stack where an overlay should prevent interactions -/// with the underlying layers. +/// This is useful in a stack where an opaque mask should prevent interactions +/// with the render objects painted below the mask. class RenderBlockSemantics extends RenderProxyBox { + /// Create a render object that blocks semantics for nodes below it in paint + /// order. RenderBlockSemantics({ RenderBox child }) : super(child); @override diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 498f133216aef..e969582f6a65c 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart @@ -35,6 +35,7 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje /// child changes its intrinsic dimensions. double get minExtent; + /// The dimension of the child in the main axis. @protected double get childExtent { if (child == null) diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart index 306435b7be3bb..1f10d738ff30a 100644 --- a/packages/flutter/lib/src/services/image_provider.dart +++ b/packages/flutter/lib/src/services/image_provider.dart @@ -19,6 +19,13 @@ import 'image_stream.dart'; /// Configuration information passed to the [ImageProvider.resolve] method to /// select a specific image. +/// +/// See also: +/// +/// * [createLocalImageConfiguration], which creates an [ImageConfiguration] +/// based on ambient configuration in a [Widget] environment. +/// * [ImageProvider], which uses [ImageConfiguration] objects to determine +/// which image to obtain. @immutable class ImageConfiguration { /// Creates an object holding the configuration information for an [ImageProvider]. @@ -149,6 +156,80 @@ class ImageConfiguration { /// /// The type argument does not have to be specified when using the type as an /// argument (where any image provider is acceptable). +/// +/// ## Sample code +/// +/// The following shows the code required to write a widget that fully conforms +/// to the [ImageProvider] and [Widget] protocols. +/// +/// ```dart +/// class Picture extends StatefulWidget { +/// const Picture({ +/// Key key, +/// @required this.imageProvider, +/// }) : assert(imageProvider != null), +/// super(key: key); +/// +/// final ImageProvider imageProvider; +/// +/// @override +/// _PictureState createState() => new _PictureState(); +/// } +/// +/// class _PictureState extends State { +/// ImageStream _imageStream; +/// ImageInfo _imageInfo; +/// +/// @override +/// void didChangeDependencies() { +/// super.didChangeDependencies(); +/// // We call _getImage here because createLocalImageConfiguration() needs to +/// // be called again if the dependencies changed, in case the changes relate +/// // to the DefaultAssetBundle, MediaQuery, etc, which that method uses. +/// _getImage(); +/// } +/// +/// @override +/// void didUpdateWidget(Picture oldWidget) { +/// super.didUpdateWidget(oldWidget); +/// if (widget.imageProvider != oldWidget.imageProvider) +/// _getImage(); +/// } +/// +/// void _getImage() { +/// final ImageStream oldImageStream = _imageStream; +/// _imageStream = widget.imageProvider.resolve(createLocalImageConfiguration(context)); +/// if (_imageStream.key != oldImageStream?.key) { +/// // If the keys are the same, then we got the same image back, and so we don't +/// // need to update the listeners. If the key changed, though, we must make sure +/// // to switch our listeners to the new image stream. +/// oldImageStream?.removeListener(_updateImage); +/// _imageStream.addListener(_updateImage); +/// } +/// } +/// +/// void _updateImage(ImageInfo imageInfo, bool synchronousCall) { +/// setState(() { +/// // Trigger a build whenever the image changes. +/// _imageInfo = imageInfo; +/// }); +/// } +/// +/// @override +/// void dispose() { +/// _imageStream.removeListener(_updateImage); +/// super.dispose(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return new RawImage( +/// image: _imageInfo?.image, // this is a dart:ui Image object +/// scale: _imageInfo?.scale ?? 1.0, +/// ); +/// } +/// } +/// ``` @optionalTypeArgs abstract class ImageProvider { /// Abstract const constructor. This constructor enables subclasses to provide diff --git a/packages/flutter/lib/src/services/image_stream.dart b/packages/flutter/lib/src/services/image_stream.dart index f6a1ca57253a7..55a3cdfee36ce 100644 --- a/packages/flutter/lib/src/services/image_stream.dart +++ b/packages/flutter/lib/src/services/image_stream.dart @@ -44,10 +44,14 @@ class ImageInfo { /// Signature for callbacks reporting that an image is available. /// -/// synchronousCall is true if the listener is being invoked during the call -/// to addListener. -/// /// Used by [ImageStream]. +/// +/// The `synchronousCall` argument is true if the listener is being invoked +/// during the call to addListener. This can be useful if, for example, +/// [ImageStream.addListener] is invoked during a frame, so that a new rendering +/// frame is requested if the call was asynchronous (after the current frame) +/// and no rendering frame is requested if the call was synchronous (within the +/// same stack frame as the call to [ImageStream.addListener]). typedef void ImageListener(ImageInfo image, bool synchronousCall); /// A handle to an image resource. @@ -61,6 +65,11 @@ typedef void ImageListener(ImageInfo image, bool synchronousCall); /// loading. /// /// ImageStream objects are backed by [ImageStreamCompleter] objects. +/// +/// See also: +/// +/// * [ImageProvider], which has an example that includes the use of an +/// [ImageStream] in a [Widget]. class ImageStream { /// Create an initially unbound image stream. /// diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart index 8c64b889a4a19..a0231e47201ef 100644 --- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart +++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart @@ -38,6 +38,26 @@ enum CrossFadeState { /// width. In the case where the two children have different heights, the /// animation crops overflowing children during the animation by aligning their /// top edge, which means that the bottom will be clipped. +/// +/// ## Sample code +/// +/// This code fades between two representations of the Flutter logo. It depends +/// on a global boolean `_on`; when `_on` is true, the first logo is shown, +/// otherwise the second logo is shown. +/// +/// ```dart +/// new AnimatedCrossFade( +/// duration: const Duration(seconds: 3), +/// firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0), +/// secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0), +/// crossFadeState: _on ? CrossFadeState.showFirst : CrossFadeState.showSecond, +/// ) +/// ``` +/// +/// See also: +/// +/// * [AnimatedSize], the lower-level widget which [AnimatedCrossFade] uses to +/// automatically change size. class AnimatedCrossFade extends StatefulWidget { /// Creates a cross-fade animation widget. /// @@ -99,6 +119,14 @@ class AnimatedCrossFade extends StatefulWidget { @override _AnimatedCrossFadeState createState() => new _AnimatedCrossFadeState(); + + @override + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('$crossFadeState'); + if (alignment != FractionalOffset.topCenter) + description.add('alignment: $alignment'); + } } class _AnimatedCrossFadeState extends State with TickerProviderStateMixin { @@ -212,4 +240,13 @@ class _AnimatedCrossFadeState extends State with TickerProvid ), ); } + + @override + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('${widget.crossFadeState}'); + description.add('$_controller'); + if (widget.alignment != FractionalOffset.topCenter) + description.add('alignment: ${widget.alignment}'); + } } diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index ce45f9cc13b73..f2a75a75f8967 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -80,6 +80,8 @@ export 'package:flutter/rendering.dart' show /// See also: /// /// * [ShaderMask], which can apply more elaborate effects to its child. +/// * [Transform], which applies an arbitrary transform to its child widget at +/// paint time. class Opacity extends SingleChildRenderObjectWidget { /// Creates a widget that makes its child partially transparent. /// @@ -147,6 +149,7 @@ class Opacity extends SingleChildRenderObjectWidget { /// * [Opacity], which can apply a uniform alpha effect to its child. /// * [CustomPaint], which lets you draw directly on the canvas. /// * [DecoratedBox], for another approach at decorating child widgets. +/// * [BackdropFilter], which applies an image filter to the background. class ShaderMask extends SingleChildRenderObjectWidget { /// Creates a widget that applies a mask generated by a [Shader] to its child. /// @@ -196,6 +199,11 @@ class ShaderMask extends SingleChildRenderObjectWidget { /// /// This effect is relatively expensive, especially if the filter is non-local, /// such as a blur. +/// +/// See also: +/// +/// * [DecoratedBox], which draws a background under (or over) a widget. +/// * [Opacity], which changes the opacity of the widget itself. class BackdropFilter extends SingleChildRenderObjectWidget { /// Creates a backdrop filter. /// @@ -547,6 +555,11 @@ class ClipPath extends SingleChildRenderObjectWidget { /// /// Physical layers cast shadows based on an [elevation] which is nominally in /// logical pixels, coming vertically out of the rendering surface. +/// +/// See also: +/// +/// * [DecoratedBox], which can apply more arbitrary shadow effects. +/// * [ClipRect], which applies a clip to its child. class PhysicalModel extends SingleChildRenderObjectWidget { /// Creates a physical model with a rounded-rectangular clip. /// @@ -607,6 +620,32 @@ class PhysicalModel extends SingleChildRenderObjectWidget { // POSITIONING AND SIZING NODES /// A widget that applies a transformation before painting its child. +/// +/// ## Sample code +/// +/// This example rotates and skews an orange box containing text, keeping the +/// top right corner pinned to its original position. +/// +/// ```dart +/// new Container( +/// color: Colors.black, +/// child: new Transform( +/// alignment: FractionalOffset.topRight, +/// transform: new Matrix4.skewY(0.3)..rotateZ(-math.PI / 12.0), +/// child: new Container( +/// padding: const EdgeInsets.all(8.0), +/// color: const Color(0xFFE8581C), +/// child: const Text('Apartment for rent!'), +/// ), +/// ), +/// ) +/// ``` +/// +/// See also: +/// * [RotatedBox], which rotates the child widget during layout, not just +/// during painting. +/// * [FittedBox], which sizes and positions its child widget to fit the parent +/// according to a given [BoxFit] discipline. class Transform extends SingleChildRenderObjectWidget { /// Creates a widget that transforms its child. /// @@ -617,10 +656,41 @@ class Transform extends SingleChildRenderObjectWidget { this.origin, this.alignment, this.transformHitTests: true, - Widget child + Widget child, }) : assert(transform != null), super(key: key, child: child); + /// Creates a widget that transforms its child using a rotation around the + /// center. + /// + /// The `angle` argument must not be null. It gives the rotation in clockwise + /// radians. + /// + /// ## Sample code + /// + /// This example rotates an orange box containing text around its center by + /// fifteen degrees. + /// + /// ```dart + /// new Transform.rotate( + /// angle: -math.PI / 12.0, + /// child: new Container( + /// padding: const EdgeInsets.all(8.0), + /// color: const Color(0xFFE8581C), + /// child: const Text('Apartment for rent!'), + /// ), + /// ) + /// ``` + Transform.rotate({ + Key key, + @required double angle, + this.origin, + this.alignment: FractionalOffset.center, + this.transformHitTests: true, + Widget child, + }) : transform = new Matrix4.rotationZ(angle), + super(key: key, child: child); + /// The matrix to transform the child by during painting. final Matrix4 transform; @@ -659,6 +729,11 @@ class Transform extends SingleChildRenderObjectWidget { } /// Scales and positions its child within itself according to [fit]. +/// +/// See also: +/// +/// * [Transform], which applies an arbitrary transform to its child widget at +/// paint time. class FittedBox extends SingleChildRenderObjectWidget { /// Creates a widget that scales and positions its child within itself according to [fit]. /// diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index f6649822e1845..6bc6363af51f1 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -27,6 +27,50 @@ export 'dart:ui' show AppLifecycleState, Locale; /// handlers, or can used with the `implements` keyword, in which case all the /// handlers must be implemented (and the analyzer will list those that have /// been omitted). +/// +/// ## Sample code +/// +/// This [StatefulWidget] implements the parts of the [State] and +/// [WidgetsBindingObserver] protocols necessary to react to application +/// lifecycle messages. See [didChangeAppLifecycleState]. +/// +/// ```dart +/// class Reactor extends StatefulWidget { +/// const Reactor({ Key key }) : super(key: key); +/// +/// @override +/// _ReactorState createState() => new _ReactorState(); +/// } +/// +/// class _ReactorState extends State with WidgetsBindingObserver { +/// @override +/// void initState() { +/// super.initState(); +/// WidgetsBinding.instance.addObserver(this); +/// } +/// +/// @override +/// void dispose() { +/// WidgetsBinding.instance.removeObserver(this); +/// super.dispose(); +/// } +/// +/// AppLifecycleState _notification; +/// +/// @override +/// void didChangeAppLifecycleState(AppLifecycleState state) { +/// setState(() { _notification = state; }); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return new Text('Last notification: $_notification'); +/// } +/// } +/// ``` +/// +/// To respond to other notifications, replace the [didChangeAppLifecycleState] +/// method above with other methods from this class. abstract class WidgetsBindingObserver { /// Called when the system tells the app to pop the current route. /// For example, on Android, this is called when the user presses @@ -52,6 +96,56 @@ abstract class WidgetsBindingObserver { /// Called when the application's dimensions change. For example, /// when a phone is rotated. + /// + /// ## Sample code + /// + /// This [StatefulWidget] implements the parts of the [State] and + /// [WidgetsBindingObserver] protocols necessary to react when the device is + /// rotated (or otherwise changes dimensions). + /// + /// ```dart + /// class Reactor extends StatefulWidget { + /// const Reactor({ Key key }) : super(key: key); + /// + /// @override + /// _ReactorState createState() => new _ReactorState(); + /// } + /// + /// class _ReactorState extends State with WidgetsBindingObserver { + /// @override + /// void initState() { + /// super.initState(); + /// WidgetsBinding.instance.addObserver(this); + /// } + /// + /// @override + /// void dispose() { + /// WidgetsBinding.instance.removeObserver(this); + /// super.dispose(); + /// } + /// + /// Size _lastSize; + /// + /// @override + /// void didChangeMetrics() { + /// setState(() { _lastSize = ui.window.physicalSize; }); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return new Text('Last size: $_lastSize'); + /// } + /// } + /// ``` + /// + /// In general, this is unnecessary as the layout system takes care of + /// automatically recomputing the application geometry when the application + /// size changes. + /// + /// See also: + /// + /// * [MediaQuery.of], which provides a similar service with less + /// boilerplate. void didChangeMetrics() { } /// Called when the system tells the app that the user's locale has @@ -61,6 +155,9 @@ abstract class WidgetsBindingObserver { /// Called when the system puts the app in the background or returns /// the app to the foreground. + /// + /// An example of implementing this method is provided in the class-level + /// documentation for the [WidgetsBindingObserver] class. void didChangeAppLifecycleState(AppLifecycleState state) { } /// Called when the system is running low on memory. @@ -159,11 +256,21 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB /// [MediaQuery.of] static method and (implicitly) the /// [InheritedWidget] mechanism to be notified whenever the screen /// size changes (e.g. whenever the screen rotates). + /// + /// See also: + /// + /// * [removeObserver], to release the resources reserved by this method. + /// * [WidgetsBindingObserver], which has an example of using this method. void addObserver(WidgetsBindingObserver observer) => _observers.add(observer); /// Unregisters the given observer. This should be used sparingly as /// it is relatively expensive (O(N) in the number of registered /// observers). + /// + /// See also: + /// + /// * [addObserver], for the method that adds observers in the first place. + /// * [WidgetsBindingObserver], which has an example of using this method. bool removeObserver(WidgetsBindingObserver observer) => _observers.remove(observer); /// Called when the system metrics change. diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart index 53f93bff09615..98c0eec85fd5a 100644 --- a/packages/flutter/lib/src/widgets/container.dart +++ b/packages/flutter/lib/src/widgets/container.dart @@ -146,7 +146,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget { /// The [constraints] are set to fit the font size plus ample headroom /// vertically, while expanding horizontally to fit the parent. The [padding] is /// used to make sure there is space between the contents and the text. The -/// [color] makes the box teal. The [alignment] causes the [child] to be +/// `color` makes the box teal. The [alignment] causes the [child] to be /// centered in the box. The [foregroundDecoration] overlays a nine-patch image /// onto the text. Finally, the [transform] applies a slight rotation to the /// entire contraption to complete the effect. diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 3a7fb14c8f6d6..a0f9fd8fee75c 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1452,7 +1452,7 @@ abstract class ParentDataWidget extends ProxyWidge /// /// Sometimes, the `of` method returns the data rather than the inherited /// widget; for example, in this case it could have returned a [Color] instead -/// of the [FrogColor] widget. +/// of the `FrogColor` widget. /// /// Occasionally, the inherited widget is an implementation detail of another /// class, and is therefore private. The `of` method in that case is typically diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart index 2a3dfac9a3649..ee4011fa44bff 100644 --- a/packages/flutter/lib/src/widgets/image.dart +++ b/packages/flutter/lib/src/widgets/image.dart @@ -25,6 +25,15 @@ export 'package:flutter/services.dart' show /// /// This is the object that must be passed to [BoxPainter.paint] and to /// [ImageProvider.resolve]. +/// +/// If this is not called from a build method, then it should be reinvoked +/// whenever the dependencies change, e.g. by calling it from +/// [State.didChangeDependencies], so that any changes in the environement are +/// picked up (e.g. if the device pixel ratio changes). +/// +/// See also: +/// +/// * [ImageProvider], which has an example showing how this might be used. ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) { return new ImageConfiguration( bundle: DefaultAssetBundle.of(context), diff --git a/packages/flutter/lib/src/widgets/navigation_toolbar.dart b/packages/flutter/lib/src/widgets/navigation_toolbar.dart index 5a4ef6af2ada7..a03497c76b890 100644 --- a/packages/flutter/lib/src/widgets/navigation_toolbar.dart +++ b/packages/flutter/lib/src/widgets/navigation_toolbar.dart @@ -13,7 +13,7 @@ import 'framework.dart'; /// widgets along a horizontal axis that's sensible for an application's /// navigation bar such as in Material Design and in iOS. /// -/// [leading] and [trailing] widgets occupy the edges of the widget with +/// The [leading] and [trailing] widgets occupy the edges of the widget with /// reasonable size constraints while the [middle] widget occupies the remaining /// space in either a center aligned or start aligned fashion. /// @@ -21,6 +21,8 @@ import 'framework.dart'; /// the iOS [CupertinoNavigationBar] or wrap this widget with more theming /// specifications for your own custom app bar. class NavigationToolbar extends StatelessWidget { + /// Creates a widget that lays out its children in a manner suitable for a + /// toolbar. const NavigationToolbar({ Key key, this.leading, diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index a68905bb8597d..05e2c9f26a1cf 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -25,6 +25,11 @@ import 'sliver.dart'; import 'ticker_provider.dart'; /// Signature used by [NestedScrollView] for building its header. +/// +/// The `innerBoxIsScrolled` argument is typically used to control the +/// [SliverAppBar.forceElevated] property to ensure that the app bar shows a +/// shadow, since it would otherwise not necessarily be aware that it had +/// content ostensibly below it. typedef List NestedScrollViewHeaderSliversBuilder(BuildContext context, bool innerBoxIsScrolled); // TODO(abarth): Make this configurable with a controller. @@ -73,8 +78,18 @@ class NestedScrollView extends StatefulWidget { /// Defaults to matching platform conventions. final ScrollPhysics physics; + /// A builder for any widgets that are to precede the inner scroll views (as + /// given by [body]). + /// + /// Typically this is used to create a [SliverAppBar] with a [TabBar]. final NestedScrollViewHeaderSliversBuilder headerSliverBuilder; + /// The widget to show inside the [NestedScrollView]. + /// + /// Typically this will be [TabBarView]. + /// + /// The [body] is built in a context that provides a [PrimaryScrollController] + /// that interacts with the [NestedScrollView]'s scroll controller. final Widget body; List _buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) { diff --git a/packages/flutter/test/widgets/transform_test.dart b/packages/flutter/test/widgets/transform_test.dart index eb8c7cbdd732d..928b209005b8c 100644 --- a/packages/flutter/test/widgets/transform_test.dart +++ b/packages/flutter/test/widgets/transform_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; + import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -171,4 +173,26 @@ void main() { final Matrix4 transform = layer.transform; expect(transform.getTranslation(), equals(new Vector3(100.0, 75.0, 0.0))); }); + + testWidgets('Transform.rotate', (WidgetTester tester) async { + await tester.pumpWidget( + new Transform.rotate( + angle: math.PI / 2.0, + child: new Opacity(opacity: 0.5, child: new Container()), + ), + ); + + final List layers = tester.layers + ..retainWhere((Layer layer) => layer is TransformLayer); + expect(layers.length, 2); + // The first transform is from the render view. + final TransformLayer layer = layers[1]; + final Matrix4 transform = layer.transform; + expect(transform.storage, [ + moreOrLessEquals(0.0), 1.0, 0.0, 0.0, + -1.0, moreOrLessEquals(0.0), 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 700.0, -100.0, 0.0, 1.0, + ]); + }); } diff --git a/packages/flutter_driver/lib/src/health.dart b/packages/flutter_driver/lib/src/health.dart index a146570ffea9d..da10a27e89363 100644 --- a/packages/flutter_driver/lib/src/health.dart +++ b/packages/flutter_driver/lib/src/health.dart @@ -16,7 +16,6 @@ class GetHealth extends Command { GetHealth.deserialize(Map json) : super.deserialize(json); } -/// Application health status. enum HealthStatus { /// Application is known to be in a good shape and should be able to respond. ok, @@ -28,7 +27,6 @@ enum HealthStatus { final EnumIndex _healthStatusIndex = new EnumIndex(HealthStatus.values); -/// Application health status. class Health extends Result { /// Creates a [Health] object with the given [status]. Health(this.status) { @@ -40,7 +38,6 @@ class Health extends Result { return new Health(_healthStatusIndex.lookupBySimpleName(json['status'])); } - /// Health status final HealthStatus status; @override diff --git a/packages/flutter_test/lib/src/all_elements.dart b/packages/flutter_test/lib/src/all_elements.dart index b0db191d659a7..9cdca78e4669e 100644 --- a/packages/flutter_test/lib/src/all_elements.dart +++ b/packages/flutter_test/lib/src/all_elements.dart @@ -17,7 +17,7 @@ import 'package:flutter/widgets.dart'; /// one, for example the results of calling `where` on this iterable /// are also cached. Iterable collectAllElementsFrom(Element rootElement, { - @required bool skipOffstage + @required bool skipOffstage, }) { return new CachingIterable(new _DepthFirstChildIterator(rootElement, skipOffstage)); } diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index ee8ff34efeaff..b200029c54ce8 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -89,6 +89,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase // Services binding omitted to avoid dragging in the licenses code. WidgetsBinding { + /// Constructor for [TestWidgetsFlutterBinding]. + /// + /// This constructor overrides the [debugPrint] global hook to point to + /// [debugPrintOverride], which can be overridden by subclasses. TestWidgetsFlutterBinding() { debugPrint = debugPrintOverride; } diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index 90a136b656621..1e111c086f1f3 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -236,6 +236,9 @@ abstract class Finder { /// [Offstage] widgets, as well as children of inactive [Route]s. final bool skipOffstage; + /// Returns all the [Element]s that will be considered by this finder. + /// + /// See [collectAllElementsFrom]. @protected Iterable get allCandidates { return collectAllElementsFrom( From 4a92f631bea0f8aff993cd73c26c50815ffe7985 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 5 Jun 2017 23:32:51 -0700 Subject: [PATCH 042/110] Rename Picture example to MyImage (#10522) --- .../flutter/lib/src/services/image_provider.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart index 1f10d738ff30a..6d4644fd93513 100644 --- a/packages/flutter/lib/src/services/image_provider.dart +++ b/packages/flutter/lib/src/services/image_provider.dart @@ -160,11 +160,12 @@ class ImageConfiguration { /// ## Sample code /// /// The following shows the code required to write a widget that fully conforms -/// to the [ImageProvider] and [Widget] protocols. +/// to the [ImageProvider] and [Widget] protocols. (It is essentially a +/// bare-bones version of the [Image] widget.) /// /// ```dart -/// class Picture extends StatefulWidget { -/// const Picture({ +/// class MyImage extends StatefulWidget { +/// const MyImage({ /// Key key, /// @required this.imageProvider, /// }) : assert(imageProvider != null), @@ -173,10 +174,10 @@ class ImageConfiguration { /// final ImageProvider imageProvider; /// /// @override -/// _PictureState createState() => new _PictureState(); +/// _MyImageState createState() => new _MyImageState(); /// } /// -/// class _PictureState extends State { +/// class _MyImageState extends State { /// ImageStream _imageStream; /// ImageInfo _imageInfo; /// @@ -190,7 +191,7 @@ class ImageConfiguration { /// } /// /// @override -/// void didUpdateWidget(Picture oldWidget) { +/// void didUpdateWidget(MyImage oldWidget) { /// super.didUpdateWidget(oldWidget); /// if (widget.imageProvider != oldWidget.imageProvider) /// _getImage(); From 0f277fcc8a699a90f8182240f63837e5f9491bcd Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Tue, 6 Jun 2017 17:19:27 +0200 Subject: [PATCH 043/110] Idea enable the plugin template (#10429) --- .../plugin/.idea/libraries/Dart_SDK.xml.tmpl | 25 ++++++++++++ .../libraries/Flutter_for_Android.xml.tmpl | 9 +++++ .../templates/plugin/.idea/modules.xml.tmpl | 9 +++++ .../runConfigurations/main_dart.xml.tmpl | 6 +++ .../templates/plugin/.idea/workspace.xml.tmpl | 38 +++++++++++++++++++ .../templates/plugin/projectName.iml.tmpl | 15 ++++++++ .../plugin/projectName_android.iml.tmpl | 12 ++++++ 7 files changed, 114 insertions(+) create mode 100644 packages/flutter_tools/templates/plugin/.idea/libraries/Dart_SDK.xml.tmpl create mode 100644 packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl create mode 100644 packages/flutter_tools/templates/plugin/.idea/modules.xml.tmpl create mode 100644 packages/flutter_tools/templates/plugin/.idea/runConfigurations/main_dart.xml.tmpl create mode 100644 packages/flutter_tools/templates/plugin/.idea/workspace.xml.tmpl create mode 100644 packages/flutter_tools/templates/plugin/projectName.iml.tmpl create mode 100644 packages/flutter_tools/templates/plugin/projectName_android.iml.tmpl diff --git a/packages/flutter_tools/templates/plugin/.idea/libraries/Dart_SDK.xml.tmpl b/packages/flutter_tools/templates/plugin/.idea/libraries/Dart_SDK.xml.tmpl new file mode 100644 index 0000000000000..4c3c50cc99e9b --- /dev/null +++ b/packages/flutter_tools/templates/plugin/.idea/libraries/Dart_SDK.xml.tmpl @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl b/packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl new file mode 100644 index 0000000000000..2fac6b7982477 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/flutter_tools/templates/plugin/.idea/modules.xml.tmpl b/packages/flutter_tools/templates/plugin/.idea/modules.xml.tmpl new file mode 100644 index 0000000000000..cd521752880e1 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/.idea/modules.xml.tmpl @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/flutter_tools/templates/plugin/.idea/runConfigurations/main_dart.xml.tmpl b/packages/flutter_tools/templates/plugin/.idea/runConfigurations/main_dart.xml.tmpl new file mode 100644 index 0000000000000..5fd9159d1c5ba --- /dev/null +++ b/packages/flutter_tools/templates/plugin/.idea/runConfigurations/main_dart.xml.tmpl @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/packages/flutter_tools/templates/plugin/.idea/workspace.xml.tmpl b/packages/flutter_tools/templates/plugin/.idea/workspace.xml.tmpl new file mode 100644 index 0000000000000..6310d69ac8721 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/.idea/workspace.xml.tmpl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_tools/templates/plugin/projectName.iml.tmpl b/packages/flutter_tools/templates/plugin/projectName.iml.tmpl new file mode 100644 index 0000000000000..9d5dae19540c2 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/projectName.iml.tmpl @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/flutter_tools/templates/plugin/projectName_android.iml.tmpl b/packages/flutter_tools/templates/plugin/projectName_android.iml.tmpl new file mode 100644 index 0000000000000..8e9d155a82244 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/projectName_android.iml.tmpl @@ -0,0 +1,12 @@ + + + + + + + + + + + + From c8e4cbf27d58bec1342d0287f389926fa0e9d9a6 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Tue, 6 Jun 2017 12:11:21 -0700 Subject: [PATCH 044/110] Improved support for saving and restoring the scroll offset, etc (#10517) --- .../catalog/lib/expansion_tile_sample.dart | 2 +- .../flutter/lib/src/widgets/framework.dart | 6 +- .../flutter/lib/src/widgets/page_storage.dart | 103 +++++++++++------- .../flutter/lib/src/widgets/page_view.dart | 25 ++++- .../lib/src/widgets/scroll_controller.dart | 27 ++++- .../lib/src/widgets/scroll_position.dart | 21 +++- .../scroll_position_with_single_context.dart | 13 ++- .../flutter/test/widgets/page_view_test.dart | 6 +- .../test/widgets/scroll_controller_test.dart | 47 ++++++++ 9 files changed, 196 insertions(+), 54 deletions(-) diff --git a/examples/catalog/lib/expansion_tile_sample.dart b/examples/catalog/lib/expansion_tile_sample.dart index 2213ee18c5520..c028b149fdbb4 100644 --- a/examples/catalog/lib/expansion_tile_sample.dart +++ b/examples/catalog/lib/expansion_tile_sample.dart @@ -76,7 +76,7 @@ class EntryItem extends StatelessWidget { if (root.children.isEmpty) return new ListTile(title: new Text(root.title)); return new ExpansionTile( - key: new ValueKey(root), + key: new PageStorageKey(root), title: new Text(root.title), children: root.children.map(_buildTiles).toList(), ); diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index a0f9fd8fee75c..8666889607b98 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -322,10 +322,10 @@ class LabeledGlobalKey> extends GlobalKey { @override String toString() { - final String tag = _debugLabel != null ? ' $_debugLabel' : '#$hashCode'; + final String label = _debugLabel != null ? ' $_debugLabel' : ''; if (runtimeType == LabeledGlobalKey) - return '[GlobalKey$tag]'; - return '[$runtimeType$tag]'; + return '[GlobalKey#$hashCode$label]'; + return '[$runtimeType#$hashCode$label]'; } } diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 7d71ac87dbf74..c2611e274cad4 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -6,43 +6,67 @@ import 'package:flutter/foundation.dart'; import 'framework.dart'; +/// A [ValueKey] that defines where [PageStorage] values will be saved. +/// +/// [Scrollable]s ([ScrollPosition]s really) use [PageStorage] to save their +/// scroll offset. Each time a scroll completes, the scrollable's page +/// storage is updated. +/// +/// [PageStorage] is used to save and restore values that can outlive the widget. +/// The values are stored in a per-route [Map] whose keys are defined by the +/// [PageStorageKey]s for the widget and its ancestors. To make it possible +/// for a saved value to be found when a widget is recreated, the key's values +/// must not be objects whose identity will change each time the widget is created. +/// +/// For example, to ensure that the scroll offsets for the scrollable within +/// each `MyScrollableTabView` below are restored when the [TabBarView] +/// is recreated, we've specified [PageStorageKey]s whose values are the the +/// tabs' string labels. +/// +/// ```dart +/// new TabBarView( +/// children: myTabs.map((Tab tab) { +/// new MyScrollableTabView( +/// key: new PageStorageKey(tab.text), // like 'Tab 1' +/// tab: tab, +/// ), +/// }), +///) +/// ``` +class PageStorageKey extends ValueKey { + /// Creates a [ValueKey] that defines where [PageStorage] values will be saved. + const PageStorageKey(T value) : super(value); +} + class _StorageEntryIdentifier { - Type clientType; - List keys; - - void addKey(Key key) { - assert(key != null); - assert(key is! GlobalKey); - keys ??= []; - keys.add(key); + _StorageEntryIdentifier(this.clientType, this.keys) { + assert(clientType != null); + assert(keys != null); } - GlobalKey scopeKey; + final Type clientType; + final List> keys; @override bool operator ==(dynamic other) { - if (other is! _StorageEntryIdentifier) + if (other.runtimeType != runtimeType) return false; final _StorageEntryIdentifier typedOther = other; - if (clientType != typedOther.clientType || - scopeKey != typedOther.scopeKey || - keys?.length != typedOther.keys?.length) + if (clientType != typedOther.clientType || keys.length != typedOther.keys.length) return false; - if (keys != null) { - for (int index = 0; index < keys.length; index += 1) { - if (keys[index] != typedOther.keys[index]) - return false; - } + for (int index = 0; index < keys.length; index += 1) { + if (keys[index] != typedOther.keys[index]) + return false; } return true; } @override - int get hashCode => hashValues(clientType, scopeKey, hashList(keys)); + int get hashCode => hashValues(clientType, hashList(keys)); @override String toString() { - return 'StorageEntryIdentifier($clientType, $scopeKey, ${keys?.join(":")})'; + return 'StorageEntryIdentifier($clientType, ${keys?.join(":")})'; } } @@ -51,27 +75,26 @@ class _StorageEntryIdentifier { /// Useful for storing per-page state that persists across navigations from one /// page to another. class PageStorageBucket { - _StorageEntryIdentifier _computeStorageIdentifier(BuildContext context) { - final _StorageEntryIdentifier result = new _StorageEntryIdentifier(); - result.clientType = context.widget.runtimeType; - Key lastKey = context.widget.key; - if (lastKey is! GlobalKey) { - if (lastKey != null) - result.addKey(lastKey); + bool _maybeAddKey(BuildContext context, List> keys) { + final Widget widget = context.widget; + final Key key = widget.key; + if (key is PageStorageKey) + keys.add(key); + return widget is! PageStorage; + } + + List> _allKeys(BuildContext context) { + final List> keys = >[]; + if (_maybeAddKey(context, keys)) { context.visitAncestorElements((Element element) { - if (element.widget.key is GlobalKey) { - lastKey = element.widget.key; - return false; - } else if (element.widget.key != null) { - result.addKey(element.widget.key); - } - return true; + return _maybeAddKey(element, keys); }); - return result; } - assert(lastKey is GlobalKey); - result.scopeKey = lastKey; - return result; + return keys; + } + + _StorageEntryIdentifier _computeIdentifier(BuildContext context) { + return new _StorageEntryIdentifier(context.widget.runtimeType, _allKeys(context)); } Map _storage; @@ -89,13 +112,13 @@ class PageStorageBucket { /// identifier will change. void writeState(BuildContext context, dynamic data, { Object identifier }) { _storage ??= {}; - _storage[identifier ?? _computeStorageIdentifier(context)] = data; + _storage[identifier ?? _computeIdentifier(context)] = data; } /// Read given data from into this page storage bucket using an identifier /// computed from the given context. More about [identifier] in [writeState]. dynamic readState(BuildContext context, { Object identifier }) { - return _storage != null ? _storage[identifier ?? _computeStorageIdentifier(context)] : null; + return _storage != null ? _storage[identifier ?? _computeIdentifier(context)] : null; } } diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 355deff5e4f10..54dba500b886e 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -38,17 +38,36 @@ import 'viewport.dart'; class PageController extends ScrollController { /// Creates a page controller. /// - /// The [initialPage] and [viewportFraction] arguments must not be null. + /// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null. PageController({ this.initialPage: 0, + this.keepPage: true, this.viewportFraction: 1.0, }) : assert(initialPage != null), + assert(keepPage != null), assert(viewportFraction != null), assert(viewportFraction > 0.0); /// The page to show when first creating the [PageView]. final int initialPage; + /// Save the current [page] with [PageStorage] and restore it if + /// this controller's scrollable is recreated. + /// + /// If this property is set to false, the current [page] is never saved + /// and [initialPage] is always used to initialize the scroll offset. + /// If true (the default), the initial page is used the first time the + /// controller's scrollable is created, since there's isn't a page to + /// restore yet. Subsequently the saved page is restored and + /// [initialPage] is ignored. + /// + /// See also: + /// + /// * [PageStorageKey], which should be used when more than one + //// scrollable appears in the same route, to distinguish the [PageStorage] + /// locations used to save scroll offsets. + final bool keepPage; + /// The fraction of the viewport that each page should occupy. /// /// Defaults to 1.0, which means each page fills the viewport in the scrolling @@ -116,6 +135,7 @@ class PageController extends ScrollController { physics: physics, context: context, initialPage: initialPage, + keepPage: keepPage, viewportFraction: viewportFraction, oldPosition: oldPosition, ); @@ -150,9 +170,11 @@ class _PagePosition extends ScrollPositionWithSingleContext { ScrollPhysics physics, ScrollContext context, this.initialPage: 0, + bool keepPage: true, double viewportFraction: 1.0, ScrollPosition oldPosition, }) : assert(initialPage != null), + assert(keepPage != null), assert(viewportFraction != null), assert(viewportFraction > 0.0), _viewportFraction = viewportFraction, @@ -161,6 +183,7 @@ class _PagePosition extends ScrollPositionWithSingleContext { physics: physics, context: context, initialPixels: null, + keepScrollOffset: keepPage, oldPosition: oldPosition, ); diff --git a/packages/flutter/lib/src/widgets/scroll_controller.dart b/packages/flutter/lib/src/widgets/scroll_controller.dart index d050414652a22..4f8b268f0b092 100644 --- a/packages/flutter/lib/src/widgets/scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/scroll_controller.dart @@ -39,20 +39,40 @@ import 'scroll_position_with_single_context.dart'; class ScrollController extends ChangeNotifier { /// Creates a controller for a scrollable widget. /// - /// The [initialScrollOffset] must not be null. + /// The values of `initialScrollOffset` and `keepScrollOffset` must not be null. ScrollController({ this.initialScrollOffset: 0.0, + this.keepScrollOffset: true, this.debugLabel, - }) : assert(initialScrollOffset != null); + }) : assert(initialScrollOffset != null), + assert(keepScrollOffset != null); /// The initial value to use for [offset]. /// /// New [ScrollPosition] objects that are created and attached to this - /// controller will have their offset initialized to this value. + /// controller will have their offset initialized to this value + /// if [keepScrollOffset] is false or a scroll offset hasn't been saved yet. /// /// Defaults to 0.0. final double initialScrollOffset; + /// Each time a scroll completes, save the current scroll [offset] with + /// [PageStorage] and restore it if this controller's scrollable is recreated. + /// + /// If this property is set to false, the scroll offset is never saved + /// and [initialScrollOffset] is always used to initialize the scroll + /// offset. If true (the default), the initial scroll offset is used the + /// first time the controller's scrollable is created, since there's no + /// scroll offset to restore yet. Subsequently the saved offset is + /// restored and [initialScrollOffset] is ignored. + /// + /// See also: + /// + /// * [PageStorageKey], which should be used when more than one + //// scrollable appears in the same route, to distinguish the [PageStorage] + /// locations used to save scroll offsets. + final bool keepScrollOffset; + /// A label that is used in the [toString] output. Intended to aid with /// identifying scroll controller instances in debug output. final String debugLabel; @@ -204,6 +224,7 @@ class ScrollController extends ChangeNotifier { physics: physics, context: context, initialPixels: initialScrollOffset, + keepScrollOffset: keepScrollOffset, oldPosition: oldPosition, debugLabel: debugLabel, ); diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 3785621a75c65..c96ee6ea2fb93 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -61,17 +61,22 @@ export 'scroll_activity.dart' show ScrollHoldController; abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Creates an object that determines which portion of the content is visible /// in a scroll view. + /// + /// The [physics], [context], and [keepScrollOffset] parameters must not be null. ScrollPosition({ @required this.physics, @required this.context, + this.keepScrollOffset: true, ScrollPosition oldPosition, this.debugLabel, }) : assert(physics != null), assert(context != null), - assert(context.vsync != null) { + assert(context.vsync != null), + assert(keepScrollOffset != null) { if (oldPosition != null) absorb(oldPosition); - restoreScrollOffset(); + if (keepScrollOffset) + restoreScrollOffset(); } /// How the scroll position should respond to user input. @@ -85,6 +90,15 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Typically implemented by [ScrollableState]. final ScrollContext context; + /// Save the current scroll [offset] with [PageStorage] and restore it if + /// this scroll position's scrollable is recreated. + /// + /// See also: + /// + /// * [ScrollController.keepScrollOffset] and [PageController.keepPage], which + /// create scroll positions and initialize this property. + final bool keepScrollOffset; + /// A label that is used in the [toString] output. Intended to aid with /// identifying animation controller instances in debug output. final String debugLabel; @@ -539,7 +553,8 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// This also saves the scroll offset using [saveScrollOffset]. void didEndScroll() { activity.dispatchScrollEndNotification(cloneMetrics(), context.notificationContext); - saveScrollOffset(); + if (keepScrollOffset) + saveScrollOffset(); } /// Called by [setPixels] to report overscroll when an attempt is made to diff --git a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart index 287f739fdda5c..4d7b81ba6521d 100644 --- a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart +++ b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart @@ -46,13 +46,24 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc /// imperative that the value be set, using [correctPixels], as soon as /// [applyNewDimensions] is invoked, before calling the inherited /// implementation of that method. + /// + /// If [keepScrollOffset] is true (the default), the current scroll offset is + /// saved with [PageStorage] and restored it if this scroll position's scrollable + /// is recreated. ScrollPositionWithSingleContext({ @required ScrollPhysics physics, @required ScrollContext context, double initialPixels: 0.0, + bool keepScrollOffset: true, ScrollPosition oldPosition, String debugLabel, - }) : super(physics: physics, context: context, oldPosition: oldPosition, debugLabel: debugLabel) { + }) : super( + physics: physics, + context: context, + keepScrollOffset: keepScrollOffset, + oldPosition: oldPosition, + debugLabel: debugLabel, + ) { // If oldPosition is not null, the superclass will first call absorb(), // which may set _pixels and _activity. if (pixels == null && initialPixels != null) diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index 29a4d17b7f81b..0089e2dd2c827 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -441,12 +441,14 @@ void main() { ), ); expect(controller.page, 2); + + final PageController controller2 = new PageController(keepPage: false); await tester.pumpWidget( new PageStorage( bucket: bucket, child: new PageView( key: const Key('Check it again against your list and see consistency!'), - controller: controller, + controller: controller2, children: [ const Placeholder(), const Placeholder(), @@ -455,6 +457,6 @@ void main() { ), ), ); - expect(controller.page, 0); + expect(controller2.page, 0); }); } diff --git a/packages/flutter/test/widgets/scroll_controller_test.dart b/packages/flutter/test/widgets/scroll_controller_test.dart index f935edcd57904..5ae0e4d5eb0f4 100644 --- a/packages/flutter/test/widgets/scroll_controller_test.dart +++ b/packages/flutter/test/widgets/scroll_controller_test.dart @@ -259,4 +259,51 @@ void main() { await tester.drag(find.byType(ListView), const Offset(0.0, -130.0)); expect(log, isEmpty); }); + + testWidgets('keepScrollOffset', (WidgetTester tester) async { + final PageStorageBucket bucket = new PageStorageBucket(); + + Widget buildFrame(ScrollController controller) { + return new PageStorage( + bucket: bucket, + child: new ListView( + key: new UniqueKey(), // it's a different ListView every time + controller: controller, + children: new List.generate(50, (int index) { + return new Container(height: 100.0, child: new Text('Item $index')); + }).toList(), + ), + ); + } + + // keepScrollOffset: true (the default). The scroll offset is restored + // when the ListView is recreated with a new ScrollController. + + // The initialScrollOffset is used in this case, because there's no saved + // scroll offset. + ScrollController controller = new ScrollController(initialScrollOffset: 200.0); + await tester.pumpWidget(buildFrame(controller)); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 2')), Offset.zero); + + controller.jumpTo(2000.0); + await tester.pump(); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero); + + // The initialScrollOffset isn't used in this case, because the scrolloffset + // can be restored. + controller = new ScrollController(initialScrollOffset: 25.0); + await tester.pumpWidget(buildFrame(controller)); + expect(controller.offset, 2000.0); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero); + + // keepScrollOffset: false. The scroll offset is -not- restored + // when the ListView is recreated with a new ScrollController and + // the initialScrollOffset is used. + + controller = new ScrollController(keepScrollOffset: false, initialScrollOffset: 100.0); + await tester.pumpWidget(buildFrame(controller)); + expect(controller.offset, 100.0); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 1')), Offset.zero); + + }); } From a5aaaa8422cee09bb69150d38ae8a632116e2268 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 6 Jun 2017 12:22:34 -0700 Subject: [PATCH 045/110] =?UTF-8?q?bin/flutter:=20don=E2=80=99t=20warn=20a?= =?UTF-8?q?bout=20running=20as=20root=20within=20Docker=20container=20(#10?= =?UTF-8?q?535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/flutter/flutter/issues/10529 Via https://stackoverflow.com/a/25518345/39827 --- bin/flutter | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/flutter b/bin/flutter index 046e5a474ab20..87708ccdb64c1 100755 --- a/bin/flutter +++ b/bin/flutter @@ -46,8 +46,8 @@ DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk" DART="$DART_SDK_PATH/bin/dart" PUB="$DART_SDK_PATH/bin/pub" -# Test if running as superuser -if [[ "$EUID" == "0" ]]; then +# Test if running as superuser – but don't warn if running within Docker +if [[ "$EUID" == "0" ]] && ! [[ -f /.dockerenv ]]; then echo " Woah! You appear to be trying to run flutter as root." echo " We strongly recommend running the flutter tool without superuser privileges." echo " /" From 3a23419d8440923384a16c11fe687d3b59091027 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 6 Jun 2017 14:07:53 -0700 Subject: [PATCH 046/110] Hide the Scaffold's scroll-to-top button from accessibility (#10539) It's redundant as iOS accessibility automatically includes this action on the clock in the status bar. --- packages/flutter/lib/src/material/scaffold.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 72ab0f7a8dd65..5a337d52a3f8a 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -867,6 +867,8 @@ class ScaffoldState extends State with TickerProviderStateMixin { child: new GestureDetector( behavior: HitTestBehavior.opaque, onTap: _handleStatusBarTap, + // iOS accessibility automatically adds scroll-to-top to the clock in the status bar + excludeFromSemantics: true, ) )); } From 1b56cb790cdc45f8725cb93d3f6ce4c62608da7d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 6 Jun 2017 18:40:32 -0700 Subject: [PATCH 047/110] Add --version-json to flutter CLI (#10538) closes https://github.com/flutter/flutter/issues/10534 --- .../lib/src/runner/flutter_command_runner.dart | 17 ++++++++++++++++- packages/flutter_tools/lib/src/version.dart | 9 +++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 605451f73614c..51f313050d58e 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:args/args.dart'; import 'package:args/command_runner.dart'; @@ -59,6 +60,9 @@ class FlutterCommandRunner extends CommandRunner { argParser.addFlag('version', negatable: false, help: 'Reports the version of this tool.'); + argParser.addFlag('json', + negatable: false, + hide: true); argParser.addFlag('color', negatable: true, hide: !verboseHelp, @@ -255,10 +259,21 @@ class FlutterCommandRunner extends CommandRunner { if (globalResults['version']) { flutterUsage.sendCommand('version'); - printStatus(FlutterVersion.instance.toString()); + String status; + if (globalResults['json']) { + status = const JsonEncoder.withIndent(' ').convert(FlutterVersion.instance.toJson()); + } else { + status = FlutterVersion.instance.toString(); + } + printStatus(status); return; } + if (globalResults['json']) { + printError('The --json flag is only valid with the --version flag.'); + throw new ProcessExit(2); + } + await super.runCommand(globalResults); } diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index e02711ce47428..4246b6176e99d 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -82,6 +82,15 @@ class FlutterVersion { return '$flutterText\n$frameworkText\n$engineText\n$toolsText'; } + Map toJson() => { + 'channel': channel, + 'repositoryUrl': repositoryUrl ?? 'unknown source', + 'frameworkRevision': frameworkRevision, + 'frameworkCommitDate': frameworkCommitDate, + 'engineRevision': engineRevision, + 'dartSdkVersion': dartSdkVersion, + }; + /// A date String describing the last framework commit. String get frameworkCommitDate => _latestGitCommitDate(); From 3e645ee2dbb5265f915776458e096104427e7c59 Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Wed, 7 Jun 2017 15:54:29 +0200 Subject: [PATCH 048/110] Add changelog and required fields to template (#10548) --- packages/flutter_tools/templates/plugin/CHANGELOG.md | 3 +++ packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 packages/flutter_tools/templates/plugin/CHANGELOG.md diff --git a/packages/flutter_tools/templates/plugin/CHANGELOG.md b/packages/flutter_tools/templates/plugin/CHANGELOG.md new file mode 100644 index 0000000000000..ac071598e5d45 --- /dev/null +++ b/packages/flutter_tools/templates/plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl index d1747d74e6ced..09b77b7b4d5a1 100644 --- a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl @@ -1,5 +1,8 @@ name: {{projectName}} description: {{description}} +version: 0.0.1 +author: +homepage: flutter: plugin: From 7d7132636369914402507c7b15d635039193ee46 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Wed, 7 Jun 2017 21:39:47 +0200 Subject: [PATCH 049/110] apply prefer_asserts_in_initializer_list lint (#10540) --- examples/catalog/lib/animated_list.dart | 16 ++++----- .../lib/demo/animation/home.dart | 30 ++++++++-------- .../lib/demo/animation/widgets.dart | 23 ++++++------ .../flutter_gallery/lib/demo/colors_demo.dart | 14 ++++---- .../lib/demo/contacts_demo.dart | 6 ++-- .../lib/demo/material/cards_demo.dart | 6 ++-- .../material/full_screen_dialog_demo.dart | 7 ++-- .../lib/demo/material/grid_list_demo.dart | 9 +++-- .../lib/demo/shrine/shrine_home.dart | 22 ++++++------ .../lib/demo/shrine/shrine_order.dart | 36 +++++++++---------- .../lib/demo/shrine/shrine_page.dart | 7 ++-- .../lib/demo/shrine/shrine_theme.dart | 6 ++-- .../lib/demo/shrine/shrine_types.dart | 9 +++-- .../lib/demo/typography_demo.dart | 9 +++-- .../flutter_gallery/lib/gallery/drawer.dart | 7 ++-- .../flutter_gallery/lib/gallery/home.dart | 7 ++-- .../flutter_gallery/lib/gallery/item.dart | 10 +++--- .../flutter_gallery/lib/gallery/updates.dart | 6 ++-- examples/layers/services/isolate.dart | 17 +++++---- examples/stocks/lib/stock_types.dart | 22 ++++++------ .../lib/src/foundation/basic_types.dart | 13 +++---- .../lib/src/widgets/scroll_simulation.dart | 4 +-- .../layout_builder_mutations_test.dart | 5 ++- .../test/widgets/linked_scroll_view_test.dart | 17 +++++---- packages/flutter_driver/lib/src/health.dart | 5 ++- 25 files changed, 144 insertions(+), 169 deletions(-) diff --git a/examples/catalog/lib/animated_list.dart b/examples/catalog/lib/animated_list.dart index 5742ed511a6c7..6fcba2eddda36 100644 --- a/examples/catalog/lib/animated_list.dart +++ b/examples/catalog/lib/animated_list.dart @@ -117,10 +117,9 @@ class ListModel { @required this.listKey, @required this.removedItemBuilder, Iterable initialItems, - }) : _items = new List.from(initialItems ?? []) { - assert(listKey != null); - assert(removedItemBuilder != null); - } + }) : assert(listKey != null), + assert(removedItemBuilder != null), + _items = new List.from(initialItems ?? []); final GlobalKey listKey; final dynamic removedItemBuilder; @@ -159,11 +158,10 @@ class CardItem extends StatelessWidget { this.onTap, @required this.item, this.selected: false - }) : super(key: key) { - assert(animation != null); - assert(item != null && item >= 0); - assert(selected != null); - } + }) : assert(animation != null), + assert(item != null && item >= 0), + assert(selected != null), + super(key: key); final Animation animation; final VoidCallback onTap; diff --git a/examples/flutter_gallery/lib/demo/animation/home.dart b/examples/flutter_gallery/lib/demo/animation/home.dart index e617c8411d57c..e01e158d1b40c 100644 --- a/examples/flutter_gallery/lib/demo/animation/home.dart +++ b/examples/flutter_gallery/lib/demo/animation/home.dart @@ -35,10 +35,10 @@ class _RenderStatusBarPaddingSliver extends RenderSliver { _RenderStatusBarPaddingSliver({ @required double maxHeight, @required double scrollFactor, - }) : _maxHeight = maxHeight, _scrollFactor = scrollFactor { - assert(maxHeight != null && maxHeight >= 0.0); - assert(scrollFactor != null && scrollFactor >= 1.0); - } + }) : assert(maxHeight != null && maxHeight >= 0.0), + assert(scrollFactor != null && scrollFactor >= 1.0), + _maxHeight = maxHeight, + _scrollFactor = scrollFactor; // The height of the status bar double get maxHeight => _maxHeight; @@ -79,10 +79,9 @@ class _StatusBarPaddingSliver extends SingleChildRenderObjectWidget { Key key, @required this.maxHeight, this.scrollFactor: 5.0, - }) : super(key: key) { - assert(maxHeight != null && maxHeight >= 0.0); - assert(scrollFactor != null && scrollFactor >= 1.0); - } + }) : assert(maxHeight != null && maxHeight >= 0.0), + assert(scrollFactor != null && scrollFactor >= 1.0), + super(key: key); final double maxHeight; final double scrollFactor; @@ -272,14 +271,13 @@ class _AllSectionsView extends AnimatedWidget { this.midHeight, this.maxHeight, this.sectionCards: const [], - }) : super(key: key, listenable: selectedIndex) { - assert(sections != null); - assert(sectionCards != null); - assert(sectionCards.length == sections.length); - assert(sectionIndex >= 0 && sectionIndex < sections.length); - assert(selectedIndex != null); - assert(selectedIndex.value >= 0.0 && selectedIndex.value < sections.length.toDouble()); - } + }) : assert(sections != null), + assert(sectionCards != null), + assert(sectionCards.length == sections.length), + assert(sectionIndex >= 0 && sectionIndex < sections.length), + assert(selectedIndex != null), + assert(selectedIndex.value >= 0.0 && selectedIndex.value < sections.length.toDouble()), + super(key: key, listenable: selectedIndex); final int sectionIndex; final List
sections; diff --git a/examples/flutter_gallery/lib/demo/animation/widgets.dart b/examples/flutter_gallery/lib/demo/animation/widgets.dart index bc0d8c6222ab7..c00945010e3e2 100644 --- a/examples/flutter_gallery/lib/demo/animation/widgets.dart +++ b/examples/flutter_gallery/lib/demo/animation/widgets.dart @@ -11,9 +11,9 @@ const double kSectionIndicatorWidth = 32.0; // The card for a single section. Displays the section's gradient and background image. class SectionCard extends StatelessWidget { - SectionCard({ Key key, @required this.section }) : super(key: key) { - assert(section != null); - } + SectionCard({ Key key, @required this.section }) + : assert(section != null), + super(key: key); final Section section; @@ -65,11 +65,10 @@ class SectionTitle extends StatelessWidget { @required this.section, @required this.scale, @required this.opacity, - }) : super(key: key) { - assert(section != null); - assert(scale != null); - assert(opacity != null && opacity >= 0.0 && opacity <= 1.0); - } + }) : assert(section != null), + assert(scale != null), + assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), + super(key: key); final Section section; final double scale; @@ -118,10 +117,10 @@ class SectionIndicator extends StatelessWidget { // Display a single SectionDetail. class SectionDetailView extends StatelessWidget { - SectionDetailView({ Key key, @required this.detail }) : super(key: key) { - assert(detail != null && detail.imageAsset != null); - assert((detail.imageAsset ?? detail.title) != null); - } + SectionDetailView({ Key key, @required this.detail }) + : assert(detail != null && detail.imageAsset != null), + assert((detail.imageAsset ?? detail.title) != null), + super(key: key); final SectionDetail detail; diff --git a/examples/flutter_gallery/lib/demo/colors_demo.dart b/examples/flutter_gallery/lib/demo/colors_demo.dart index b84f0e673f7e1..f76eae777f1e4 100644 --- a/examples/flutter_gallery/lib/demo/colors_demo.dart +++ b/examples/flutter_gallery/lib/demo/colors_demo.dart @@ -47,11 +47,10 @@ class ColorItem extends StatelessWidget { @required this.index, @required this.color, this.prefix: '', - }) : super(key: key) { - assert(index != null); - assert(color != null); - assert(prefix != null); - } + }) : assert(index != null), + assert(color != null), + assert(prefix != null), + super(key: key); final int index; final Color color; @@ -84,9 +83,8 @@ class PaletteTabView extends StatelessWidget { PaletteTabView({ Key key, @required this.colors, - }) : super(key: key) { - assert(colors != null && colors.isValid); - } + }) : assert(colors != null && colors.isValid), + super(key: key); final Palette colors; diff --git a/examples/flutter_gallery/lib/demo/contacts_demo.dart b/examples/flutter_gallery/lib/demo/contacts_demo.dart index 5a34a344723ce..3cd48317fff3d 100644 --- a/examples/flutter_gallery/lib/demo/contacts_demo.dart +++ b/examples/flutter_gallery/lib/demo/contacts_demo.dart @@ -37,9 +37,9 @@ class _ContactCategory extends StatelessWidget { } class _ContactItem extends StatelessWidget { - _ContactItem({ Key key, this.icon, this.lines, this.tooltip, this.onPressed }) : super(key: key) { - assert(lines.length > 1); - } + _ContactItem({ Key key, this.icon, this.lines, this.tooltip, this.onPressed }) + : assert(lines.length > 1), + super(key: key); final IconData icon; final List lines; diff --git a/examples/flutter_gallery/lib/demo/material/cards_demo.dart b/examples/flutter_gallery/lib/demo/material/cards_demo.dart index b02b0ee46a084..4beaacb78c198 100644 --- a/examples/flutter_gallery/lib/demo/material/cards_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/cards_demo.dart @@ -37,9 +37,9 @@ final List destinations = [ ]; class TravelDestinationItem extends StatelessWidget { - TravelDestinationItem({ Key key, @required this.destination }) : super(key: key) { - assert(destination != null && destination.isValid); - } + TravelDestinationItem({ Key key, @required this.destination }) + : assert(destination != null && destination.isValid), + super(key: key); static final double height = 366.0; final TravelDestination destination; diff --git a/examples/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart b/examples/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart index 1709e99a067e2..e2876e3f74cce 100644 --- a/examples/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart @@ -19,11 +19,10 @@ enum DismissDialogAction { class DateTimeItem extends StatelessWidget { DateTimeItem({ Key key, DateTime dateTime, @required this.onChanged }) - : date = new DateTime(dateTime.year, dateTime.month, dateTime.day), + : assert(onChanged != null), + date = new DateTime(dateTime.year, dateTime.month, dateTime.day), time = new TimeOfDay(hour: dateTime.hour, minute: dateTime.minute), - super(key: key) { - assert(onChanged != null); - } + super(key: key); final DateTime date; final TimeOfDay time; diff --git a/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart b/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart index 87bba384631ec..fe06de77827bd 100644 --- a/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart @@ -143,11 +143,10 @@ class GridDemoPhotoItem extends StatelessWidget { @required this.photo, @required this.tileStyle, @required this.onBannerTap - }) : super(key: key) { - assert(photo != null && photo.isValid); - assert(tileStyle != null); - assert(onBannerTap != null); - } + }) : assert(photo != null && photo.isValid), + assert(tileStyle != null), + assert(onBannerTap != null), + super(key: key); final Photo photo; final GridDemoTileStyle tileStyle; diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart index a61ee5c902168..c4b035e89120b 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart @@ -116,9 +116,9 @@ class _ShrineGridDelegate extends SliverGridDelegate { // Displays the Vendor's name and avatar. class _VendorItem extends StatelessWidget { - _VendorItem({ Key key, @required this.vendor }) : super(key: key) { - assert(vendor != null); - } + _VendorItem({ Key key, @required this.vendor }) + : assert(vendor != null), + super(key: key); final Vendor vendor; @@ -240,11 +240,11 @@ class _HeadingLayout extends MultiChildLayoutDelegate { // A card that highlights the "featured" catalog item. class _Heading extends StatelessWidget { - _Heading({ Key key, @required this.product }) : super(key: key) { - assert(product != null); - assert(product.featureTitle != null); - assert(product.featureDescription != null); - } + _Heading({ Key key, @required this.product }) + : assert(product != null), + assert(product.featureTitle != null), + assert(product.featureDescription != null), + super(key: key); final Product product; @@ -294,9 +294,9 @@ class _Heading extends StatelessWidget { // A card that displays a product's image, price, and vendor. The _ProductItem // cards appear in a grid below the heading. class _ProductItem extends StatelessWidget { - _ProductItem({ Key key, @required this.product, this.onPressed }) : super(key: key) { - assert(product != null); - } + _ProductItem({ Key key, @required this.product, this.onPressed }) + : assert(product != null), + super(key: key); final Product product; final VoidCallback onPressed; diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart index 052bfa0ccd2d9..a8c373914ee6b 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart @@ -17,11 +17,10 @@ class _ProductItem extends StatelessWidget { @required this.product, @required this.quantity, @required this.onChanged, - }) : super(key: key) { - assert(product != null); - assert(quantity != null); - assert(onChanged != null); - } + }) : assert(product != null), + assert(quantity != null), + assert(onChanged != null), + super(key: key); final Product product; final int quantity; @@ -70,9 +69,9 @@ class _ProductItem extends StatelessWidget { // Vendor name and description class _VendorItem extends StatelessWidget { - _VendorItem({ Key key, @required this.vendor }) : super(key: key) { - assert(vendor != null); - } + _VendorItem({ Key key, @required this.vendor }) + : assert(vendor != null), + super(key: key); final Vendor vendor; @@ -146,10 +145,9 @@ class _Heading extends StatelessWidget { @required this.product, @required this.quantity, this.quantityChanged, - }) : super(key: key) { - assert(product != null); - assert(quantity != null && quantity >= 0 && quantity <= 5); - } + }) : assert(product != null), + assert(quantity != null && quantity >= 0 && quantity <= 5), + super(key: key); final Product product; final int quantity; @@ -213,11 +211,10 @@ class OrderPage extends StatefulWidget { @required this.order, @required this.products, @required this.shoppingCart, - }) : super(key: key) { - assert(order != null); - assert(products != null && products.isNotEmpty); - assert(shoppingCart != null); - } + }) : assert(order != null), + assert(products != null && products.isNotEmpty), + assert(shoppingCart != null), + super(key: key); final Order order; final List products; @@ -328,9 +325,8 @@ class ShrineOrderRoute extends ShrinePageRoute { @required this.order, WidgetBuilder builder, RouteSettings settings: const RouteSettings(), - }) : super(builder: builder, settings: settings) { - assert(order != null); - } + }) : assert(order != null), + super(builder: builder, settings: settings); Order order; diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart index eb073feda3e45..908a8375c91f5 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart @@ -22,10 +22,9 @@ class ShrinePage extends StatefulWidget { this.floatingActionButton, this.products, this.shoppingCart - }) : super(key: key) { - assert(body != null); - assert(scaffoldKey != null); - } + }) : assert(body != null), + assert(scaffoldKey != null), + super(key: key); final GlobalKey scaffoldKey; final Widget body; diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_theme.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_theme.dart index e51bbb7430be7..8dc5cf5e73254 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_theme.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_theme.dart @@ -27,9 +27,9 @@ TextStyle abrilFatfaceRegular34(Color color) => new ShrineStyle.abrilFatface(34. /// InheritedWidget is shared by all of the routes and widgets created for /// the Shrine app. class ShrineTheme extends InheritedWidget { - ShrineTheme({ Key key, @required Widget child }) : super(key: key, child: child) { - assert(child != null); - } + ShrineTheme({ Key key, @required Widget child }) + : assert(child != null), + super(key: key, child: child); final Color cardBackgroundColor = Colors.white; final Color appBarBackgroundColor = Colors.white; diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_types.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_types.dart index 13673f85f3142..42827d0f13de5 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_types.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_types.dart @@ -66,11 +66,10 @@ class Product { } class Order { - Order({ @required this.product, this.quantity: 1, this.inCart: false }) { - assert(product != null); - assert(quantity != null && quantity >= 0); - assert(inCart != null); - } + Order({ @required this.product, this.quantity: 1, this.inCart: false }) + : assert(product != null), + assert(quantity != null && quantity >= 0), + assert(inCart != null); final Product product; final int quantity; diff --git a/examples/flutter_gallery/lib/demo/typography_demo.dart b/examples/flutter_gallery/lib/demo/typography_demo.dart index da6e34f5d6ea9..9dd9181da7b90 100644 --- a/examples/flutter_gallery/lib/demo/typography_demo.dart +++ b/examples/flutter_gallery/lib/demo/typography_demo.dart @@ -11,11 +11,10 @@ class TextStyleItem extends StatelessWidget { @required this.name, @required this.style, @required this.text, - }) : super(key: key) { - assert(name != null); - assert(style != null); - assert(text != null); - } + }) : assert(name != null), + assert(style != null), + assert(text != null), + super(key: key); final String name; final TextStyle style; diff --git a/examples/flutter_gallery/lib/gallery/drawer.dart b/examples/flutter_gallery/lib/gallery/drawer.dart index e0ff3e3e6a457..8a5f2ef27eb92 100644 --- a/examples/flutter_gallery/lib/gallery/drawer.dart +++ b/examples/flutter_gallery/lib/gallery/drawer.dart @@ -102,10 +102,9 @@ class GalleryDrawer extends StatelessWidget { this.onCheckerboardOffscreenLayersChanged, this.onPlatformChanged, this.onSendFeedback, - }) : super(key: key) { - assert(onThemeChanged != null); - assert(onTimeDilationChanged != null); - } + }) : assert(onThemeChanged != null), + assert(onTimeDilationChanged != null), + super(key: key); final bool useLightTheme; final ValueChanged onThemeChanged; diff --git a/examples/flutter_gallery/lib/gallery/home.dart b/examples/flutter_gallery/lib/gallery/home.dart index 76f9994e8af4d..9f1ebb22419df 100644 --- a/examples/flutter_gallery/lib/gallery/home.dart +++ b/examples/flutter_gallery/lib/gallery/home.dart @@ -80,10 +80,9 @@ class GalleryHome extends StatefulWidget { this.onCheckerboardOffscreenLayersChanged, this.onPlatformChanged, this.onSendFeedback, - }) : super(key: key) { - assert(onThemeChanged != null); - assert(onTimeDilationChanged != null); - } + }) : assert(onThemeChanged != null), + assert(onTimeDilationChanged != null), + super(key: key); final bool useLightTheme; final ValueChanged onThemeChanged; diff --git a/examples/flutter_gallery/lib/gallery/item.dart b/examples/flutter_gallery/lib/gallery/item.dart index aa9b74dd4a007..fe118fd3cfaeb 100644 --- a/examples/flutter_gallery/lib/gallery/item.dart +++ b/examples/flutter_gallery/lib/gallery/item.dart @@ -18,12 +18,10 @@ class GalleryItem extends StatelessWidget { @required this.category, @required this.routeName, @required this.buildRoute, - }) { - assert(title != null); - assert(category != null); - assert(routeName != null); - assert(buildRoute != null); - } + }) : assert(title != null), + assert(category != null), + assert(routeName != null), + assert(buildRoute != null); final String title; final String subtitle; diff --git a/examples/flutter_gallery/lib/gallery/updates.dart b/examples/flutter_gallery/lib/gallery/updates.dart index 021dd3db217e4..e100fa53e54ea 100644 --- a/examples/flutter_gallery/lib/gallery/updates.dart +++ b/examples/flutter_gallery/lib/gallery/updates.dart @@ -12,9 +12,9 @@ import 'package:url_launcher/url_launcher.dart'; typedef Future UpdateUrlFetcher(); class Updater extends StatefulWidget { - Updater({ @required this.updateUrlFetcher, this.child, Key key }) : super(key: key) { - assert(updateUrlFetcher != null); - } + Updater({ @required this.updateUrlFetcher, this.child, Key key }) + : assert(updateUrlFetcher != null), + super(key: key); final UpdateUrlFetcher updateUrlFetcher; final Widget child; diff --git a/examples/layers/services/isolate.dart b/examples/layers/services/isolate.dart index b325d7d9138d1..395875780faf4 100644 --- a/examples/layers/services/isolate.dart +++ b/examples/layers/services/isolate.dart @@ -18,12 +18,11 @@ typedef void OnResultListener(String result); // in real-world applications. class Calculator { Calculator({ @required this.onProgressListener, @required this.onResultListener, String data }) - // In order to keep the example files smaller, we "cheat" a little and - // replicate our small json string into a 10,000-element array. - : _data = _replicateJson(data, 10000) { - assert(onProgressListener != null); - assert(onResultListener != null); - } + : assert(onProgressListener != null), + assert(onResultListener != null), + // In order to keep the example files smaller, we "cheat" a little and + // replicate our small json string into a 10,000-element array. + _data = _replicateJson(data, 10000); final OnProgressListener onProgressListener; final OnResultListener onResultListener; @@ -87,9 +86,9 @@ class CalculationMessage { // progress of the background computation. class CalculationManager { CalculationManager({ @required this.onProgressListener, @required this.onResultListener }) - : _receivePort = new ReceivePort() { - assert(onProgressListener != null); - assert(onResultListener != null); + : assert(onProgressListener != null), + assert(onResultListener != null), + _receivePort = new ReceivePort() { _receivePort.listen(_handleMessage); } diff --git a/examples/stocks/lib/stock_types.dart b/examples/stocks/lib/stock_types.dart index f3d356b406a01..163628e7a8803 100644 --- a/examples/stocks/lib/stock_types.dart +++ b/examples/stocks/lib/stock_types.dart @@ -19,18 +19,16 @@ class StockConfiguration { @required this.debugShowRainbow, @required this.showPerformanceOverlay, @required this.showSemanticsDebugger - }) { - assert(stockMode != null); - assert(backupMode != null); - assert(debugShowGrid != null); - assert(debugShowSizes != null); - assert(debugShowBaselines != null); - assert(debugShowLayers != null); - assert(debugShowPointers != null); - assert(debugShowRainbow != null); - assert(showPerformanceOverlay != null); - assert(showSemanticsDebugger != null); - } + }) : assert(stockMode != null), + assert(backupMode != null), + assert(debugShowGrid != null), + assert(debugShowSizes != null), + assert(debugShowBaselines != null), + assert(debugShowLayers != null), + assert(debugShowPointers != null), + assert(debugShowRainbow != null), + assert(showPerformanceOverlay != null), + assert(showSemanticsDebugger != null); final StockMode stockMode; final BackupMode backupMode; diff --git a/packages/flutter/lib/src/foundation/basic_types.dart b/packages/flutter/lib/src/foundation/basic_types.dart index 5d63bf244f67c..7e0fe50260af6 100644 --- a/packages/flutter/lib/src/foundation/basic_types.dart +++ b/packages/flutter/lib/src/foundation/basic_types.dart @@ -79,9 +79,9 @@ class BitField { /// Creates a bit field of all zeros. /// /// The given length must be at most 62. - BitField(this._length) : _bits = _kAllZeros { - assert(_length <= _kSMIBits); - } + BitField(this._length) + : assert(_length <= _kSMIBits), + _bits = _kAllZeros; /// Creates a bit field filled with a particular value. /// @@ -89,9 +89,10 @@ class BitField { /// the bits are filled with zeros. /// /// The given length must be at most 62. - BitField.filled(this._length, bool value) : _bits = value ? _kAllOnes : _kAllZeros { - assert(_length <= _kSMIBits); - } + BitField.filled(this._length, bool value) + : assert(_length <= _kSMIBits), + _bits = value ? _kAllOnes : _kAllZeros; + final int _length; int _bits; diff --git a/packages/flutter/lib/src/widgets/scroll_simulation.dart b/packages/flutter/lib/src/widgets/scroll_simulation.dart index a462a79ad464e..33d851401990b 100644 --- a/packages/flutter/lib/src/widgets/scroll_simulation.dart +++ b/packages/flutter/lib/src/widgets/scroll_simulation.dart @@ -135,8 +135,8 @@ class ClampingScrollSimulation extends Simulation { @required this.velocity, this.friction: 0.015, Tolerance tolerance: Tolerance.defaultTolerance, - }) : super(tolerance: tolerance) { - assert(_flingVelocityPenetration(0.0) == _kInitialVelocityPenetration); + }) : assert(_flingVelocityPenetration(0.0) == _kInitialVelocityPenetration), + super(tolerance: tolerance) { _duration = _flingDuration(velocity); _distance = (velocity * _duration / _kInitialVelocityPenetration).abs(); } diff --git a/packages/flutter/test/widgets/layout_builder_mutations_test.dart b/packages/flutter/test/widgets/layout_builder_mutations_test.dart index e6014f2481911..79a09b5ef9f79 100644 --- a/packages/flutter/test/widgets/layout_builder_mutations_test.dart +++ b/packages/flutter/test/widgets/layout_builder_mutations_test.dart @@ -12,9 +12,8 @@ class Wrapper extends StatelessWidget { Wrapper({ Key key, @required this.child - }) : super(key: key) { - assert(child != null); - } + }) : assert(child != null), + super(key: key); final Widget child; diff --git a/packages/flutter/test/widgets/linked_scroll_view_test.dart b/packages/flutter/test/widgets/linked_scroll_view_test.dart index ea044b4b43936..05bd566dff0ae 100644 --- a/packages/flutter/test/widgets/linked_scroll_view_test.dart +++ b/packages/flutter/test/widgets/linked_scroll_view_test.dart @@ -113,14 +113,13 @@ class LinkedScrollPosition extends ScrollPositionWithSingleContext { ScrollContext context, double initialPixels, ScrollPosition oldPosition, - }) : super( - physics: physics, - context: context, - initialPixels: initialPixels, - oldPosition: oldPosition, - ) { - assert(owner != null); - } + }) : assert(owner != null), + super( + physics: physics, + context: context, + initialPixels: initialPixels, + oldPosition: oldPosition, + ); final LinkedScrollController owner; @@ -547,4 +546,4 @@ void main() { await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 60)); }); -} \ No newline at end of file +} diff --git a/packages/flutter_driver/lib/src/health.dart b/packages/flutter_driver/lib/src/health.dart index da10a27e89363..52c998d6a8ef0 100644 --- a/packages/flutter_driver/lib/src/health.dart +++ b/packages/flutter_driver/lib/src/health.dart @@ -29,9 +29,8 @@ final EnumIndex _healthStatusIndex = class Health extends Result { /// Creates a [Health] object with the given [status]. - Health(this.status) { - assert(status != null); - } + Health(this.status) + : assert(status != null); /// Deserializes the result from JSON. static Health fromJson(Map json) { From e9c7f604e96578770b1dfca3c35759b6b33be58c Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Wed, 7 Jun 2017 13:16:16 -0700 Subject: [PATCH 050/110] Make generated Java folder names match package names. (#10537) --- packages/flutter_tools/lib/src/template.dart | 14 +++++----- .../MainActivity.java.tmpl | 0 .../MainActivity.kt.tmpl | 0 .../pluginClass.java.tmpl | 0 .../pluginClass.kt.tmpl | 0 .../test/commands/create_test.dart | 26 +++++++++---------- 6 files changed, 20 insertions(+), 20 deletions(-) rename packages/flutter_tools/templates/create/android-java.tmpl/app/src/main/java/{organization/projectName => androidIdentifier}/MainActivity.java.tmpl (100%) rename packages/flutter_tools/templates/create/android-kotlin.tmpl/app/src/main/kotlin/{organization/projectName => androidIdentifier}/MainActivity.kt.tmpl (100%) rename packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/{organization/projectName => androidIdentifier}/pluginClass.java.tmpl (100%) rename packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/{organization/projectName => androidIdentifier}/pluginClass.kt.tmpl (100%) diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index 580b3afc7c3f7..215e1f35ca921 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart @@ -84,20 +84,20 @@ class Template { return null; relativeDestinationPath = relativeDestinationPath.replaceAll('$platform-$language.tmpl', platform); } - final String organization = context['organization']; final String projectName = context['projectName']; + final String androidIdentifier = context['androidIdentifier']; final String pluginClass = context['pluginClass']; final String destinationDirPath = destination.absolute.path; final String pathSeparator = fs.path.separator; String finalDestinationPath = fs.path .join(destinationDirPath, relativeDestinationPath) .replaceAll(_kCopyTemplateExtension, '') - .replaceAll(_kTemplateExtension, '') - .replaceAll( - '${pathSeparator}organization$pathSeparator', - '$pathSeparator${organization.replaceAll('.', pathSeparator)}$pathSeparator', - ); - + .replaceAll(_kTemplateExtension, ''); + + if (androidIdentifier != null) { + finalDestinationPath = finalDestinationPath + .replaceAll('androidIdentifier', androidIdentifier.replaceAll('.', pathSeparator)); + } if (projectName != null) finalDestinationPath = finalDestinationPath.replaceAll('projectName', projectName); if (pluginClass != null) diff --git a/packages/flutter_tools/templates/create/android-java.tmpl/app/src/main/java/organization/projectName/MainActivity.java.tmpl b/packages/flutter_tools/templates/create/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl similarity index 100% rename from packages/flutter_tools/templates/create/android-java.tmpl/app/src/main/java/organization/projectName/MainActivity.java.tmpl rename to packages/flutter_tools/templates/create/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl diff --git a/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/src/main/kotlin/organization/projectName/MainActivity.kt.tmpl b/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl similarity index 100% rename from packages/flutter_tools/templates/create/android-kotlin.tmpl/app/src/main/kotlin/organization/projectName/MainActivity.kt.tmpl rename to packages/flutter_tools/templates/create/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl diff --git a/packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/organization/projectName/pluginClass.java.tmpl b/packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/organization/projectName/pluginClass.java.tmpl rename to packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/organization/projectName/pluginClass.kt.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/organization/projectName/pluginClass.kt.tmpl rename to packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl diff --git a/packages/flutter_tools/test/commands/create_test.dart b/packages/flutter_tools/test/commands/create_test.dart index 744a2b62b7ca4..45558bb6a8948 100644 --- a/packages/flutter_tools/test/commands/create_test.dart +++ b/packages/flutter_tools/test/commands/create_test.dart @@ -40,7 +40,7 @@ void main() { projectDir, [], [ - 'android/app/src/main/java/com/yourcompany/flutter_project/MainActivity.java', + 'android/app/src/main/java/com/yourcompany/flutterproject/MainActivity.java', 'ios/Runner/AppDelegate.h', 'ios/Runner/AppDelegate.m', 'ios/Runner/main.m', @@ -54,13 +54,13 @@ void main() { projectDir, ['--android-language', 'kotlin', '-i', 'swift'], [ - 'android/app/src/main/kotlin/com/yourcompany/flutter_project/MainActivity.kt', + 'android/app/src/main/kotlin/com/yourcompany/flutterproject/MainActivity.kt', 'ios/Runner/AppDelegate.swift', 'ios/Runner/Runner-Bridging-Header.h', 'lib/main.dart', ], [ - 'android/app/src/main/java/com/yourcompany/flutter_project/MainActivity.java', + 'android/app/src/main/java/com/yourcompany/flutterproject/MainActivity.java', 'ios/Runner/AppDelegate.h', 'ios/Runner/AppDelegate.m', 'ios/Runner/main.m', @@ -73,11 +73,11 @@ void main() { projectDir, ['--plugin'], [ - 'android/src/main/java/com/yourcompany/flutter_project/FlutterProjectPlugin.java', + 'android/src/main/java/com/yourcompany/flutterproject/FlutterProjectPlugin.java', 'ios/Classes/FlutterProjectPlugin.h', 'ios/Classes/FlutterProjectPlugin.m', 'lib/flutter_project.dart', - 'example/android/app/src/main/java/com/yourcompany/flutter_project_example/MainActivity.java', + 'example/android/app/src/main/java/com/yourcompany/flutterprojectexample/MainActivity.java', 'example/ios/Runner/AppDelegate.h', 'example/ios/Runner/AppDelegate.m', 'example/ios/Runner/main.m', @@ -91,19 +91,19 @@ void main() { projectDir, ['--plugin', '-a', 'kotlin', '--ios-language', 'swift'], [ - 'android/src/main/kotlin/com/yourcompany/flutter_project/FlutterProjectPlugin.kt', + 'android/src/main/kotlin/com/yourcompany/flutterproject/FlutterProjectPlugin.kt', 'ios/Classes/FlutterProjectPlugin.h', 'ios/Classes/FlutterProjectPlugin.m', 'ios/Classes/SwiftFlutterProjectPlugin.swift', 'lib/flutter_project.dart', - 'example/android/app/src/main/kotlin/com/yourcompany/flutter_project_example/MainActivity.kt', + 'example/android/app/src/main/kotlin/com/yourcompany/flutterprojectexample/MainActivity.kt', 'example/ios/Runner/AppDelegate.swift', 'example/ios/Runner/Runner-Bridging-Header.h', 'example/lib/main.dart', ], [ - 'android/src/main/java/com/yourcompany/flutter_project/FlutterProjectPlugin.java', - 'example/android/app/src/main/java/com/yourcompany/flutter_project_example/MainActivity.java', + 'android/src/main/java/com/yourcompany/flutterproject/FlutterProjectPlugin.java', + 'example/android/app/src/main/java/com/yourcompany/flutterprojectexample/MainActivity.java', 'example/ios/Runner/AppDelegate.h', 'example/ios/Runner/AppDelegate.m', 'example/ios/Runner/main.m', @@ -116,12 +116,12 @@ void main() { projectDir, ['--plugin', '--org', 'com.bar.foo'], [ - 'android/src/main/java/com/bar/foo/flutter_project/FlutterProjectPlugin.java', - 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java', + 'android/src/main/java/com/bar/foo/flutterproject/FlutterProjectPlugin.java', + 'example/android/app/src/main/java/com/bar/foo/flutterprojectexample/MainActivity.java', ], [ - 'android/src/main/java/com/yourcompany/flutter_project/FlutterProjectPlugin.java', - 'example/android/app/src/main/java/com/yourcompany/flutter_project_example/MainActivity.java', + 'android/src/main/java/com/yourcompany/flutterproject/FlutterProjectPlugin.java', + 'example/android/app/src/main/java/com/yourcompany/flutterprojectexample/MainActivity.java', ], ); }); From b83ddcd1922100b02d40e1df9c6ab3130f65eec8 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Wed, 7 Jun 2017 13:41:42 -0700 Subject: [PATCH 051/110] Rev engine to pick up FlutterActivity change (#10560) https://github.com/flutter/flutter/issues/10072 --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 1904543b30301..f0df42f1dd145 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -8686a458104628c4cd5e142e59cac53458724e31 +ec8cbe0fb6c052a5f8b047fb88d7103ce77ae732 From a0ca48c35c86bf8e7069eec9bb4a183ea584fa77 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 7 Jun 2017 14:31:52 -0700 Subject: [PATCH 052/110] Revert use of const asserts in flutter_driver (#10568) Fixes bot breakage resulting from commit 7d7132636369914402507c7b15d635039193ee46 (#10540). --- packages/flutter_driver/lib/src/health.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/flutter_driver/lib/src/health.dart b/packages/flutter_driver/lib/src/health.dart index 52c998d6a8ef0..da10a27e89363 100644 --- a/packages/flutter_driver/lib/src/health.dart +++ b/packages/flutter_driver/lib/src/health.dart @@ -29,8 +29,9 @@ final EnumIndex _healthStatusIndex = class Health extends Result { /// Creates a [Health] object with the given [status]. - Health(this.status) - : assert(status != null); + Health(this.status) { + assert(status != null); + } /// Deserializes the result from JSON. static Health fromJson(Map json) { From c445c0880ccf224a8343a33215a359d13620cb44 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 7 Jun 2017 15:25:39 -0700 Subject: [PATCH 053/110] Don't visit element children of the childless (#10558) --- .../src/widgets/sliver_persistent_header.dart | 3 +- .../material/tabbed_scrollview_warp_test.dart | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 packages/flutter/test/material/tabbed_scrollview_warp_test.dart diff --git a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart index e4e19a35bef0c..6836268398a4f 100644 --- a/packages/flutter/lib/src/widgets/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/widgets/sliver_persistent_header.dart @@ -140,7 +140,8 @@ class _SliverPersistentHeaderElement extends RenderObjectElement { @override void visitChildren(ElementVisitor visitor) { - visitor(child); + if (child != null) + visitor(child); } } diff --git a/packages/flutter/test/material/tabbed_scrollview_warp_test.dart b/packages/flutter/test/material/tabbed_scrollview_warp_test.dart new file mode 100644 index 0000000000000..49824231cfa1d --- /dev/null +++ b/packages/flutter/test/material/tabbed_scrollview_warp_test.dart @@ -0,0 +1,82 @@ +// Copyright 2017 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_test/flutter_test.dart'; +import 'package:flutter/material.dart'; + +// This is a regression test for https://github.com/flutter/flutter/issues/10549 +// which was failing because _SliverPersistentHeaderElement.visitChildren() +// didn't check child != null before visiting its child. + +class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { + @override + double get minExtent => 50.0; + + @override + double get maxExtent => 150.0; + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => const Placeholder(color: Colors.teal); + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => false; +} + +class MyHomePage extends StatefulWidget { + @override + _MyHomePageState createState() => new _MyHomePageState(); +} + +class _MyHomePageState extends State with TickerProviderStateMixin { + static const int tabCount = 3; + TabController tabController; + + @override + void initState() { + super.initState(); + tabController = new TabController(initialIndex: 0, length: tabCount, vsync: this); + } + + @override + void dispose() { + tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + appBar: new AppBar( + bottom: new TabBar( + controller: tabController, + tabs: new List.generate(tabCount, (int index) => new Tab(text: 'Tab $index')).toList(), + ), + ), + body: new TabBarView( + controller: tabController, + children: new List.generate(tabCount, (int index) { + return new CustomScrollView( + // The bug only occurs when this key is included + key: new ValueKey('Page $index'), + slivers: [ + new SliverPersistentHeader( + delegate: new MySliverPersistentHeaderDelegate(), + ), + ], + ); + }).toList(), + ), + ); + } +} + +void main() { + testWidgets('Tabbed CustomScrollViews, warp from tab 1 to 3', (WidgetTester tester) async { + await tester.pumpWidget(new MaterialApp(home: new MyHomePage())); + + // should not crash. + await tester.tap(find.text('Tab 2')); + await tester.pumpAndSettle(); + }); +} From 96b9d6473e831891ca6c7a2cff681466721a6833 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 7 Jun 2017 15:46:18 -0700 Subject: [PATCH 054/110] Center all strings in SemanticDebugger (#10570) Fixes https://github.com/flutter/flutter/issues/4647 --- packages/flutter/lib/src/widgets/semantics_debugger.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index d8de489a5c61c..ac888f8f62102 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -252,6 +252,7 @@ void _paintMessage(Canvas canvas, SemanticsNode node) { canvas.clipRect(rect); final TextPainter textPainter = new TextPainter() ..text = new TextSpan(style: _messageStyle, text: message) + ..textAlign = TextAlign.center ..layout(maxWidth: rect.width); textPainter.paint(canvas, FractionalOffset.center.inscribe(textPainter.size, rect).topLeft); From c2b0a30c5746a4f94d53c4205cf748173765d6fa Mon Sep 17 00:00:00 2001 From: xster Date: Wed, 7 Jun 2017 15:56:13 -0700 Subject: [PATCH 055/110] Add more instructions and handling for first time iOS run (#10521) * Before tests * Add the part to trust the cert on the device * flip the error checks since some are more specific and are more actionable * add tests * review --- .../lib/src/commands/build_ios.dart | 4 +- .../lib/src/ios/code_signing.dart | 52 ++++-- .../flutter_tools/lib/src/ios/devices.dart | 4 +- packages/flutter_tools/lib/src/ios/mac.dart | 67 +++---- packages/flutter_tools/test/ios/mac_test.dart | 176 ++++++++++++++++++ 5 files changed, 247 insertions(+), 56 deletions(-) create mode 100644 packages/flutter_tools/test/ios/mac_test.dart diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index 93792eca58419..eb72d4ae01ef4 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -45,7 +45,7 @@ class BuildIOSCommand extends BuildSubCommand { if (getCurrentHostPlatform() != HostPlatform.darwin_x64) throwToolExit('Building for iOS is only supported on the Mac.'); - final IOSApp app = applicationPackages.getPackageForPlatform(TargetPlatform.ios); + final BuildableIOSApp app = applicationPackages.getPackageForPlatform(TargetPlatform.ios); if (app == null) throwToolExit('Application not configured for iOS'); @@ -73,7 +73,7 @@ class BuildIOSCommand extends BuildSubCommand { ); if (!result.success) { - await diagnoseXcodeBuildFailure(result); + await diagnoseXcodeBuildFailure(result, app); throwToolExit('Encountered error while building for $logTarget.'); } diff --git a/packages/flutter_tools/lib/src/ios/code_signing.dart b/packages/flutter_tools/lib/src/ios/code_signing.dart index 5a917dba522e1..169332dc04628 100644 --- a/packages/flutter_tools/lib/src/ios/code_signing.dart +++ b/packages/flutter_tools/lib/src/ios/code_signing.dart @@ -14,34 +14,60 @@ import '../base/process.dart'; import '../base/terminal.dart'; import '../globals.dart'; +/// User message when no development certificates are found in the keychain. +/// +/// The user likely never did any iOS development. const String noCertificatesInstruction = ''' ═══════════════════════════════════════════════════════════════════════════════════ No valid code signing certificates were found -Please ensure that you have a valid Development Team with valid iOS Development Certificates -associated with your Apple ID by: - 1- Opening the Xcode application - 2- Go to Xcode->Preferences->Accounts - 3- Make sure that you're signed in with your Apple ID via the '+' button on the bottom left - 4- Make sure that you have development certificates available by signing up to Apple - Developer Program and/or downloading available profiles as needed. +You can connect to your Apple Developer account by signing in with your Apple ID in Xcode +and create an iOS Development Certificate as well as a Provisioning Profile for your project by: +$fixWithDevelopmentTeamInstruction + 5- Trust your newly created Development Certificate on your iOS device + via Settings > General > Device Management > [your new certificate] > Trust + For more information, please visit: https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html Or run on an iOS simulator without code signing ═══════════════════════════════════════════════════════════════════════════════════'''; +/// User message when there are no provisioning profile for the current app bundle identifier. +/// +/// The user did iOS development but never on this project and/or device. +const String noProvisioningProfileInstruction = ''' +═══════════════════════════════════════════════════════════════════════════════════ +No Provisioning Profile was found for your project's Bundle Identifier or your device. +You can create a new Provisioning Profile for your project in Xcode for your +team by: +$fixWithDevelopmentTeamInstruction + +For more information, please visit: + https://flutter.io/setup/#deploy-to-ios-devices + +Or run on an iOS simulator without code signing +═══════════════════════════════════════════════════════════════════════════════════'''; +/// Fallback error message for signing issues. +/// +/// Couldn't auto sign the app but can likely solved by retracing the signing flow in Xcode. const String noDevelopmentTeamInstruction = ''' ═══════════════════════════════════════════════════════════════════════════════════ Building a deployable iOS app requires a selected Development Team with a Provisioning Profile Please ensure that a Development Team is selected by: - 1- Opening the Flutter project's Xcode target with +$fixWithDevelopmentTeamInstruction + +For more information, please visit: + https://flutter.io/setup/#deploy-to-ios-devices + +Or run on an iOS simulator without code signing +═══════════════════════════════════════════════════════════════════════════════════'''; +const String fixWithDevelopmentTeamInstruction = ''' + 1- Open the Flutter project's Xcode target with open ios/Runner.xcworkspace 2- Select the 'Runner' project in the navigator then the 'Runner' target in the project settings - 3- In the 'General' tab, make sure a 'Development Team' is selected\n -For more information, please visit: - https://flutter.io/setup/#deploy-to-ios-devices\n -Or run on an iOS simulator -═══════════════════════════════════════════════════════════════════════════════════'''; + 3- In the 'General' tab, make sure a 'Development Team' is selected. You may need to add + your Apple ID first. + 4- Build or run your project again'''; final RegExp _securityFindIdentityDeveloperIdentityExtractionPattern = new RegExp(r'^\s*\d+\).+"(.+Developer.+)"$'); diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 0b4d0be117234..a1cb175e7d855 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -208,7 +208,7 @@ class IOSDevice extends Device { final XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: mode, target: mainPath, buildForDevice: true); if (!buildResult.success) { printError('Could not build the precompiled application for the device.'); - await diagnoseXcodeBuildFailure(buildResult); + await diagnoseXcodeBuildFailure(buildResult, app); printError(''); return new LaunchResult.failed(); } @@ -242,7 +242,7 @@ class IOSDevice extends Device { // the port picked and scrape that later. } - if (debuggingOptions.enableSoftwareRendering) + if (debuggingOptions.enableSoftwareRendering) launchArguments.add('--enable-software-rendering'); if (platformArgs['trace-startup'] ?? false) diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 84f6aabfc34d6..16430aa207fab 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -147,7 +147,7 @@ Future buildXcodeProject({ } String developmentTeam; - if (codesign && mode != BuildMode.release && buildForDevice) + if (codesign && buildForDevice) developmentTeam = await getCodeSigningIdentityDevelopmentTeam(app); // Before the build, all service definitions must be updated and the dylibs @@ -246,51 +246,40 @@ Future buildXcodeProject({ } } -Future diagnoseXcodeBuildFailure(XcodeBuildResult result) async { - final File plistFile = fs.file('ios/Runner/Info.plist'); - if (plistFile.existsSync()) { - final String plistContent = plistFile.readAsStringSync(); - if (plistContent.contains('com.yourcompany')) { - printError(''); - printError('It appears that your application still contains the default signing identifier.'); - printError("Try replacing 'com.yourcompany' with your signing id"); - printError('in ${plistFile.absolute.path}'); - return; - } +Future diagnoseXcodeBuildFailure(XcodeBuildResult result, BuildableIOSApp app) async { + if (result.xcodeBuildExecution != null && + result.xcodeBuildExecution.buildForPhysicalDevice && + result.stdout?.contains('BCEROR') == true && + // May need updating if Xcode changes its outputs. + result.stdout?.contains('Xcode couldn\'t find a provisioning profile matching') == true) { + printError(noProvisioningProfileInstruction, emphasis: true); + return; + } + if (result.xcodeBuildExecution != null && + result.xcodeBuildExecution.buildForPhysicalDevice && + // Make sure the user has specified at least the DEVELOPMENT_TEAM (for automatic Xcode 8) + // signing or the PROVISIONING_PROFILE (for manual signing or Xcode 7). + !(app.buildSettings?.containsKey('DEVELOPMENT_TEAM')) == true || app.buildSettings?.containsKey('PROVISIONING_PROFILE') == true) { + printError(noDevelopmentTeamInstruction, emphasis: true); + return; + } + if (app.id?.contains('com.yourcompany') ?? false) { + printError(''); + printError('It appears that your application still contains the default signing identifier.'); + printError("Try replacing 'com.yourcompany' with your signing id in Xcode:"); + printError(' open ios/Runner.xcworkspace'); + return; } if (result.stdout?.contains('Code Sign error') == true) { printError(''); printError('It appears that there was a problem signing your application prior to installation on the device.'); printError(''); - if (plistFile.existsSync()) { - printError('Verify that the CFBundleIdentifier in the Info.plist file is your signing id'); - printError(' ${plistFile.absolute.path}'); - printError(''); - } - printError("Try launching Xcode and selecting 'Product > Build' to fix the problem:"); - printError(" open ios/Runner.xcworkspace"); + printError('Verify that the Bundle Identifier in your project is your signing id in Xcode'); + printError(' open ios/Runner.xcworkspace'); + printError(''); + printError("Also try selecting 'Product > Build' to fix the problem:"); return; } - if (result.xcodeBuildExecution != null) { - assert(result.xcodeBuildExecution.buildForPhysicalDevice != null); - assert(result.xcodeBuildExecution.buildCommands != null); - assert(result.xcodeBuildExecution.appDirectory != null); - if (result.xcodeBuildExecution.buildForPhysicalDevice && - result.xcodeBuildExecution.buildCommands.contains('build')) { - final RunResult checkBuildSettings = await runAsync( - result.xcodeBuildExecution.buildCommands..add('-showBuildSettings'), - workingDirectory: result.xcodeBuildExecution.appDirectory, - allowReentrantFlutter: true - ); - // Make sure the user has specified at least the DEVELOPMENT_TEAM (for automatic Xcode 8) - // signing or the PROVISIONING_PROFILE (for manual signing or Xcode 7). - if (checkBuildSettings.exitCode == 0 && - !checkBuildSettings.stdout?.contains(new RegExp(r'\bDEVELOPMENT_TEAM\b')) == true && - !checkBuildSettings.stdout?.contains(new RegExp(r'\bPROVISIONING_PROFILE\b')) == true) { - printError(noDevelopmentTeamInstruction, emphasis: true); - } - } - } } class XcodeBuildResult { diff --git a/packages/flutter_tools/test/ios/mac_test.dart b/packages/flutter_tools/test/ios/mac_test.dart new file mode 100644 index 0000000000000..e267b05ad8553 --- /dev/null +++ b/packages/flutter_tools/test/ios/mac_test.dart @@ -0,0 +1,176 @@ +// Copyright 2017 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_tools/src/application_package.dart'; +import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:test/test.dart'; + +import '../src/context.dart'; + +void main() { + group('Diagnose Xcode build failure', () { + BuildableIOSApp app; + + setUp(() { + app = new BuildableIOSApp( + projectBundleId: 'test.app', + buildSettings: { + 'For our purposes': 'a non-empty build settings map is valid', + }, + ); + }); + + testUsingContext('No provisioning profile shows message', () async { + final XcodeBuildResult buildResult = new XcodeBuildResult( + success: false, + stdout: ''' +Launching lib/main.dart on iPhone in debug mode... +Signing iOS app for device deployment using developer identity: "iPhone Developer: test@flutter.io (1122334455)" +Running Xcode build... 1.3s +Failed to build iOS app +Error output from Xcode build: +↳ + ** BUILD FAILED ** + + + The following build commands failed: + Check dependencies + (1 failure) +Xcode's output: +↳ + Build settings from command line: + ARCHS = arm64 + BUILD_DIR = /Users/blah/blah + DEVELOPMENT_TEAM = AABBCCDDEE + ONLY_ACTIVE_ARCH = YES + SDKROOT = iphoneos10.3 + + === CLEAN TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release === + + Check dependencies + [BCEROR]No profiles for 'com.yourcompany.test' were found: Xcode couldn't find a provisioning profile matching 'com.yourcompany.test'. + [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3' + [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3' + [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3' + + Create product structure + /bin/mkdir -p /Users/blah/Runner.app + + Clean.Remove clean /Users/blah/Runner.app.dSYM + builtin-rm -rf /Users/blah/Runner.app.dSYM + + Clean.Remove clean /Users/blah/Runner.app + builtin-rm -rf /Users/blah/Runner.app + + Clean.Remove clean /Users/blah/Runner-dfvicjniknvzghgwsthwtgcjhtsk/Build/Intermediates/Runner.build/Release-iphoneos/Runner.build + builtin-rm -rf /Users/blah/Runner-dfvicjniknvzghgwsthwtgcjhtsk/Build/Intermediates/Runner.build/Release-iphoneos/Runner.build + + ** CLEAN SUCCEEDED ** + + === BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release === + + Check dependencies + No profiles for 'com.yourcompany.test' were found: Xcode couldn't find a provisioning profile matching 'com.yourcompany.test'. + Code signing is required for product type 'Application' in SDK 'iOS 10.3' + Code signing is required for product type 'Application' in SDK 'iOS 10.3' + Code signing is required for product type 'Application' in SDK 'iOS 10.3' + +Could not build the precompiled application for the device. + +Error launching application on iPhone.''', + xcodeBuildExecution: new XcodeBuildExecution( + ['xcrun', 'xcodebuild', 'blah'], + '/blah/blah', + buildForPhysicalDevice: true + ), + ); + + await diagnoseXcodeBuildFailure(buildResult, app); + expect( + testLogger.errorText, + contains('No Provisioning Profile was found for your project\'s Bundle Identifier or your device.'), + ); + }); + + testUsingContext('No development team shows message', () async { + final XcodeBuildResult buildResult = new XcodeBuildResult( + success: false, + stdout: ''' +Running "flutter packages get" in flutter_gallery... 0.6s +Launching lib/main.dart on x in release mode... +Running pod install... 1.2s +Running Xcode build... 1.4s +Failed to build iOS app +Error output from Xcode build: +↳ + ** BUILD FAILED ** + + + The following build commands failed: + Check dependencies + (1 failure) +Xcode's output: +↳ + blah + + === CLEAN TARGET url_launcher OF PROJECT Pods WITH CONFIGURATION Release === + + Check dependencies + + blah + + === CLEAN TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release === + + Check dependencies + + blah + + === CLEAN TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release === + + Check dependencies + [BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor. + [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3' + [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3' + [BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3' + + blah + + ** CLEAN SUCCEEDED ** + + === BUILD TARGET url_launcher OF PROJECT Pods WITH CONFIGURATION Release === + + Check dependencies + + blah + + === BUILD TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release === + + Check dependencies + + blah + + === BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release === + + Check dependencies + Signing for "Runner" requires a development team. Select a development team in the project editor. + Code signing is required for product type 'Application' in SDK 'iOS 10.3' + Code signing is required for product type 'Application' in SDK 'iOS 10.3' + Code signing is required for product type 'Application' in SDK 'iOS 10.3' + +Could not build the precompiled application for the device.''', + xcodeBuildExecution: new XcodeBuildExecution( + ['xcrun', 'xcodebuild', 'blah'], + '/blah/blah', + buildForPhysicalDevice: true + ), + ); + + await diagnoseXcodeBuildFailure(buildResult, app); + expect( + testLogger.errorText, + contains('Building a deployable iOS app requires a selected Development Team with a Provisioning Profile'), + ); + }); + }); +} From b5365d9352e8e65ccf08e851c85adc8982914099 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 7 Jun 2017 16:10:29 -0700 Subject: [PATCH 056/110] Roll engine to 1f765cdba7aee9d0a953043209b05d2c59270391 (#10572) This picks up the latest engine build, built with ios_deployment_target set to iOS 8 (changed from iOS 7). --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index f0df42f1dd145..9164762ecfb76 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -ec8cbe0fb6c052a5f8b047fb88d7103ce77ae732 +1f765cdba7aee9d0a953043209b05d2c59270391 From 46b316c4904eeda64aaa2286178719db45c05869 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Wed, 7 Jun 2017 18:04:46 -0700 Subject: [PATCH 057/110] Change RawGestureDetector API to be better for strong mode Dart. (#10553) --- dev/manual_tests/lib/material_arc.dart | 22 +- .../flutter/lib/src/material/scaffold.dart | 5 + .../lib/src/widgets/gesture_detector.dart | 223 +++++++++++++----- .../flutter/lib/src/widgets/scrollable.dart | 50 ++-- 4 files changed, 216 insertions(+), 84 deletions(-) diff --git a/dev/manual_tests/lib/material_arc.dart b/dev/manual_tests/lib/material_arc.dart index 19aa04a2bb515..b6d71f671f28f 100644 --- a/dev/manual_tests/lib/material_arc.dart +++ b/dev/manual_tests/lib/material_arc.dart @@ -189,10 +189,13 @@ class _PointDemoState extends State<_PointDemo> { return new RawGestureDetector( behavior: _dragTarget == null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque, gestures: { - ImmediateMultiDragGestureRecognizer: (ImmediateMultiDragGestureRecognizer recognizer) { // ignore: map_value_type_not_assignable, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new ImmediateMultiDragGestureRecognizer()) - ..onStart = _handleOnStart; - } + ImmediateMultiDragGestureRecognizer: new GestureRecognizerFactoryWithHandlers( + () => new ImmediateMultiDragGestureRecognizer(), + (ImmediateMultiDragGestureRecognizer instance) { + instance + ..onStart = _handleOnStart; + }, + ), }, child: new ClipRect( child: new CustomPaint( @@ -359,10 +362,13 @@ class _RectangleDemoState extends State<_RectangleDemo> { return new RawGestureDetector( behavior: _dragTarget == null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque, gestures: { - ImmediateMultiDragGestureRecognizer: (ImmediateMultiDragGestureRecognizer recognizer) { // ignore: map_value_type_not_assignable, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new ImmediateMultiDragGestureRecognizer()) - ..onStart = _handleOnStart; - } + ImmediateMultiDragGestureRecognizer: new GestureRecognizerFactoryWithHandlers( + () => new ImmediateMultiDragGestureRecognizer(), + (ImmediateMultiDragGestureRecognizer instance) { + instance + ..onStart = _handleOnStart; + }, + ), }, child: new ClipRect( child: new CustomPaint( diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 5a337d52a3f8a..72e72d46ecb36 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -721,18 +721,22 @@ class ScaffoldState extends State with TickerProviderStateMixin { NavigationGestureController _backGestureController; bool _shouldHandleBackGesture() { + assert(mounted); return Theme.of(context).platform == TargetPlatform.iOS && Navigator.canPop(context); } void _handleDragStart(DragStartDetails details) { + assert(mounted); _backGestureController = Navigator.of(context).startPopGesture(); } void _handleDragUpdate(DragUpdateDetails details) { + assert(mounted); _backGestureController?.dragUpdate(details.primaryDelta / context.size.width); } void _handleDragEnd(DragEndDetails details) { + assert(mounted); final bool willPop = _backGestureController?.dragEnd(details.velocity.pixelsPerSecond.dx / context.size.width) ?? false; if (willPop) _currentBottomSheet?.close(); @@ -740,6 +744,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { } void _handleDragCancel() { + assert(mounted); final bool willPop = _backGestureController?.dragEnd(0.0) ?? false; if (willPop) _currentBottomSheet?.close(); diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index e039080a394d8..42d61a36364b8 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; @@ -33,13 +34,59 @@ export 'package:flutter/gestures.dart' show TapUpDetails, Velocity; -/// Signature for creating gesture recognizers. +/// Factory for creating gesture recognizers. /// -/// The `recognizer` argument is the gesture recognizer that currently occupies -/// the slot for which a gesture recognizer is being created. +/// `T` is the type of gesture recognizer this class manages. /// /// Used by [RawGestureDetector.gestures]. -typedef GestureRecognizer GestureRecognizerFactory(GestureRecognizer recognizer); +@optionalTypeArgs +abstract class GestureRecognizerFactory { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const GestureRecognizerFactory(); + + /// Must return an instance of T. + T constructor(); + + /// Must configure the given instance (which will have been created by + /// `constructor`). + /// + /// This normally means setting the callbacks. + void initializer(T instance); + + bool _debugAssertTypeMatches(Type type) { + assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.'); + return true; + } +} + +/// Signature for closures that implement [GestureRecognizerFactory.constructor]. +typedef T GestureRecognizerFactoryConstructor(); + +/// Signature for closures that implement [GestureRecognizerFactory.initializer]. +typedef void GestureRecognizerFactoryInitializer(T instance); + +/// Factory for creating gesture recognizers that delegates to callbacks. +/// +/// Used by [RawGestureDetector.gestures]. +class GestureRecognizerFactoryWithHandlers extends GestureRecognizerFactory { + /// Creates a gesture recognizer factory with the given callbacks. + /// + /// The arguments must not be null. + const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer) : + assert(_constructor != null), + assert(_initializer != null); + + final GestureRecognizerFactoryConstructor _constructor; + + final GestureRecognizerFactoryInitializer _initializer; + + @override + T constructor() => _constructor(); + + @override + void initializer(T instance) => _initializer(instance); +} /// A widget that detects gestures. /// @@ -231,27 +278,36 @@ class GestureDetector extends StatelessWidget { final Map gestures = {}; if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) { - gestures[TapGestureRecognizer] = (TapGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new TapGestureRecognizer()) - ..onTapDown = onTapDown - ..onTapUp = onTapUp - ..onTap = onTap - ..onTapCancel = onTapCancel; - }; + gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new TapGestureRecognizer(), + (TapGestureRecognizer instance) { + instance + ..onTapDown = onTapDown + ..onTapUp = onTapUp + ..onTap = onTap + ..onTapCancel = onTapCancel; + }, + ); } if (onDoubleTap != null) { - gestures[DoubleTapGestureRecognizer] = (DoubleTapGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new DoubleTapGestureRecognizer()) - ..onDoubleTap = onDoubleTap; - }; + gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new DoubleTapGestureRecognizer(), + (DoubleTapGestureRecognizer instance) { + instance + ..onDoubleTap = onDoubleTap; + }, + ); } if (onLongPress != null) { - gestures[LongPressGestureRecognizer] = (LongPressGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new LongPressGestureRecognizer()) - ..onLongPress = onLongPress; - }; + gestures[LongPressGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new LongPressGestureRecognizer(), + (LongPressGestureRecognizer instance) { + instance + ..onLongPress = onLongPress; + }, + ); } if (onVerticalDragDown != null || @@ -259,14 +315,17 @@ class GestureDetector extends StatelessWidget { onVerticalDragUpdate != null || onVerticalDragEnd != null || onVerticalDragCancel != null) { - gestures[VerticalDragGestureRecognizer] = (VerticalDragGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new VerticalDragGestureRecognizer()) - ..onDown = onVerticalDragDown - ..onStart = onVerticalDragStart - ..onUpdate = onVerticalDragUpdate - ..onEnd = onVerticalDragEnd - ..onCancel = onVerticalDragCancel; - }; + gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new VerticalDragGestureRecognizer(), + (VerticalDragGestureRecognizer instance) { + instance + ..onDown = onVerticalDragDown + ..onStart = onVerticalDragStart + ..onUpdate = onVerticalDragUpdate + ..onEnd = onVerticalDragEnd + ..onCancel = onVerticalDragCancel; + }, + ); } if (onHorizontalDragDown != null || @@ -274,14 +333,17 @@ class GestureDetector extends StatelessWidget { onHorizontalDragUpdate != null || onHorizontalDragEnd != null || onHorizontalDragCancel != null) { - gestures[HorizontalDragGestureRecognizer] = (HorizontalDragGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new HorizontalDragGestureRecognizer()) - ..onDown = onHorizontalDragDown - ..onStart = onHorizontalDragStart - ..onUpdate = onHorizontalDragUpdate - ..onEnd = onHorizontalDragEnd - ..onCancel = onHorizontalDragCancel; - }; + gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new HorizontalDragGestureRecognizer(), + (HorizontalDragGestureRecognizer instance) { + instance + ..onDown = onHorizontalDragDown + ..onStart = onHorizontalDragStart + ..onUpdate = onHorizontalDragUpdate + ..onEnd = onHorizontalDragEnd + ..onCancel = onHorizontalDragCancel; + }, + ); } if (onPanDown != null || @@ -289,23 +351,29 @@ class GestureDetector extends StatelessWidget { onPanUpdate != null || onPanEnd != null || onPanCancel != null) { - gestures[PanGestureRecognizer] = (PanGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new PanGestureRecognizer()) - ..onDown = onPanDown - ..onStart = onPanStart - ..onUpdate = onPanUpdate - ..onEnd = onPanEnd - ..onCancel = onPanCancel; - }; + gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new PanGestureRecognizer(), + (PanGestureRecognizer instance) { + instance + ..onDown = onPanDown + ..onStart = onPanStart + ..onUpdate = onPanUpdate + ..onEnd = onPanEnd + ..onCancel = onPanCancel; + }, + ); } if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) { - gestures[ScaleGestureRecognizer] = (ScaleGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771 - return (recognizer ??= new ScaleGestureRecognizer()) - ..onStart = onScaleStart - ..onUpdate = onScaleUpdate - ..onEnd = onScaleEnd; - }; + gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers( + () => new ScaleGestureRecognizer(), + (ScaleGestureRecognizer instance) { + instance + ..onStart = onScaleStart + ..onUpdate = onScaleUpdate + ..onEnd = onScaleEnd; + }, + ); } return new RawGestureDetector( @@ -321,13 +389,47 @@ class GestureDetector extends StatelessWidget { /// factories. /// /// For common gestures, use a [GestureRecognizer]. -/// RawGestureDetector is useful primarily when developing your +/// [RawGestureDetector] is useful primarily when developing your /// own gesture recognizers. +/// +/// Configuring the gesture recognizers requires a carefully constructed map, as +/// described in [gestures] and as shown in the example below. +/// +/// ## Sample code +/// +/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that +/// the code is being used inside a [State] object with a `_last` field that is +/// then displayed as the child of the gesture detector. +/// +/// ```dart +/// new RawGestureDetector( +/// gestures: { +/// TapGestureRecognizer: new GestureRecognizerFactoryWithHandlers( +/// () => new TapGestureRecognizer(), +/// (TapGestureRecognizer instance) { +/// instance +/// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }, +/// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }, +/// ..onTap = () { setState(() { _last = 'tap'; }); }, +/// ..onTapCancel = () { setState(() { _last = 'cancel'; }); }, +/// }, +/// ), +/// }, +/// child: new Container(width: 300.0, height: 300.0, color: Colors.yellow, child: new Text(_last)), +/// ) +/// ``` +/// +/// See also: +/// +/// * [GestureDetector], a less flexible but much simpler widget that does the same thing. +/// * [PointerListener], a widget that reports raw pointer events. +/// * [GestureRecognizer], the class that you extend to create a custom gesture recognizer. class RawGestureDetector extends StatefulWidget { /// Creates a widget that detects gestures. /// /// By default, gesture detectors contribute semantic information to the tree - /// that is used by assistive technology. + /// that is used by assistive technology. This can be controlled using + /// [excludeFromSemantics]. const RawGestureDetector({ Key key, this.child, @@ -342,6 +444,12 @@ class RawGestureDetector extends StatefulWidget { final Widget child; /// The gestures that this widget will attempt to recognize. + /// + /// This should be a map from [GestureRecognizer] subclasses to + /// [GestureRecognizerFactory] subclasses specialized with the same type. + /// + /// This value can be late-bound at layout time using + /// [RawGestureDetectorState.replaceGestureRecognizers]. final Map gestures; /// How this gesture detector should behave during hit testing. @@ -375,7 +483,7 @@ class RawGestureDetectorState extends State { } /// This method can be called after the build phase, during the - /// layout of the nearest descendant RenderObjectWidget of the + /// layout of the nearest descendant [RenderObjectWidget] of the /// gesture detector, to update the list of active gesture /// recognizers. /// @@ -383,6 +491,10 @@ class RawGestureDetectorState extends State { /// in their gesture detector, and then need to know the dimensions /// of the viewport and the viewport's child to determine whether /// the gesture detector should be enabled. + /// + /// The argument should follow the same conventions as + /// [RawGestureDetector.gestures]. It acts like a temporary replacement for + /// that value until the next build. void replaceGestureRecognizers(Map gestures) { assert(() { if (!context.findRenderObject().owner.debugDoingLayout) { @@ -419,9 +531,12 @@ class RawGestureDetectorState extends State { final Map oldRecognizers = _recognizers; _recognizers = {}; for (Type type in gestures.keys) { + assert(gestures[type] != null); + assert(gestures[type]._debugAssertTypeMatches(type)); assert(!_recognizers.containsKey(type)); - _recognizers[type] = gestures[type](oldRecognizers[type]); - assert(_recognizers[type].runtimeType == type); + _recognizers[type] = oldRecognizers[type] ?? gestures[type].constructor(); + assert(_recognizers[type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers[type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.'); + gestures[type].initializer(_recognizers[type]); } for (Type type in oldRecognizers.keys) { if (!_recognizers.containsKey(type)) diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index c317b457fe698..45e06e99cf249 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -327,32 +327,38 @@ class ScrollableState extends State with TickerProviderStateMixin switch (widget.axis) { case Axis.vertical: _gestureRecognizers = { - VerticalDragGestureRecognizer: (VerticalDragGestureRecognizer recognizer) { // ignore: map_value_type_not_assignable, https://github.com/flutter/flutter/issues/7173 - return (recognizer ??= new VerticalDragGestureRecognizer()) - ..onDown = _handleDragDown - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..onCancel = _handleDragCancel - ..minFlingDistance = _physics?.minFlingDistance - ..minFlingVelocity = _physics?.minFlingVelocity - ..maxFlingVelocity = _physics?.maxFlingVelocity; - } + VerticalDragGestureRecognizer: new GestureRecognizerFactoryWithHandlers( + () => new VerticalDragGestureRecognizer(), + (VerticalDragGestureRecognizer instance) { + instance + ..onDown = _handleDragDown + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onCancel = _handleDragCancel + ..minFlingDistance = _physics?.minFlingDistance + ..minFlingVelocity = _physics?.minFlingVelocity + ..maxFlingVelocity = _physics?.maxFlingVelocity; + }, + ), }; break; case Axis.horizontal: _gestureRecognizers = { - HorizontalDragGestureRecognizer: (HorizontalDragGestureRecognizer recognizer) { // ignore: map_value_type_not_assignable, https://github.com/flutter/flutter/issues/7173 - return (recognizer ??= new HorizontalDragGestureRecognizer()) - ..onDown = _handleDragDown - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..onCancel = _handleDragCancel - ..minFlingDistance = _physics?.minFlingDistance - ..minFlingVelocity = _physics?.minFlingVelocity - ..maxFlingVelocity = _physics?.maxFlingVelocity; - } + HorizontalDragGestureRecognizer: new GestureRecognizerFactoryWithHandlers( + () => new HorizontalDragGestureRecognizer(), + (HorizontalDragGestureRecognizer instance) { + instance + ..onDown = _handleDragDown + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onCancel = _handleDragCancel + ..minFlingDistance = _physics?.minFlingDistance + ..minFlingVelocity = _physics?.minFlingVelocity + ..maxFlingVelocity = _physics?.maxFlingVelocity; + }, + ), }; break; } From 0cef0aaf358fde6ca9efe5669d0a73774799b2b6 Mon Sep 17 00:00:00 2001 From: perlatus Date: Wed, 7 Jun 2017 21:32:29 -0400 Subject: [PATCH 058/110] Check for initialRoute before Navigator.defaultRouteName (#10216) * Check for initialRoute before Navigator.defaultRouteName * Default initialRoute to Navigator.defaultRouteName * Take suggestions from code review * Add test for old and new routes behavior * Revert "Add test for old and new routes behavior" This reverts commit 282fb64b165ed532583e9a5d2e4debe29469fba4. * Retry: without dartfmt, with dartanalyzer * Rename tests, check the routes are taken * Fix flutter analyze --flutter-repo warnings * Add test for initial vs default route * Update test and fix analyzer warnings * Add test for initial route only being used initially --- packages/flutter/lib/src/material/app.dart | 11 +-- packages/flutter/test/material/app_test.dart | 72 ++++++++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index 425944da8516e..a860860d86a23 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -42,8 +42,9 @@ 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] (`'/'`). + /// given. If only [routes] is given, it must include an entry for the + /// [initialRoute], which defaults to [Navigator.defaultRouteName] + /// (`'/'`). /// /// This class creates an instance of [WidgetsApp]. MaterialApp({ @@ -53,7 +54,7 @@ class MaterialApp extends StatefulWidget { this.theme, this.home, this.routes: const {}, - this.initialRoute, + this.initialRoute: Navigator.defaultRouteName, this.onGenerateRoute, this.onLocaleChanged, this.navigatorObservers: const [], @@ -65,8 +66,8 @@ class MaterialApp extends StatefulWidget { this.debugShowCheckedModeBanner: true }) : assert(debugShowMaterialGrid != null), assert(routes != null), - assert(!routes.containsKey(Navigator.defaultRouteName) || (home == null)), - assert(routes.containsKey(Navigator.defaultRouteName) || (home != null) || (onGenerateRoute != null)), + assert(!routes.containsKey(initialRoute) || (home == null)), + assert(routes.containsKey(initialRoute) || (home != null) || (onGenerateRoute != null)), super(key: key); /// A one-line description of this app for use in the window manager. diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart index e9b2662214a1f..92249a5ae1244 100644 --- a/packages/flutter/test/material/app_test.dart +++ b/packages/flutter/test/material/app_test.dart @@ -143,4 +143,76 @@ void main() { expect(find.text('Home'), findsOneWidget); }); + + testWidgets('Default initialRoute', (WidgetTester tester) async { + await tester.pumpWidget(new MaterialApp(routes: { + '/': (BuildContext context) => const Text('route "/"'), + })); + + expect(find.text('route "/"'), findsOneWidget); + }); + + testWidgets('Custom initialRoute only', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + initialRoute: '/a', + routes: { + '/a': (BuildContext context) => const Text('route "/a"'), + }, + ) + ); + + expect(find.text('route "/a"'), findsOneWidget); + }); + + testWidgets('Custom initialRoute along with Navigator.defaultRouteName', (WidgetTester tester) async { + final Map routes = { + '/': (BuildContext context) => const Text('route "/"'), + '/a': (BuildContext context) => const Text('route "/a"'), + '/b': (BuildContext context) => const Text('route "/b"'), + }; + + await tester.pumpWidget( + new MaterialApp( + initialRoute: '/a', + routes: routes, + ) + ); + expect(find.text('route "/"'), findsNothing); + expect(find.text('route "/a"'), findsOneWidget); + expect(find.text('route "/b"'), findsNothing); + }); + + testWidgets('Make sure initialRoute is only used the first time', (WidgetTester tester) async { + final Map routes = { + '/': (BuildContext context) => const Text('route "/"'), + '/a': (BuildContext context) => const Text('route "/a"'), + '/b': (BuildContext context) => const Text('route "/b"'), + }; + + await tester.pumpWidget( + new MaterialApp( + initialRoute: '/a', + routes: routes, + ) + ); + expect(find.text('route "/"'), findsNothing); + expect(find.text('route "/a"'), findsOneWidget); + expect(find.text('route "/b"'), findsNothing); + + await tester.pumpWidget( + new MaterialApp( + initialRoute: '/b', + routes: routes, + ) + ); + expect(find.text('route "/"'), findsNothing); + expect(find.text('route "/a"'), findsOneWidget); + expect(find.text('route "/b"'), findsNothing); + + await tester.pumpWidget(new MaterialApp(routes: routes)); + expect(find.text('route "/"'), findsNothing); + expect(find.text('route "/a"'), findsOneWidget); + expect(find.text('route "/b"'), findsNothing); + }); } From 6ccc618abd6de2b33807a72030c075099216cd6e Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Wed, 7 Jun 2017 18:51:28 -0700 Subject: [PATCH 059/110] Revert "Improved support for saving and restoring the scroll offset, etc (#10517)" (#10575) This reverts commit c8e4cbf27d58bec1342d0287f389926fa0e9d9a6. --- .../catalog/lib/expansion_tile_sample.dart | 2 +- .../flutter/lib/src/widgets/framework.dart | 6 +- .../flutter/lib/src/widgets/page_storage.dart | 103 +++++++----------- .../flutter/lib/src/widgets/page_view.dart | 25 +---- .../lib/src/widgets/scroll_controller.dart | 27 +---- .../lib/src/widgets/scroll_position.dart | 21 +--- .../scroll_position_with_single_context.dart | 13 +-- .../flutter/test/widgets/page_view_test.dart | 6 +- .../test/widgets/scroll_controller_test.dart | 47 -------- 9 files changed, 54 insertions(+), 196 deletions(-) diff --git a/examples/catalog/lib/expansion_tile_sample.dart b/examples/catalog/lib/expansion_tile_sample.dart index c028b149fdbb4..2213ee18c5520 100644 --- a/examples/catalog/lib/expansion_tile_sample.dart +++ b/examples/catalog/lib/expansion_tile_sample.dart @@ -76,7 +76,7 @@ class EntryItem extends StatelessWidget { if (root.children.isEmpty) return new ListTile(title: new Text(root.title)); return new ExpansionTile( - key: new PageStorageKey(root), + key: new ValueKey(root), title: new Text(root.title), children: root.children.map(_buildTiles).toList(), ); diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 8666889607b98..a0f9fd8fee75c 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -322,10 +322,10 @@ class LabeledGlobalKey> extends GlobalKey { @override String toString() { - final String label = _debugLabel != null ? ' $_debugLabel' : ''; + final String tag = _debugLabel != null ? ' $_debugLabel' : '#$hashCode'; if (runtimeType == LabeledGlobalKey) - return '[GlobalKey#$hashCode$label]'; - return '[$runtimeType#$hashCode$label]'; + return '[GlobalKey$tag]'; + return '[$runtimeType$tag]'; } } diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index c2611e274cad4..7d71ac87dbf74 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -6,67 +6,43 @@ import 'package:flutter/foundation.dart'; import 'framework.dart'; -/// A [ValueKey] that defines where [PageStorage] values will be saved. -/// -/// [Scrollable]s ([ScrollPosition]s really) use [PageStorage] to save their -/// scroll offset. Each time a scroll completes, the scrollable's page -/// storage is updated. -/// -/// [PageStorage] is used to save and restore values that can outlive the widget. -/// The values are stored in a per-route [Map] whose keys are defined by the -/// [PageStorageKey]s for the widget and its ancestors. To make it possible -/// for a saved value to be found when a widget is recreated, the key's values -/// must not be objects whose identity will change each time the widget is created. -/// -/// For example, to ensure that the scroll offsets for the scrollable within -/// each `MyScrollableTabView` below are restored when the [TabBarView] -/// is recreated, we've specified [PageStorageKey]s whose values are the the -/// tabs' string labels. -/// -/// ```dart -/// new TabBarView( -/// children: myTabs.map((Tab tab) { -/// new MyScrollableTabView( -/// key: new PageStorageKey(tab.text), // like 'Tab 1' -/// tab: tab, -/// ), -/// }), -///) -/// ``` -class PageStorageKey extends ValueKey { - /// Creates a [ValueKey] that defines where [PageStorage] values will be saved. - const PageStorageKey(T value) : super(value); -} - class _StorageEntryIdentifier { - _StorageEntryIdentifier(this.clientType, this.keys) { - assert(clientType != null); - assert(keys != null); + Type clientType; + List keys; + + void addKey(Key key) { + assert(key != null); + assert(key is! GlobalKey); + keys ??= []; + keys.add(key); } - final Type clientType; - final List> keys; + GlobalKey scopeKey; @override bool operator ==(dynamic other) { - if (other.runtimeType != runtimeType) + if (other is! _StorageEntryIdentifier) return false; final _StorageEntryIdentifier typedOther = other; - if (clientType != typedOther.clientType || keys.length != typedOther.keys.length) + if (clientType != typedOther.clientType || + scopeKey != typedOther.scopeKey || + keys?.length != typedOther.keys?.length) return false; - for (int index = 0; index < keys.length; index += 1) { - if (keys[index] != typedOther.keys[index]) - return false; + if (keys != null) { + for (int index = 0; index < keys.length; index += 1) { + if (keys[index] != typedOther.keys[index]) + return false; + } } return true; } @override - int get hashCode => hashValues(clientType, hashList(keys)); + int get hashCode => hashValues(clientType, scopeKey, hashList(keys)); @override String toString() { - return 'StorageEntryIdentifier($clientType, ${keys?.join(":")})'; + return 'StorageEntryIdentifier($clientType, $scopeKey, ${keys?.join(":")})'; } } @@ -75,26 +51,27 @@ class _StorageEntryIdentifier { /// Useful for storing per-page state that persists across navigations from one /// page to another. class PageStorageBucket { - bool _maybeAddKey(BuildContext context, List> keys) { - final Widget widget = context.widget; - final Key key = widget.key; - if (key is PageStorageKey) - keys.add(key); - return widget is! PageStorage; - } - - List> _allKeys(BuildContext context) { - final List> keys = >[]; - if (_maybeAddKey(context, keys)) { + _StorageEntryIdentifier _computeStorageIdentifier(BuildContext context) { + final _StorageEntryIdentifier result = new _StorageEntryIdentifier(); + result.clientType = context.widget.runtimeType; + Key lastKey = context.widget.key; + if (lastKey is! GlobalKey) { + if (lastKey != null) + result.addKey(lastKey); context.visitAncestorElements((Element element) { - return _maybeAddKey(element, keys); + if (element.widget.key is GlobalKey) { + lastKey = element.widget.key; + return false; + } else if (element.widget.key != null) { + result.addKey(element.widget.key); + } + return true; }); + return result; } - return keys; - } - - _StorageEntryIdentifier _computeIdentifier(BuildContext context) { - return new _StorageEntryIdentifier(context.widget.runtimeType, _allKeys(context)); + assert(lastKey is GlobalKey); + result.scopeKey = lastKey; + return result; } Map _storage; @@ -112,13 +89,13 @@ class PageStorageBucket { /// identifier will change. void writeState(BuildContext context, dynamic data, { Object identifier }) { _storage ??= {}; - _storage[identifier ?? _computeIdentifier(context)] = data; + _storage[identifier ?? _computeStorageIdentifier(context)] = data; } /// Read given data from into this page storage bucket using an identifier /// computed from the given context. More about [identifier] in [writeState]. dynamic readState(BuildContext context, { Object identifier }) { - return _storage != null ? _storage[identifier ?? _computeIdentifier(context)] : null; + return _storage != null ? _storage[identifier ?? _computeStorageIdentifier(context)] : null; } } diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 54dba500b886e..355deff5e4f10 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -38,36 +38,17 @@ import 'viewport.dart'; class PageController extends ScrollController { /// Creates a page controller. /// - /// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null. + /// The [initialPage] and [viewportFraction] arguments must not be null. PageController({ this.initialPage: 0, - this.keepPage: true, this.viewportFraction: 1.0, }) : assert(initialPage != null), - assert(keepPage != null), assert(viewportFraction != null), assert(viewportFraction > 0.0); /// The page to show when first creating the [PageView]. final int initialPage; - /// Save the current [page] with [PageStorage] and restore it if - /// this controller's scrollable is recreated. - /// - /// If this property is set to false, the current [page] is never saved - /// and [initialPage] is always used to initialize the scroll offset. - /// If true (the default), the initial page is used the first time the - /// controller's scrollable is created, since there's isn't a page to - /// restore yet. Subsequently the saved page is restored and - /// [initialPage] is ignored. - /// - /// See also: - /// - /// * [PageStorageKey], which should be used when more than one - //// scrollable appears in the same route, to distinguish the [PageStorage] - /// locations used to save scroll offsets. - final bool keepPage; - /// The fraction of the viewport that each page should occupy. /// /// Defaults to 1.0, which means each page fills the viewport in the scrolling @@ -135,7 +116,6 @@ class PageController extends ScrollController { physics: physics, context: context, initialPage: initialPage, - keepPage: keepPage, viewportFraction: viewportFraction, oldPosition: oldPosition, ); @@ -170,11 +150,9 @@ class _PagePosition extends ScrollPositionWithSingleContext { ScrollPhysics physics, ScrollContext context, this.initialPage: 0, - bool keepPage: true, double viewportFraction: 1.0, ScrollPosition oldPosition, }) : assert(initialPage != null), - assert(keepPage != null), assert(viewportFraction != null), assert(viewportFraction > 0.0), _viewportFraction = viewportFraction, @@ -183,7 +161,6 @@ class _PagePosition extends ScrollPositionWithSingleContext { physics: physics, context: context, initialPixels: null, - keepScrollOffset: keepPage, oldPosition: oldPosition, ); diff --git a/packages/flutter/lib/src/widgets/scroll_controller.dart b/packages/flutter/lib/src/widgets/scroll_controller.dart index 4f8b268f0b092..d050414652a22 100644 --- a/packages/flutter/lib/src/widgets/scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/scroll_controller.dart @@ -39,40 +39,20 @@ import 'scroll_position_with_single_context.dart'; class ScrollController extends ChangeNotifier { /// Creates a controller for a scrollable widget. /// - /// The values of `initialScrollOffset` and `keepScrollOffset` must not be null. + /// The [initialScrollOffset] must not be null. ScrollController({ this.initialScrollOffset: 0.0, - this.keepScrollOffset: true, this.debugLabel, - }) : assert(initialScrollOffset != null), - assert(keepScrollOffset != null); + }) : assert(initialScrollOffset != null); /// The initial value to use for [offset]. /// /// New [ScrollPosition] objects that are created and attached to this - /// controller will have their offset initialized to this value - /// if [keepScrollOffset] is false or a scroll offset hasn't been saved yet. + /// controller will have their offset initialized to this value. /// /// Defaults to 0.0. final double initialScrollOffset; - /// Each time a scroll completes, save the current scroll [offset] with - /// [PageStorage] and restore it if this controller's scrollable is recreated. - /// - /// If this property is set to false, the scroll offset is never saved - /// and [initialScrollOffset] is always used to initialize the scroll - /// offset. If true (the default), the initial scroll offset is used the - /// first time the controller's scrollable is created, since there's no - /// scroll offset to restore yet. Subsequently the saved offset is - /// restored and [initialScrollOffset] is ignored. - /// - /// See also: - /// - /// * [PageStorageKey], which should be used when more than one - //// scrollable appears in the same route, to distinguish the [PageStorage] - /// locations used to save scroll offsets. - final bool keepScrollOffset; - /// A label that is used in the [toString] output. Intended to aid with /// identifying scroll controller instances in debug output. final String debugLabel; @@ -224,7 +204,6 @@ class ScrollController extends ChangeNotifier { physics: physics, context: context, initialPixels: initialScrollOffset, - keepScrollOffset: keepScrollOffset, oldPosition: oldPosition, debugLabel: debugLabel, ); diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index c96ee6ea2fb93..3785621a75c65 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -61,22 +61,17 @@ export 'scroll_activity.dart' show ScrollHoldController; abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Creates an object that determines which portion of the content is visible /// in a scroll view. - /// - /// The [physics], [context], and [keepScrollOffset] parameters must not be null. ScrollPosition({ @required this.physics, @required this.context, - this.keepScrollOffset: true, ScrollPosition oldPosition, this.debugLabel, }) : assert(physics != null), assert(context != null), - assert(context.vsync != null), - assert(keepScrollOffset != null) { + assert(context.vsync != null) { if (oldPosition != null) absorb(oldPosition); - if (keepScrollOffset) - restoreScrollOffset(); + restoreScrollOffset(); } /// How the scroll position should respond to user input. @@ -90,15 +85,6 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Typically implemented by [ScrollableState]. final ScrollContext context; - /// Save the current scroll [offset] with [PageStorage] and restore it if - /// this scroll position's scrollable is recreated. - /// - /// See also: - /// - /// * [ScrollController.keepScrollOffset] and [PageController.keepPage], which - /// create scroll positions and initialize this property. - final bool keepScrollOffset; - /// A label that is used in the [toString] output. Intended to aid with /// identifying animation controller instances in debug output. final String debugLabel; @@ -553,8 +539,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// This also saves the scroll offset using [saveScrollOffset]. void didEndScroll() { activity.dispatchScrollEndNotification(cloneMetrics(), context.notificationContext); - if (keepScrollOffset) - saveScrollOffset(); + saveScrollOffset(); } /// Called by [setPixels] to report overscroll when an attempt is made to diff --git a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart index 4d7b81ba6521d..287f739fdda5c 100644 --- a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart +++ b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart @@ -46,24 +46,13 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc /// imperative that the value be set, using [correctPixels], as soon as /// [applyNewDimensions] is invoked, before calling the inherited /// implementation of that method. - /// - /// If [keepScrollOffset] is true (the default), the current scroll offset is - /// saved with [PageStorage] and restored it if this scroll position's scrollable - /// is recreated. ScrollPositionWithSingleContext({ @required ScrollPhysics physics, @required ScrollContext context, double initialPixels: 0.0, - bool keepScrollOffset: true, ScrollPosition oldPosition, String debugLabel, - }) : super( - physics: physics, - context: context, - keepScrollOffset: keepScrollOffset, - oldPosition: oldPosition, - debugLabel: debugLabel, - ) { + }) : super(physics: physics, context: context, oldPosition: oldPosition, debugLabel: debugLabel) { // If oldPosition is not null, the superclass will first call absorb(), // which may set _pixels and _activity. if (pixels == null && initialPixels != null) diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index 0089e2dd2c827..29a4d17b7f81b 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -441,14 +441,12 @@ void main() { ), ); expect(controller.page, 2); - - final PageController controller2 = new PageController(keepPage: false); await tester.pumpWidget( new PageStorage( bucket: bucket, child: new PageView( key: const Key('Check it again against your list and see consistency!'), - controller: controller2, + controller: controller, children: [ const Placeholder(), const Placeholder(), @@ -457,6 +455,6 @@ void main() { ), ), ); - expect(controller2.page, 0); + expect(controller.page, 0); }); } diff --git a/packages/flutter/test/widgets/scroll_controller_test.dart b/packages/flutter/test/widgets/scroll_controller_test.dart index 5ae0e4d5eb0f4..f935edcd57904 100644 --- a/packages/flutter/test/widgets/scroll_controller_test.dart +++ b/packages/flutter/test/widgets/scroll_controller_test.dart @@ -259,51 +259,4 @@ void main() { await tester.drag(find.byType(ListView), const Offset(0.0, -130.0)); expect(log, isEmpty); }); - - testWidgets('keepScrollOffset', (WidgetTester tester) async { - final PageStorageBucket bucket = new PageStorageBucket(); - - Widget buildFrame(ScrollController controller) { - return new PageStorage( - bucket: bucket, - child: new ListView( - key: new UniqueKey(), // it's a different ListView every time - controller: controller, - children: new List.generate(50, (int index) { - return new Container(height: 100.0, child: new Text('Item $index')); - }).toList(), - ), - ); - } - - // keepScrollOffset: true (the default). The scroll offset is restored - // when the ListView is recreated with a new ScrollController. - - // The initialScrollOffset is used in this case, because there's no saved - // scroll offset. - ScrollController controller = new ScrollController(initialScrollOffset: 200.0); - await tester.pumpWidget(buildFrame(controller)); - expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 2')), Offset.zero); - - controller.jumpTo(2000.0); - await tester.pump(); - expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero); - - // The initialScrollOffset isn't used in this case, because the scrolloffset - // can be restored. - controller = new ScrollController(initialScrollOffset: 25.0); - await tester.pumpWidget(buildFrame(controller)); - expect(controller.offset, 2000.0); - expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero); - - // keepScrollOffset: false. The scroll offset is -not- restored - // when the ListView is recreated with a new ScrollController and - // the initialScrollOffset is used. - - controller = new ScrollController(keepScrollOffset: false, initialScrollOffset: 100.0); - await tester.pumpWidget(buildFrame(controller)); - expect(controller.offset, 100.0); - expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 1')), Offset.zero); - - }); } From 739b379ecae3744fb17ed5516cf0bba024ee7dad Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Thu, 8 Jun 2017 09:54:38 +0200 Subject: [PATCH 060/110] Give changelog proper extension (#10577) --- .../templates/plugin/{CHANGELOG.md => CHANGELOG.md.tmpl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/flutter_tools/templates/plugin/{CHANGELOG.md => CHANGELOG.md.tmpl} (100%) diff --git a/packages/flutter_tools/templates/plugin/CHANGELOG.md b/packages/flutter_tools/templates/plugin/CHANGELOG.md.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/CHANGELOG.md rename to packages/flutter_tools/templates/plugin/CHANGELOG.md.tmpl From 4d2806e7e529da1aeff204eb22f9a709b508a3ab Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Thu, 8 Jun 2017 10:51:56 +0200 Subject: [PATCH 061/110] Remove comments unrelated to plugin example app (#10532) * Remove comments unrelated to plugin example app * Review feedback --- .../templates/create/lib/main.dart.tmpl | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/flutter_tools/templates/create/lib/main.dart.tmpl b/packages/flutter_tools/templates/create/lib/main.dart.tmpl index 0038a09c43981..e0eef759559a8 100644 --- a/packages/flutter_tools/templates/create/lib/main.dart.tmpl +++ b/packages/flutter_tools/templates/create/lib/main.dart.tmpl @@ -16,6 +16,7 @@ void main() { runApp(new MyApp()); } +{{^withPluginHook}} class MyApp extends StatelessWidget { // This widget is the root of your application. @override @@ -57,7 +58,6 @@ class MyHomePage extends StatefulWidget { _MyHomePageState createState() => new _MyHomePageState(); } -{{^withPluginHook}} class _MyHomePageState extends State { int _counter = 0; @@ -129,7 +129,12 @@ class _MyHomePageState extends State { } {{/withPluginHook}} {{#withPluginHook}} -class _MyHomePageState extends State { +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => new _MyAppState(); +} + +class _MyAppState extends State { String _platformVersion = 'Unknown'; @override @@ -161,11 +166,15 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { - return new Scaffold( - appBar: new AppBar( - title: new Text('Plugin example app'), + return new MaterialApp( + home: new Scaffold( + appBar: new AppBar( + title: new Text('Plugin example app'), + ), + body: new Center( + child: new Text('Running on: $_platformVersion\n'), + ), ), - body: new Center(child: new Text('Running on: $_platformVersion\n')), ); } } From dfc7788c8ea42e2b4d448928e774b2345b9ac848 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Thu, 8 Jun 2017 11:14:02 -0700 Subject: [PATCH 062/110] update_devicelab_manifest (#10583) --- dev/devicelab/manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 2515df07e994b..cccac3fc86c34 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -130,7 +130,7 @@ tasks: description: > Builds sample catalog markdown pages and Android screenshots stage: devicelab - required_agent_capabilities: ["has-android-device"] + required_agent_capabilities: ["linux/android"] flaky: true # iOS on-device tests From 1eaefe18376e6bf61ed958fb952736de8b0281e2 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Thu, 8 Jun 2017 22:04:18 +0200 Subject: [PATCH 063/110] more const for immutables (#10563) --- examples/catalog/lib/animated_list.dart | 2 +- examples/catalog/lib/expansion_tile_sample.dart | 2 +- examples/flutter_gallery/lib/demo/animation/home.dart | 4 ++-- .../flutter_gallery/lib/demo/animation/widgets.dart | 4 ++-- examples/flutter_gallery/lib/demo/colors_demo.dart | 2 +- .../flutter_gallery/lib/demo/shrine/shrine_home.dart | 4 ++-- .../flutter_gallery/lib/demo/shrine/shrine_order.dart | 6 +++--- .../flutter_gallery/lib/demo/shrine/shrine_page.dart | 2 +- examples/flutter_gallery/lib/demo/typography_demo.dart | 2 +- examples/flutter_gallery/lib/gallery/drawer.dart | 2 +- examples/flutter_gallery/lib/gallery/home.dart | 2 +- examples/flutter_gallery/lib/gallery/item.dart | 2 +- examples/flutter_gallery/lib/gallery/updates.dart | 2 +- examples/platform_view/lib/main.dart | 4 ++-- packages/flutter/lib/src/services/message_codec.dart | 2 +- packages/flutter/lib/src/widgets/animated_list.dart | 2 +- packages/flutter/lib/src/widgets/async.dart | 2 +- packages/flutter/lib/src/widgets/framework.dart | 4 ++-- .../flutter/lib/src/widgets/nested_scroll_view.dart | 2 +- .../flutter/test/services/haptic_feedback_test.dart | 2 +- .../flutter/test/services/platform_channel_test.dart | 10 +++++----- .../flutter/test/services/system_navigator_test.dart | 2 +- packages/flutter/test/services/system_sound_test.dart | 6 +++--- packages/flutter/test/widgets/async_test.dart | 2 +- .../test/widgets/layout_builder_mutations_test.dart | 2 +- 25 files changed, 38 insertions(+), 38 deletions(-) diff --git a/examples/catalog/lib/animated_list.dart b/examples/catalog/lib/animated_list.dart index 6fcba2eddda36..85101a74292fc 100644 --- a/examples/catalog/lib/animated_list.dart +++ b/examples/catalog/lib/animated_list.dart @@ -152,7 +152,7 @@ class ListModel { /// This widget's height is based on the animation parameter, it varies /// from 0 to 128 as the animation varies from 0.0 to 1.0. class CardItem extends StatelessWidget { - CardItem({ + const CardItem({ Key key, @required this.animation, this.onTap, diff --git a/examples/catalog/lib/expansion_tile_sample.dart b/examples/catalog/lib/expansion_tile_sample.dart index 2213ee18c5520..a61ff8676d887 100644 --- a/examples/catalog/lib/expansion_tile_sample.dart +++ b/examples/catalog/lib/expansion_tile_sample.dart @@ -68,7 +68,7 @@ final List data = [ // Displays one Entry. If the entry has children then it's displayed // with an ExpansionTile. class EntryItem extends StatelessWidget { - EntryItem(this.entry); + const EntryItem(this.entry); final Entry entry; diff --git a/examples/flutter_gallery/lib/demo/animation/home.dart b/examples/flutter_gallery/lib/demo/animation/home.dart index e01e158d1b40c..503b766a93013 100644 --- a/examples/flutter_gallery/lib/demo/animation/home.dart +++ b/examples/flutter_gallery/lib/demo/animation/home.dart @@ -75,7 +75,7 @@ class _RenderStatusBarPaddingSliver extends RenderSliver { } class _StatusBarPaddingSliver extends SingleChildRenderObjectWidget { - _StatusBarPaddingSliver({ + const _StatusBarPaddingSliver({ Key key, @required this.maxHeight, this.scrollFactor: 5.0, @@ -369,7 +369,7 @@ class _AllSectionsView extends AnimatedWidget { // app bar's height is _kAppBarMidHeight and only one section heading is // visible. class _SnappingScrollPhysics extends ClampingScrollPhysics { - _SnappingScrollPhysics({ + const _SnappingScrollPhysics({ ScrollPhysics parent, @required this.midScrollOffset, }) : assert(midScrollOffset != null), diff --git a/examples/flutter_gallery/lib/demo/animation/widgets.dart b/examples/flutter_gallery/lib/demo/animation/widgets.dart index c00945010e3e2..1207acc13b49e 100644 --- a/examples/flutter_gallery/lib/demo/animation/widgets.dart +++ b/examples/flutter_gallery/lib/demo/animation/widgets.dart @@ -11,7 +11,7 @@ const double kSectionIndicatorWidth = 32.0; // The card for a single section. Displays the section's gradient and background image. class SectionCard extends StatelessWidget { - SectionCard({ Key key, @required this.section }) + const SectionCard({ Key key, @required this.section }) : assert(section != null), super(key: key); @@ -60,7 +60,7 @@ class SectionTitle extends StatelessWidget { color: const Color(0x19000000), ); - SectionTitle({ + const SectionTitle({ Key key, @required this.section, @required this.scale, diff --git a/examples/flutter_gallery/lib/demo/colors_demo.dart b/examples/flutter_gallery/lib/demo/colors_demo.dart index f76eae777f1e4..35a1be8a5d9e0 100644 --- a/examples/flutter_gallery/lib/demo/colors_demo.dart +++ b/examples/flutter_gallery/lib/demo/colors_demo.dart @@ -42,7 +42,7 @@ final List allPalettes = [ class ColorItem extends StatelessWidget { - ColorItem({ + const ColorItem({ Key key, @required this.index, @required this.color, diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart index c4b035e89120b..4383bedc305d5 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart @@ -116,7 +116,7 @@ class _ShrineGridDelegate extends SliverGridDelegate { // Displays the Vendor's name and avatar. class _VendorItem extends StatelessWidget { - _VendorItem({ Key key, @required this.vendor }) + const _VendorItem({ Key key, @required this.vendor }) : assert(vendor != null), super(key: key); @@ -294,7 +294,7 @@ class _Heading extends StatelessWidget { // A card that displays a product's image, price, and vendor. The _ProductItem // cards appear in a grid below the heading. class _ProductItem extends StatelessWidget { - _ProductItem({ Key key, @required this.product, this.onPressed }) + const _ProductItem({ Key key, @required this.product, this.onPressed }) : assert(product != null), super(key: key); diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart index a8c373914ee6b..a55f37b08ea64 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_order.dart @@ -12,7 +12,7 @@ import 'shrine_types.dart'; // Displays the product title's, description, and order quantity dropdown. class _ProductItem extends StatelessWidget { - _ProductItem({ + const _ProductItem({ Key key, @required this.product, @required this.quantity, @@ -69,7 +69,7 @@ class _ProductItem extends StatelessWidget { // Vendor name and description class _VendorItem extends StatelessWidget { - _VendorItem({ Key key, @required this.vendor }) + const _VendorItem({ Key key, @required this.vendor }) : assert(vendor != null), super(key: key); @@ -140,7 +140,7 @@ class _HeadingLayout extends MultiChildLayoutDelegate { // Describes a product and vendor in detail, supports specifying // a order quantity (0-5). Appears at the top of the OrderPage. class _Heading extends StatelessWidget { - _Heading({ + const _Heading({ Key key, @required this.product, @required this.quantity, diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart index 908a8375c91f5..d6c401860af97 100644 --- a/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart +++ b/examples/flutter_gallery/lib/demo/shrine/shrine_page.dart @@ -15,7 +15,7 @@ enum ShrineAction { } class ShrinePage extends StatefulWidget { - ShrinePage({ + const ShrinePage({ Key key, @required this.scaffoldKey, @required this.body, diff --git a/examples/flutter_gallery/lib/demo/typography_demo.dart b/examples/flutter_gallery/lib/demo/typography_demo.dart index 9dd9181da7b90..b39f6328dffcd 100644 --- a/examples/flutter_gallery/lib/demo/typography_demo.dart +++ b/examples/flutter_gallery/lib/demo/typography_demo.dart @@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class TextStyleItem extends StatelessWidget { - TextStyleItem({ + const TextStyleItem({ Key key, @required this.name, @required this.style, diff --git a/examples/flutter_gallery/lib/gallery/drawer.dart b/examples/flutter_gallery/lib/gallery/drawer.dart index 8a5f2ef27eb92..900719c18e118 100644 --- a/examples/flutter_gallery/lib/gallery/drawer.dart +++ b/examples/flutter_gallery/lib/gallery/drawer.dart @@ -88,7 +88,7 @@ class _GalleryDrawerHeaderState extends State { } class GalleryDrawer extends StatelessWidget { - GalleryDrawer({ + const GalleryDrawer({ Key key, this.useLightTheme, @required this.onThemeChanged, diff --git a/examples/flutter_gallery/lib/gallery/home.dart b/examples/flutter_gallery/lib/gallery/home.dart index 9f1ebb22419df..03a766e27358d 100644 --- a/examples/flutter_gallery/lib/gallery/home.dart +++ b/examples/flutter_gallery/lib/gallery/home.dart @@ -66,7 +66,7 @@ class _AppBarBackground extends StatelessWidget { } class GalleryHome extends StatefulWidget { - GalleryHome({ + const GalleryHome({ Key key, this.useLightTheme, @required this.onThemeChanged, diff --git a/examples/flutter_gallery/lib/gallery/item.dart b/examples/flutter_gallery/lib/gallery/item.dart index fe118fd3cfaeb..dafa548b4cfe5 100644 --- a/examples/flutter_gallery/lib/gallery/item.dart +++ b/examples/flutter_gallery/lib/gallery/item.dart @@ -12,7 +12,7 @@ import '../demo/all.dart'; typedef Widget GalleryDemoBuilder(); class GalleryItem extends StatelessWidget { - GalleryItem({ + const GalleryItem({ @required this.title, this.subtitle, @required this.category, diff --git a/examples/flutter_gallery/lib/gallery/updates.dart b/examples/flutter_gallery/lib/gallery/updates.dart index e100fa53e54ea..fabafe0998e1c 100644 --- a/examples/flutter_gallery/lib/gallery/updates.dart +++ b/examples/flutter_gallery/lib/gallery/updates.dart @@ -12,7 +12,7 @@ import 'package:url_launcher/url_launcher.dart'; typedef Future UpdateUrlFetcher(); class Updater extends StatefulWidget { - Updater({ @required this.updateUrlFetcher, this.child, Key key }) + const Updater({ @required this.updateUrlFetcher, this.child, Key key }) : assert(updateUrlFetcher != null), super(key: key); diff --git a/examples/platform_view/lib/main.dart b/examples/platform_view/lib/main.dart index b6426b5eff3a6..a1ab83a17118a 100644 --- a/examples/platform_view/lib/main.dart +++ b/examples/platform_view/lib/main.dart @@ -19,13 +19,13 @@ class PlatformView extends StatelessWidget { theme: new ThemeData( primarySwatch: Colors.grey, ), - home: new MyHomePage(title: 'Platform View'), + home: const MyHomePage(title: 'Platform View'), ); } } class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); + const MyHomePage({Key key, this.title}) : super(key: key); final String title; diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index 22691f44e11f5..7f2aebf3797c2 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -35,7 +35,7 @@ abstract class MessageCodec { class MethodCall { /// Creates a [MethodCall] representing the invocation of [method] with the /// specified [arguments]. - MethodCall(this.method, [this.arguments]) + const MethodCall(this.method, [this.arguments]) : assert(method != null); /// The name of the method to be called. diff --git a/packages/flutter/lib/src/widgets/animated_list.dart b/packages/flutter/lib/src/widgets/animated_list.dart index 6f0a55864ff16..2b6cb747d99eb 100644 --- a/packages/flutter/lib/src/widgets/animated_list.dart +++ b/packages/flutter/lib/src/widgets/animated_list.dart @@ -46,7 +46,7 @@ class _ActiveItem implements Comparable<_ActiveItem> { /// This widget is similar to one created by [ListView.builder]. class AnimatedList extends StatefulWidget { /// Creates a scrolling container that animates items when they are inserted or removed. - AnimatedList({ + const AnimatedList({ Key key, @required this.itemBuilder, this.initialItemCount: 0, diff --git a/packages/flutter/lib/src/widgets/async.dart b/packages/flutter/lib/src/widgets/async.dart index f64cb8cb87470..5dd926788a813 100644 --- a/packages/flutter/lib/src/widgets/async.dart +++ b/packages/flutter/lib/src/widgets/async.dart @@ -195,7 +195,7 @@ class AsyncSnapshot { const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null); /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data]. - AsyncSnapshot.withData(ConnectionState state, T data) : this._(state, data, null); // not const because https://github.com/dart-lang/sdk/issues/29432 + const AsyncSnapshot.withData(ConnectionState state, T data) : this._(state, data, null); /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [error]. const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, null, error); diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index a0f9fd8fee75c..1a25d1a2efb4b 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -313,10 +313,10 @@ class LabeledGlobalKey> extends GlobalKey { /// Creates a global key with a debugging label. /// /// The label does not affect the key's identity. - const LabeledGlobalKey(this._debugLabel) : super.constructor(); + LabeledGlobalKey(this._debugLabel) : super.constructor(); // Used for forwarding the constructor from GlobalKey. - const LabeledGlobalKey._({ String debugLabel }) : _debugLabel = debugLabel, super.constructor(); + LabeledGlobalKey._({ String debugLabel }) : _debugLabel = debugLabel, super.constructor(); final String _debugLabel; diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 05e2c9f26a1cf..2b6280ec30bd8 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -36,7 +36,7 @@ typedef List NestedScrollViewHeaderSliversBuilder(BuildContext context, const double _kInitialScrollOffset = 0.0; class NestedScrollView extends StatefulWidget { - NestedScrollView({ + const NestedScrollView({ Key key, this.scrollDirection: Axis.vertical, this.reverse: false, diff --git a/packages/flutter/test/services/haptic_feedback_test.dart b/packages/flutter/test/services/haptic_feedback_test.dart index f051fb49e3f44..3f029fdc89bd5 100644 --- a/packages/flutter/test/services/haptic_feedback_test.dart +++ b/packages/flutter/test/services/haptic_feedback_test.dart @@ -15,6 +15,6 @@ void main() { await HapticFeedback.vibrate(); - expect(log, equals([new MethodCall('HapticFeedback.vibrate')])); + expect(log, equals([const MethodCall('HapticFeedback.vibrate')])); }); } diff --git a/packages/flutter/test/services/platform_channel_test.dart b/packages/flutter/test/services/platform_channel_test.dart index 1f17812cbaeaa..bde93d085aeb2 100644 --- a/packages/flutter/test/services/platform_channel_test.dart +++ b/packages/flutter/test/services/platform_channel_test.dart @@ -91,7 +91,7 @@ void main() { }); test('can handle method call with no registered plugin', () async { channel.setMethodCallHandler(null); - final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData envelope; await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { envelope = result; @@ -102,7 +102,7 @@ void main() { channel.setMethodCallHandler((MethodCall call) async { throw new MissingPluginException(); }); - final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData envelope; await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { envelope = result; @@ -111,7 +111,7 @@ void main() { }); test('can handle method call with successful result', () async { channel.setMethodCallHandler((MethodCall call) async => '${call.arguments}, world'); - final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData envelope; await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { envelope = result; @@ -122,7 +122,7 @@ void main() { channel.setMethodCallHandler((MethodCall call) async { throw new PlatformException(code: 'bad', message: 'sayHello failed', details: null); }); - final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData envelope; await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { envelope = result; @@ -141,7 +141,7 @@ void main() { channel.setMethodCallHandler((MethodCall call) async { throw new ArgumentError('bad'); }); - final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData envelope; await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { envelope = result; diff --git a/packages/flutter/test/services/system_navigator_test.dart b/packages/flutter/test/services/system_navigator_test.dart index 828e80b983da4..a693761716406 100644 --- a/packages/flutter/test/services/system_navigator_test.dart +++ b/packages/flutter/test/services/system_navigator_test.dart @@ -15,6 +15,6 @@ void main() { await SystemNavigator.pop(); - expect(log, equals([new MethodCall('SystemNavigator.pop')])); + expect(log, equals([const MethodCall('SystemNavigator.pop')])); }); } diff --git a/packages/flutter/test/services/system_sound_test.dart b/packages/flutter/test/services/system_sound_test.dart index f06683919c9d2..38b4f81a5214c 100644 --- a/packages/flutter/test/services/system_sound_test.dart +++ b/packages/flutter/test/services/system_sound_test.dart @@ -8,13 +8,13 @@ import 'package:test/test.dart'; void main() { test('System sound control test', () async { final List log = []; - + SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); }); - + await SystemSound.play(SystemSoundType.click); - expect(log, equals([new MethodCall('SystemSound.play', "SystemSoundType.click")])); + expect(log, equals([const MethodCall('SystemSound.play', "SystemSoundType.click")])); }); } diff --git a/packages/flutter/test/widgets/async_test.dart b/packages/flutter/test/widgets/async_test.dart index 684c8b2a76db1..ad5bade69599d 100644 --- a/packages/flutter/test/widgets/async_test.dart +++ b/packages/flutter/test/widgets/async_test.dart @@ -14,7 +14,7 @@ void main() { group('AsyncSnapshot', () { test('requiring data succeeds if data is present', () { expect( - new AsyncSnapshot.withData(ConnectionState.done, 'hello').requireData, + const AsyncSnapshot.withData(ConnectionState.done, 'hello').requireData, 'hello', ); }); diff --git a/packages/flutter/test/widgets/layout_builder_mutations_test.dart b/packages/flutter/test/widgets/layout_builder_mutations_test.dart index 79a09b5ef9f79..1eff184584df5 100644 --- a/packages/flutter/test/widgets/layout_builder_mutations_test.dart +++ b/packages/flutter/test/widgets/layout_builder_mutations_test.dart @@ -9,7 +9,7 @@ import 'package:flutter/src/widgets/layout_builder.dart'; import 'package:flutter_test/flutter_test.dart' hide TypeMatcher; class Wrapper extends StatelessWidget { - Wrapper({ + const Wrapper({ Key key, @required this.child }) : assert(child != null), From 95544383ef7fe39935b5cd40b18feed865c87cb4 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Thu, 8 Jun 2017 15:15:18 -0700 Subject: [PATCH 064/110] Bump to Dart SDK 1.24.0-dev.6.7. (#10585) * Bump to Dart SDK 1.24.0-dev.6.7. * nits and fixes --- bin/internal/dart-sdk.version | 2 +- dev/devicelab/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- .../flutter_tools/lib/src/commands/analyze_once.dart | 11 +++++++---- packages/flutter_tools/pubspec.yaml | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bin/internal/dart-sdk.version b/bin/internal/dart-sdk.version index 7856c3dc9fd35..a93ad5a024906 100644 --- a/bin/internal/dart-sdk.version +++ b/bin/internal/dart-sdk.version @@ -1 +1 @@ -1.24.0-dev.3.0 +1.24.0-dev.6.7 diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 12815963c0908..141f8eeee70f2 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -17,4 +17,4 @@ dependencies: dev_dependencies: # See packages/flutter_test/pubspec.yaml for why we're pinning this version. - test: 0.12.20 + test: 0.12.21 diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 46b48c6fa5a38..59d1130b2953b 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -21,6 +21,6 @@ dependencies: sdk: flutter dev_dependencies: - test: 0.12.20 + test: 0.12.21 mockito: ^2.0.2 quiver: ^0.24.0 diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 55453c8932fa2..101eec854b887 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -4,7 +4,7 @@ dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so # here we pin it precisely to avoid version skew across our packages. - test: 0.12.20 + test: 0.12.21 # We use FakeAsync and other testing utilities. quiver: ^0.24.0 diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart index fe514479f1bb6..e8c111870c934 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_once.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart @@ -31,6 +31,9 @@ class AnalyzeOnce extends AnalyzeBase { /// The working directory for testing analysis using dartanalyzer final Directory workingDirectory; + /// Packages whose source is defined in the vended SDK. + static const List _vendedSdkPackages = const ['analyzer', 'front_end', 'kernel']; + @override Future analyze() async { final Stopwatch stopwatch = new Stopwatch()..start(); @@ -158,10 +161,10 @@ class AnalyzeOnce extends AnalyzeBase { if (colon > 0) { final String packageName = line.substring(0, colon); final String packagePath = fs.path.fromUri(line.substring(colon+1)); - // Ensure that we only add the `analyzer` package defined in the vended SDK (and referred to with a local fs.path. directive). - // Analyzer package versions reached via transitive dependencies (e.g., via `test`) are ignored since they would produce - // spurious conflicts. - if (packageName != 'analyzer' || packagePath.startsWith('..')) + // Ensure that we only add `analyzer` and dependent packages defined in the vended SDK (and referred to with a local + // fs.path. directive). Analyzer package versions reached via transitive dependencies (e.g., via `test`) are ignored + // since they would produce spurious conflicts. + if (!_vendedSdkPackages.contains(packageName) || packagePath.startsWith('..')) dependencies.add(packageName, fs.path.normalize(fs.path.absolute(directory.path, packagePath)), dotPackagesPath); } }); diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index e960ea2fd94e4..dfb3e9410306e 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so here we pin it # precisely. - test: 0.12.20 + test: 0.12.21 # Version from the vended Dart SDK as defined in `dependency_overrides`. analyzer: any From 285ab18dde13b19304095fb3b62f5f9acae65677 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 8 Jun 2017 15:25:22 -0700 Subject: [PATCH 065/110] Ensure that SemanticDebugger shows SemanticTree changes from last frame (#10573) * Ensure that SemanticDebugger shows SemanticTree changes from last frame Fixes https://github.com/flutter/flutter/issues/10566 * Comment for clearification --- .../flutter/lib/src/widgets/semantics_debugger.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index ac888f8f62102..f6e1c2e7ccacd 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -65,8 +65,13 @@ class _SemanticsDebuggerState extends State with WidgetsBindi void _update() { SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { - // We want the update to take effect next frame, so to make that - // explicit we call setState() in a post-frame callback. + // Semantic information are only available at the end of a frame and our + // only chance to paint them on the screen is the next frame. To achieve + // this, we call setState() in a post-frame callback. THIS PATTERN SHOULD + // NOT BE COPIED. Calling setState() in a post-frame callback is a bad + // idea as it will not schedule a frame and your app may be lagging behind + // by one frame. We manually call scheduleFrame() to force a frame and + // ensure that the semantic information are always painted on the screen. if (mounted) { // If we got disposed this frame, we will still get an update, // because the inactive list is flushed after the semantics updates @@ -74,6 +79,7 @@ class _SemanticsDebuggerState extends State with WidgetsBindi setState(() { // The generation of the _SemanticsDebuggerListener has changed. }); + SchedulerBinding.instance.scheduleFrame(); } }); } From 0a8713efe04b94cbedfd3e71a1c88552adb9fb54 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 8 Jun 2017 15:57:59 -0700 Subject: [PATCH 066/110] Do not apply scroll offset corrections with zero values during sliver list layout (#10574) Fixes https://github.com/flutter/flutter/issues/10547 --- .../flutter/lib/src/rendering/sliver.dart | 9 ++--- .../lib/src/rendering/sliver_list.dart | 22 +++++++++---- .../flutter/lib/src/rendering/viewport.dart | 2 +- .../test/rendering/slivers_block_test.dart | 33 ++++++++++++++++++- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart index 4414ee53411ff..b8e2c54ca30fc 100644 --- a/packages/flutter/lib/src/rendering/sliver.dart +++ b/packages/flutter/lib/src/rendering/sliver.dart @@ -485,13 +485,13 @@ class SliverGeometry { double hitTestExtent, bool visible, this.hasVisualOverflow: false, - this.scrollOffsetCorrection: 0.0 + this.scrollOffsetCorrection, }) : assert(scrollExtent != null), assert(paintExtent != null), assert(paintOrigin != null), assert(maxPaintExtent != null), assert(hasVisualOverflow != null), - assert(scrollOffsetCorrection != null), + assert(scrollOffsetCorrection != 0.0), layoutExtent = layoutExtent ?? paintExtent, hitTestExtent = hitTestExtent ?? paintExtent, visible = visible ?? paintExtent > 0.0; @@ -613,7 +613,7 @@ class SliverGeometry { verify(hitTestExtent >= 0.0, 'The "hitTestExtent" is negative.'); verify(visible != null, 'The "visible" property is null.'); verify(hasVisualOverflow != null, 'The "hasVisualOverflow" is null.'); - verify(scrollOffsetCorrection != null, 'The "scrollOffsetCorrection" is null.'); + verify(scrollOffsetCorrection != 0.0, 'The "scrollOffsetCorrection" is zero.'); return true; }); return true; @@ -648,7 +648,8 @@ class SliverGeometry { buffer.write('hitTestExtent: ${hitTestExtent.toStringAsFixed(1)}, '); if (hasVisualOverflow) buffer.write('hasVisualOverflow: true, '); - buffer.write('scrollOffsetCorrection: ${scrollOffsetCorrection.toStringAsFixed(1)}'); + if (scrollOffsetCorrection != null) + buffer.write('scrollOffsetCorrection: ${scrollOffsetCorrection.toStringAsFixed(1)}'); buffer.write(')'); return buffer.toString(); } diff --git a/packages/flutter/lib/src/rendering/sliver_list.dart b/packages/flutter/lib/src/rendering/sliver_list.dart index 739538142d086..4abff380ca83d 100644 --- a/packages/flutter/lib/src/rendering/sliver_list.dart +++ b/packages/flutter/lib/src/rendering/sliver_list.dart @@ -99,15 +99,23 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); if (earliestUsefulChild == null) { - // We ran out of children before reaching the scroll offset. - // We must inform our parent that this sliver cannot fulfill - // its contract and that we need a scroll offset correction. - geometry = new SliverGeometry( - scrollOffsetCorrection: -scrollOffset, - ); final SliverMultiBoxAdaptorParentData childParentData = firstChild.parentData; childParentData.layoutOffset = 0.0; - return; + + if (scrollOffset == 0.0) { + earliestUsefulChild = firstChild; + leadingChildWithLayout = earliestUsefulChild; + trailingChildWithLayout ??= earliestUsefulChild; + break; + } else { + // We ran out of children before reaching the scroll offset. + // We must inform our parent that this sliver cannot fulfill + // its contract and that we need a scroll offset correction. + geometry = new SliverGeometry( + scrollOffsetCorrection: -scrollOffset, + ); + return; + } } final double firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild); diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 96eb971ddc52a..89bc9565d40ef 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -260,7 +260,7 @@ abstract class RenderViewportBase= 0); if (index < 0 || index >= children.length) return null; try { @@ -213,4 +212,36 @@ void main() { expect(e.attached, false); }); + test('SliverList - no zero scroll offset correction', () { + RenderSliverList inner; + RenderBox a; + final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager( + children: [ + a = new RenderSizedBox(const Size(100.0, 400.0)), + new RenderSizedBox(const Size(100.0, 400.0)), + new RenderSizedBox(const Size(100.0, 400.0)), + new RenderSizedBox(const Size(100.0, 400.0)), + new RenderSizedBox(const Size(100.0, 400.0)), + ], + ); + final RenderViewport root = new RenderViewport( + axisDirection: AxisDirection.down, + offset: new ViewportOffset.zero(), + children: [ + inner = childManager.createRenderObject(), + ], + ); + layout(root); + + final SliverMultiBoxAdaptorParentData parentData = a.parentData; + parentData.layoutOffset = 0.001; + + root.offset = new ViewportOffset.fixed(900.0); + pumpFrame(); + + root.offset = new ViewportOffset.fixed(0.0); + pumpFrame(); + + expect(inner.geometry.scrollOffsetCorrection, isNull); + }); } From 0f1a703a6b4674388dd54cfdbcb243001c6e5ad5 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Thu, 8 Jun 2017 17:13:03 -0700 Subject: [PATCH 067/110] More documentation (#10589) --- .../lib/src/foundation/change_notifier.dart | 2 +- .../lib/src/painting/text_painter.dart | 5 ++ packages/flutter/lib/src/rendering/debug.dart | 6 ++- .../flutter/lib/src/rendering/object.dart | 2 +- .../flutter/lib/src/widgets/page_storage.dart | 2 +- packages/flutter/lib/src/widgets/routes.dart | 2 +- .../flutter/lib/src/widgets/transitions.dart | 54 +++++++++++++++++-- packages/flutter_driver/lib/src/driver.dart | 4 +- .../flutter_driver/lib/src/extension.dart | 7 +-- .../lib/src/timeline_summary.dart | 8 +-- .../lib/src/android/android_device.dart | 2 +- packages/flutter_tools/lib/src/asset.dart | 2 +- packages/flutter_tools/lib/src/base/os.dart | 4 +- .../lib/src/commands/build_aot.dart | 2 +- .../lib/src/commands/config.dart | 2 +- .../lib/src/commands/create.dart | 4 +- .../flutter_tools/lib/src/dart/analysis.dart | 2 +- .../lib/src/runner/flutter_command.dart | 6 +-- packages/flutter_tools/lib/src/version.dart | 2 +- 19 files changed, 88 insertions(+), 30 deletions(-) diff --git a/packages/flutter/lib/src/foundation/change_notifier.dart b/packages/flutter/lib/src/foundation/change_notifier.dart index 953296c11497f..06dd9ea97d4af 100644 --- a/packages/flutter/lib/src/foundation/change_notifier.dart +++ b/packages/flutter/lib/src/foundation/change_notifier.dart @@ -20,7 +20,7 @@ abstract class Listenable { /// The list must not be changed after this method has been called. Doing so /// will lead to memory leaks or exceptions. /// - /// The list may contain `null`s; they are ignored. + /// The list may contain nulls; they are ignored. factory Listenable.merge(List listenables) = _MergingListenable; /// Register a closure to be called when the object notifies its listeners. diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 8c7e458f6c4eb..8afa2858e716e 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -109,6 +109,11 @@ class TextPainter { /// passed to [layout]. /// /// After this is set, you must call [layout] before the next call to [paint]. + /// + /// The higher layers of the system, such as the [Text] widget, represent + /// overflow effects using the [TextOverflow] enum. The + /// [TextOverflow.ellipsis] value corresponds to setting this property to + /// U+2026 HORIZONTAL ELLIPSIS (…). String get ellipsis => _ellipsis; String _ellipsis; set ellipsis(String value) { diff --git a/packages/flutter/lib/src/rendering/debug.dart b/packages/flutter/lib/src/rendering/debug.dart index fdd137177a1e8..e605d73e52bd9 100644 --- a/packages/flutter/lib/src/rendering/debug.dart +++ b/packages/flutter/lib/src/rendering/debug.dart @@ -108,7 +108,11 @@ bool debugPrintMarkNeedsLayoutStacks = false; /// Check the intrinsic sizes of each [RenderBox] during layout. bool debugCheckIntrinsicSizes = false; -/// Adds [dart:developer.Timeline] events for every RenderObject painted. +/// Adds [dart:developer.Timeline] events for every [RenderObject] painted. +/// +/// This is only enabled in debug builds. The timing information this exposes is +/// not representative of actual paints. However, it can expose unexpected +/// painting in the timeline. /// /// For details on how to use [dart:developer.Timeline] events in the Dart /// Observatory to optimize your app, see: diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index b5fd189f3dde5..81856d505a569 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2401,7 +2401,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// the approximate bounding box of the clip rect that would be /// applied to the given child during the paint phase, if any. /// - /// Returns `null` if the child would not be clipped. + /// Returns null if the child would not be clipped. /// /// This is used in the semantics phase to avoid including children /// that are not physically visible. diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 7d71ac87dbf74..8b4d42ffe14a4 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -119,7 +119,7 @@ class PageStorage extends StatelessWidget { /// The bucket from the closest instance of this class that encloses the given context. /// - /// Returns `null` if none exists. + /// Returns null if none exists. /// /// Typical usage is as follows: /// diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 571d86f0b8e33..ddd68b14aa6f5 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -524,7 +524,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute new _SpinnerState(); +/// } +/// +/// class _SpinnerState extends State with SingleTickerProviderStateMixin { +/// AnimationController _controller; +/// +/// @override +/// void initState() { +/// super.initState(); +/// _controller = new AnimationController( +/// duration: const Duration(seconds: 10), +/// vsync: this, +/// )..repeat(); +/// } +/// +/// @override +/// void dispose() { +/// _controller.dispose(); +/// super.dispose(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return new AnimatedBuilder( +/// animation: _controller, +/// child: new Container(width: 200.0, height: 200.0, color: Colors.green), +/// builder: (BuildContext context, Widget child) { +/// return new Transform.rotate( +/// angle: _controller.value * 2.0 * math.PI, +/// child: child, +/// ); +/// }, +/// ); +/// } +/// } +/// ``` class AnimatedBuilder extends AnimatedWidget { /// Creates an animated builder. /// diff --git a/packages/flutter_driver/lib/src/driver.dart b/packages/flutter_driver/lib/src/driver.dart index 8652cd553acad..2b8addaeea7c3 100644 --- a/packages/flutter_driver/lib/src/driver.dart +++ b/packages/flutter_driver/lib/src/driver.dart @@ -158,8 +158,8 @@ class FlutterDriver { VMIsolate isolate = await vm.isolates.first.loadRunnable(); // TODO(yjbanov): vm_service_client does not support "None" pause event yet. - // It is currently reported as `null`, but we cannot rely on it because - // eventually the event will be reported as a non-`null` object. For now, + // It is currently reported as null, but we cannot rely on it because + // eventually the event will be reported as a non-null object. For now, // list all the events we know about. Later we'll check for "None" event // explicitly. // diff --git a/packages/flutter_driver/lib/src/extension.dart b/packages/flutter_driver/lib/src/extension.dart index 1af359d89e615..851c2cb3b4397 100644 --- a/packages/flutter_driver/lib/src/extension.dart +++ b/packages/flutter_driver/lib/src/extension.dart @@ -49,13 +49,14 @@ void enableFlutterDriverExtension() { assert(WidgetsBinding.instance is _DriverBinding); } -/// Handles a command and returns a result. +/// Signature for functions that handle a command and return a result. typedef Future CommandHandlerCallback(Command c); -/// Deserializes JSON map to a command object. +/// Signature for functions that deserialize a JSON map to a command object. typedef Command CommandDeserializerCallback(Map params); -/// Runs the finder and returns the [Element] found, or `null`. +/// Signature for functions that run the given finder and return the [Element] +/// found, if any, or null otherwise. typedef Finder FinderConstructor(SerializableFinder finder); @visibleForTesting diff --git a/packages/flutter_driver/lib/src/timeline_summary.dart b/packages/flutter_driver/lib/src/timeline_summary.dart index df5c098c11672..f0d90f3f2b65d 100644 --- a/packages/flutter_driver/lib/src/timeline_summary.dart +++ b/packages/flutter_driver/lib/src/timeline_summary.dart @@ -28,14 +28,14 @@ class TimelineSummary { /// Average amount of time spent per frame in the framework building widgets, /// updating layout, painting and compositing. /// - /// Returns `null` if no frames were recorded. + /// Returns null if no frames were recorded. double computeAverageFrameBuildTimeMillis() { return _averageInMillis(_extractFrameThreadDurations()); } /// The longest frame build time in milliseconds. /// - /// Returns `null` if no frames were recorded. + /// Returns null if no frames were recorded. double computeWorstFrameBuildTimeMillis() { return _maxInMillis(_extractFrameThreadDurations()); } @@ -48,14 +48,14 @@ class TimelineSummary { /// Average amount of time spent per frame in the GPU rasterizer. /// - /// Returns `null` if no frames were recorded. + /// Returns null if no frames were recorded. double computeAverageFrameRasterizerTimeMillis() { return _averageInMillis(_extractDuration(_extractGpuRasterizerDrawEvents())); } /// The longest frame rasterization time in milliseconds. /// - /// Returns `null` if no frames were recorded. + /// Returns null if no frames were recorded. double computeWorstFrameRasterizerTimeMillis() { return _maxInMillis(_extractDuration(_extractGpuRasterizerDrawEvents())); } diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 59019ab752a88..b4dd532fbd59f 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -476,7 +476,7 @@ class AndroidDevice extends Device { static final RegExp _timeRegExp = new RegExp(r'^\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}', multiLine: true); - /// Return the most recent timestamp in the Android log or `null` if there is + /// Return the most recent timestamp in the Android log or null if there is /// no available timestamp. The format can be passed to logcat's -T option. String get lastLogcatTimestamp { final String output = runCheckedSync(adbCommandForDevice([ diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index df951e7eab92c..6f1e39ae87154 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -316,7 +316,7 @@ DevFSContent _createFontManifest(Map manifestDescriptor, /// Given an assetBase location and a pubspec.yaml Flutter manifest, return a /// map of assets to asset variants. /// -/// Returns `null` on missing assets. +/// Returns null on missing assets. Map<_Asset, List<_Asset>> _parseAssets( PackageMap packageMap, Map manifestDescriptor, diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart index b4027ad8b3893..1cb56968f03f8 100644 --- a/packages/flutter_tools/lib/src/base/os.dart +++ b/packages/flutter_tools/lib/src/base/os.dart @@ -27,7 +27,7 @@ abstract class OperatingSystemUtils { /// Make the given file executable. This may be a no-op on some platforms. ProcessResult makeExecutable(File file); - /// Return the path (with symlinks resolved) to the given executable, or `null` + /// Return the path (with symlinks resolved) to the given executable, or null /// if `which` was not able to locate the binary. File which(String execName) { final List result = _which(execName); @@ -206,7 +206,7 @@ class _WindowsUtils extends OperatingSystemUtils { /// Find and return the project root directory relative to the specified /// directory or the current working directory if none specified. -/// Return `null` if the project root could not be found +/// Return null if the project root could not be found /// or if the project root is the flutter repository root. String findProjectRoot([String directory]) { const String kProjectRootSentinel = 'pubspec.yaml'; diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index 10f79bb9cc4bc..22e0b2ca23985 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -82,7 +82,7 @@ String _getPackagePath(PackageMap packageMap, String package) { return fs.path.dirname(packageMap.map[package].toFilePath()); } -/// Build an AOT snapshot. Return `null` (and log to `printError`) if the method +/// Build an AOT snapshot. Return null (and log to `printError`) if the method /// fails. Future buildAotSnapshot( String mainPath, diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index 11f7e078aaa68..16c06291930df 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -42,7 +42,7 @@ class ConfigCommand extends FlutterCommand { 'Analytics reporting is currently ${flutterUsage.enabled ? 'enabled' : 'disabled'}.'; } - /// Return `null` to disable tracking of the `config` command. + /// Return null to disable tracking of the `config` command. @override Future get usagePath => null; diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index cf8c3ec1bf2dc..cb60d498f3cf9 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -311,7 +311,7 @@ final Set _packageDependencies = new Set.from([ 'yaml' ]); -/// Return `null` if the project name is legal. Return a validation message if +/// Return null if the project name is legal. Return a validation message if /// we should disallow the project name. String _validateProjectName(String projectName) { if (!package_names.isValidPackageName(projectName)) @@ -324,7 +324,7 @@ String _validateProjectName(String projectName) { return null; } -/// Return `null` if the project directory is legal. Return a validation message +/// Return null if the project directory is legal. Return a validation message /// if we should disallow the directory name. String _validateProjectDir(String dirPath, { String flutterRoot }) { if (fs.path.isWithin(flutterRoot, dirPath)) { diff --git a/packages/flutter_tools/lib/src/dart/analysis.dart b/packages/flutter_tools/lib/src/dart/analysis.dart index cd20c4546c302..a41dfe4d5e6b7 100644 --- a/packages/flutter_tools/lib/src/dart/analysis.dart +++ b/packages/flutter_tools/lib/src/dart/analysis.dart @@ -136,7 +136,7 @@ class AnalysisDriver { bool _isFiltered(AnalysisError error) { final ErrorProcessor processor = ErrorProcessor.getProcessor(context.analysisOptions, error); - // Filtered errors are processed to a severity of `null`. + // Filtered errors are processed to a severity of null. return processor != null && processor.severity == null; } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index eb6984beba1d4..28b227ed17cb8 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -134,7 +134,7 @@ abstract class FlutterCommand extends Command { applicationPackages ??= new ApplicationPackageStore(); } - /// The path to send to Google Analytics. Return `null` here to disable + /// The path to send to Google Analytics. Return null here to disable /// tracking of the command. Future get usagePath async => name; @@ -218,7 +218,7 @@ abstract class FlutterCommand extends Command { /// Find and return all target [Device]s based upon currently connected /// devices and criteria entered by the user on the command line. /// If no device can be found that meets specified criteria, - /// then print an error message and return `null`. + /// then print an error message and return null. Future> findAllTargetDevices() async { if (!doctor.canLaunchAnything) { printError("Unable to locate a development device; please run 'flutter doctor' " @@ -264,7 +264,7 @@ abstract class FlutterCommand extends Command { /// Find and return the target [Device] based upon currently connected /// devices and criteria entered by the user on the command line. /// If a device cannot be found that meets specified criteria, - /// then print an error message and return `null`. + /// then print an error message and return null. Future findTargetDevice() async { List deviceList = await findAllTargetDevices(); if (deviceList == null) diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index 4246b6176e99d..de68f74d76606 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -236,7 +236,7 @@ class FlutterVersion { /// This method sends a server request if it's been more than /// [kCheckAgeConsideredUpToDate] since the last version check. /// - /// Returns `null` if the cached version is out-of-date or missing, and we are + /// Returns null if the cached version is out-of-date or missing, and we are /// unable to reach the server to get the latest version. Future _getLatestAvailableFlutterVersion() async { Cache.checkLockAcquired(); From 435c25bfc40bcf7b7aca1ac5785397e7a8abe54b Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 8 Jun 2017 17:17:48 -0700 Subject: [PATCH 068/110] Add note to .analysis_options about .analysis_options in flutter/plugins (#10588) * Add note to .analysis_options about .analysis_options in flutter/plugins * review comments --- .analysis_options | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.analysis_options b/.analysis_options index 2a1fece32f7e2..38cff14072e3e 100644 --- a/.analysis_options +++ b/.analysis_options @@ -7,15 +7,19 @@ # See the configuration guide for more # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer # -# There are three similar analysis options files in the flutter repo: +# There are four similar analysis options files in the flutter repos: # - .analysis_options (this file) # - .analysis_options_repo # - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/.analysis_options # # This file contains the analysis options used by Flutter editors, such as Atom. # It is very similar to the .analysis_options_repo file in this same directory; # the only difference (currently) is the public_member_api_docs option, # which triggers too many messages to be used in editors. +# +# The flutter/plugins repo contains a copy of this file, which should be kept +# in sync with this file. analyzer: language: From 0e4dda77365c6a35187708db9faa709015b2d1d5 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Thu, 8 Jun 2017 17:20:36 -0700 Subject: [PATCH 069/110] Improved support for saving and restoring the scroll offset, etc V2 (#10590) --- dev/benchmarks/complex_layout/lib/main.dart | 7 +- .../catalog/lib/expansion_tile_sample.dart | 2 +- .../flutter/lib/src/widgets/framework.dart | 6 +- .../flutter/lib/src/widgets/page_storage.dart | 103 +++++++++++------- .../flutter/lib/src/widgets/page_view.dart | 25 ++++- .../lib/src/widgets/scroll_controller.dart | 27 ++++- .../lib/src/widgets/scroll_position.dart | 21 +++- .../scroll_position_with_single_context.dart | 13 ++- .../flutter/test/widgets/page_view_test.dart | 6 +- .../test/widgets/scroll_controller_test.dart | 47 ++++++++ 10 files changed, 200 insertions(+), 57 deletions(-) diff --git a/dev/benchmarks/complex_layout/lib/main.dart b/dev/benchmarks/complex_layout/lib/main.dart index 1d78c37ffdd33..27bd8c1a2402c 100644 --- a/dev/benchmarks/complex_layout/lib/main.dart +++ b/dev/benchmarks/complex_layout/lib/main.dart @@ -111,9 +111,9 @@ class ComplexLayoutState extends State { key: const Key('complex-scroll'), // this key is used by the driver test itemBuilder: (BuildContext context, int index) { if (index % 2 == 0) - return new FancyImageItem(index, key: new ValueKey(index)); + return new FancyImageItem(index, key: new PageStorageKey(index)); else - return new FancyGalleryItem(index, key: new ValueKey(index)); + return new FancyGalleryItem(index, key: new PageStorageKey(index)); }, ) ), @@ -496,7 +496,7 @@ class ItemGalleryBox extends StatelessWidget { child: new TabBarView( children: tabNames.map((String tabName) { return new Container( - key: new Key(tabName), + key: new PageStorageKey(tabName), child: new Padding( padding: const EdgeInsets.all(8.0), child: new Card( @@ -611,6 +611,7 @@ class GalleryDrawer extends StatelessWidget { final ScrollMode currentMode = ComplexLayoutApp.of(context).scrollMode; return new Drawer( child: new ListView( + key: const PageStorageKey('gallery-drawer'), children: [ new FancyDrawerHeader(), new ListTile( diff --git a/examples/catalog/lib/expansion_tile_sample.dart b/examples/catalog/lib/expansion_tile_sample.dart index a61ff8676d887..01b75c7917fe5 100644 --- a/examples/catalog/lib/expansion_tile_sample.dart +++ b/examples/catalog/lib/expansion_tile_sample.dart @@ -76,7 +76,7 @@ class EntryItem extends StatelessWidget { if (root.children.isEmpty) return new ListTile(title: new Text(root.title)); return new ExpansionTile( - key: new ValueKey(root), + key: new PageStorageKey(root), title: new Text(root.title), children: root.children.map(_buildTiles).toList(), ); diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 1a25d1a2efb4b..4639251c9f4ea 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -322,10 +322,10 @@ class LabeledGlobalKey> extends GlobalKey { @override String toString() { - final String tag = _debugLabel != null ? ' $_debugLabel' : '#$hashCode'; + final String label = _debugLabel != null ? ' $_debugLabel' : ''; if (runtimeType == LabeledGlobalKey) - return '[GlobalKey$tag]'; - return '[$runtimeType$tag]'; + return '[GlobalKey#$hashCode$label]'; + return '[$runtimeType#$hashCode$label]'; } } diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 8b4d42ffe14a4..90cb7afbcecf7 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -6,43 +6,67 @@ import 'package:flutter/foundation.dart'; import 'framework.dart'; +/// A [ValueKey] that defines where [PageStorage] values will be saved. +/// +/// [Scrollable]s ([ScrollPosition]s really) use [PageStorage] to save their +/// scroll offset. Each time a scroll completes, the scrollable's page +/// storage is updated. +/// +/// [PageStorage] is used to save and restore values that can outlive the widget. +/// The values are stored in a per-route [Map] whose keys are defined by the +/// [PageStorageKey]s for the widget and its ancestors. To make it possible +/// for a saved value to be found when a widget is recreated, the key's values +/// must not be objects whose identity will change each time the widget is created. +/// +/// For example, to ensure that the scroll offsets for the scrollable within +/// each `MyScrollableTabView` below are restored when the [TabBarView] +/// is recreated, we've specified [PageStorageKey]s whose values are the the +/// tabs' string labels. +/// +/// ```dart +/// new TabBarView( +/// children: myTabs.map((Tab tab) { +/// new MyScrollableTabView( +/// key: new PageStorageKey(tab.text), // like 'Tab 1' +/// tab: tab, +/// ), +/// }), +///) +/// ``` +class PageStorageKey extends ValueKey { + /// Creates a [ValueKey] that defines where [PageStorage] values will be saved. + const PageStorageKey(T value) : super(value); +} + class _StorageEntryIdentifier { - Type clientType; - List keys; - - void addKey(Key key) { - assert(key != null); - assert(key is! GlobalKey); - keys ??= []; - keys.add(key); + _StorageEntryIdentifier(this.clientType, this.keys) { + assert(clientType != null); + assert(keys != null); } - GlobalKey scopeKey; + final Type clientType; + final List> keys; @override bool operator ==(dynamic other) { - if (other is! _StorageEntryIdentifier) + if (other.runtimeType != runtimeType) return false; final _StorageEntryIdentifier typedOther = other; - if (clientType != typedOther.clientType || - scopeKey != typedOther.scopeKey || - keys?.length != typedOther.keys?.length) + if (clientType != typedOther.clientType || keys.length != typedOther.keys.length) return false; - if (keys != null) { - for (int index = 0; index < keys.length; index += 1) { - if (keys[index] != typedOther.keys[index]) - return false; - } + for (int index = 0; index < keys.length; index += 1) { + if (keys[index] != typedOther.keys[index]) + return false; } return true; } @override - int get hashCode => hashValues(clientType, scopeKey, hashList(keys)); + int get hashCode => hashValues(clientType, hashList(keys)); @override String toString() { - return 'StorageEntryIdentifier($clientType, $scopeKey, ${keys?.join(":")})'; + return 'StorageEntryIdentifier($clientType, ${keys?.join(":")})'; } } @@ -51,27 +75,26 @@ class _StorageEntryIdentifier { /// Useful for storing per-page state that persists across navigations from one /// page to another. class PageStorageBucket { - _StorageEntryIdentifier _computeStorageIdentifier(BuildContext context) { - final _StorageEntryIdentifier result = new _StorageEntryIdentifier(); - result.clientType = context.widget.runtimeType; - Key lastKey = context.widget.key; - if (lastKey is! GlobalKey) { - if (lastKey != null) - result.addKey(lastKey); + static bool _maybeAddKey(BuildContext context, List> keys) { + final Widget widget = context.widget; + final Key key = widget.key; + if (key is PageStorageKey) + keys.add(key); + return widget is! PageStorage; + } + + List> _allKeys(BuildContext context) { + final List> keys = >[]; + if (_maybeAddKey(context, keys)) { context.visitAncestorElements((Element element) { - if (element.widget.key is GlobalKey) { - lastKey = element.widget.key; - return false; - } else if (element.widget.key != null) { - result.addKey(element.widget.key); - } - return true; + return _maybeAddKey(element, keys); }); - return result; } - assert(lastKey is GlobalKey); - result.scopeKey = lastKey; - return result; + return keys; + } + + _StorageEntryIdentifier _computeIdentifier(BuildContext context) { + return new _StorageEntryIdentifier(context.widget.runtimeType, _allKeys(context)); } Map _storage; @@ -89,13 +112,13 @@ class PageStorageBucket { /// identifier will change. void writeState(BuildContext context, dynamic data, { Object identifier }) { _storage ??= {}; - _storage[identifier ?? _computeStorageIdentifier(context)] = data; + _storage[identifier ?? _computeIdentifier(context)] = data; } /// Read given data from into this page storage bucket using an identifier /// computed from the given context. More about [identifier] in [writeState]. dynamic readState(BuildContext context, { Object identifier }) { - return _storage != null ? _storage[identifier ?? _computeStorageIdentifier(context)] : null; + return _storage != null ? _storage[identifier ?? _computeIdentifier(context)] : null; } } diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 355deff5e4f10..54dba500b886e 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -38,17 +38,36 @@ import 'viewport.dart'; class PageController extends ScrollController { /// Creates a page controller. /// - /// The [initialPage] and [viewportFraction] arguments must not be null. + /// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null. PageController({ this.initialPage: 0, + this.keepPage: true, this.viewportFraction: 1.0, }) : assert(initialPage != null), + assert(keepPage != null), assert(viewportFraction != null), assert(viewportFraction > 0.0); /// The page to show when first creating the [PageView]. final int initialPage; + /// Save the current [page] with [PageStorage] and restore it if + /// this controller's scrollable is recreated. + /// + /// If this property is set to false, the current [page] is never saved + /// and [initialPage] is always used to initialize the scroll offset. + /// If true (the default), the initial page is used the first time the + /// controller's scrollable is created, since there's isn't a page to + /// restore yet. Subsequently the saved page is restored and + /// [initialPage] is ignored. + /// + /// See also: + /// + /// * [PageStorageKey], which should be used when more than one + //// scrollable appears in the same route, to distinguish the [PageStorage] + /// locations used to save scroll offsets. + final bool keepPage; + /// The fraction of the viewport that each page should occupy. /// /// Defaults to 1.0, which means each page fills the viewport in the scrolling @@ -116,6 +135,7 @@ class PageController extends ScrollController { physics: physics, context: context, initialPage: initialPage, + keepPage: keepPage, viewportFraction: viewportFraction, oldPosition: oldPosition, ); @@ -150,9 +170,11 @@ class _PagePosition extends ScrollPositionWithSingleContext { ScrollPhysics physics, ScrollContext context, this.initialPage: 0, + bool keepPage: true, double viewportFraction: 1.0, ScrollPosition oldPosition, }) : assert(initialPage != null), + assert(keepPage != null), assert(viewportFraction != null), assert(viewportFraction > 0.0), _viewportFraction = viewportFraction, @@ -161,6 +183,7 @@ class _PagePosition extends ScrollPositionWithSingleContext { physics: physics, context: context, initialPixels: null, + keepScrollOffset: keepPage, oldPosition: oldPosition, ); diff --git a/packages/flutter/lib/src/widgets/scroll_controller.dart b/packages/flutter/lib/src/widgets/scroll_controller.dart index d050414652a22..4f8b268f0b092 100644 --- a/packages/flutter/lib/src/widgets/scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/scroll_controller.dart @@ -39,20 +39,40 @@ import 'scroll_position_with_single_context.dart'; class ScrollController extends ChangeNotifier { /// Creates a controller for a scrollable widget. /// - /// The [initialScrollOffset] must not be null. + /// The values of `initialScrollOffset` and `keepScrollOffset` must not be null. ScrollController({ this.initialScrollOffset: 0.0, + this.keepScrollOffset: true, this.debugLabel, - }) : assert(initialScrollOffset != null); + }) : assert(initialScrollOffset != null), + assert(keepScrollOffset != null); /// The initial value to use for [offset]. /// /// New [ScrollPosition] objects that are created and attached to this - /// controller will have their offset initialized to this value. + /// controller will have their offset initialized to this value + /// if [keepScrollOffset] is false or a scroll offset hasn't been saved yet. /// /// Defaults to 0.0. final double initialScrollOffset; + /// Each time a scroll completes, save the current scroll [offset] with + /// [PageStorage] and restore it if this controller's scrollable is recreated. + /// + /// If this property is set to false, the scroll offset is never saved + /// and [initialScrollOffset] is always used to initialize the scroll + /// offset. If true (the default), the initial scroll offset is used the + /// first time the controller's scrollable is created, since there's no + /// scroll offset to restore yet. Subsequently the saved offset is + /// restored and [initialScrollOffset] is ignored. + /// + /// See also: + /// + /// * [PageStorageKey], which should be used when more than one + //// scrollable appears in the same route, to distinguish the [PageStorage] + /// locations used to save scroll offsets. + final bool keepScrollOffset; + /// A label that is used in the [toString] output. Intended to aid with /// identifying scroll controller instances in debug output. final String debugLabel; @@ -204,6 +224,7 @@ class ScrollController extends ChangeNotifier { physics: physics, context: context, initialPixels: initialScrollOffset, + keepScrollOffset: keepScrollOffset, oldPosition: oldPosition, debugLabel: debugLabel, ); diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 3785621a75c65..c96ee6ea2fb93 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -61,17 +61,22 @@ export 'scroll_activity.dart' show ScrollHoldController; abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Creates an object that determines which portion of the content is visible /// in a scroll view. + /// + /// The [physics], [context], and [keepScrollOffset] parameters must not be null. ScrollPosition({ @required this.physics, @required this.context, + this.keepScrollOffset: true, ScrollPosition oldPosition, this.debugLabel, }) : assert(physics != null), assert(context != null), - assert(context.vsync != null) { + assert(context.vsync != null), + assert(keepScrollOffset != null) { if (oldPosition != null) absorb(oldPosition); - restoreScrollOffset(); + if (keepScrollOffset) + restoreScrollOffset(); } /// How the scroll position should respond to user input. @@ -85,6 +90,15 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Typically implemented by [ScrollableState]. final ScrollContext context; + /// Save the current scroll [offset] with [PageStorage] and restore it if + /// this scroll position's scrollable is recreated. + /// + /// See also: + /// + /// * [ScrollController.keepScrollOffset] and [PageController.keepPage], which + /// create scroll positions and initialize this property. + final bool keepScrollOffset; + /// A label that is used in the [toString] output. Intended to aid with /// identifying animation controller instances in debug output. final String debugLabel; @@ -539,7 +553,8 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// This also saves the scroll offset using [saveScrollOffset]. void didEndScroll() { activity.dispatchScrollEndNotification(cloneMetrics(), context.notificationContext); - saveScrollOffset(); + if (keepScrollOffset) + saveScrollOffset(); } /// Called by [setPixels] to report overscroll when an attempt is made to diff --git a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart index 287f739fdda5c..4d7b81ba6521d 100644 --- a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart +++ b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart @@ -46,13 +46,24 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc /// imperative that the value be set, using [correctPixels], as soon as /// [applyNewDimensions] is invoked, before calling the inherited /// implementation of that method. + /// + /// If [keepScrollOffset] is true (the default), the current scroll offset is + /// saved with [PageStorage] and restored it if this scroll position's scrollable + /// is recreated. ScrollPositionWithSingleContext({ @required ScrollPhysics physics, @required ScrollContext context, double initialPixels: 0.0, + bool keepScrollOffset: true, ScrollPosition oldPosition, String debugLabel, - }) : super(physics: physics, context: context, oldPosition: oldPosition, debugLabel: debugLabel) { + }) : super( + physics: physics, + context: context, + keepScrollOffset: keepScrollOffset, + oldPosition: oldPosition, + debugLabel: debugLabel, + ) { // If oldPosition is not null, the superclass will first call absorb(), // which may set _pixels and _activity. if (pixels == null && initialPixels != null) diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index 29a4d17b7f81b..0089e2dd2c827 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -441,12 +441,14 @@ void main() { ), ); expect(controller.page, 2); + + final PageController controller2 = new PageController(keepPage: false); await tester.pumpWidget( new PageStorage( bucket: bucket, child: new PageView( key: const Key('Check it again against your list and see consistency!'), - controller: controller, + controller: controller2, children: [ const Placeholder(), const Placeholder(), @@ -455,6 +457,6 @@ void main() { ), ), ); - expect(controller.page, 0); + expect(controller2.page, 0); }); } diff --git a/packages/flutter/test/widgets/scroll_controller_test.dart b/packages/flutter/test/widgets/scroll_controller_test.dart index f935edcd57904..5ae0e4d5eb0f4 100644 --- a/packages/flutter/test/widgets/scroll_controller_test.dart +++ b/packages/flutter/test/widgets/scroll_controller_test.dart @@ -259,4 +259,51 @@ void main() { await tester.drag(find.byType(ListView), const Offset(0.0, -130.0)); expect(log, isEmpty); }); + + testWidgets('keepScrollOffset', (WidgetTester tester) async { + final PageStorageBucket bucket = new PageStorageBucket(); + + Widget buildFrame(ScrollController controller) { + return new PageStorage( + bucket: bucket, + child: new ListView( + key: new UniqueKey(), // it's a different ListView every time + controller: controller, + children: new List.generate(50, (int index) { + return new Container(height: 100.0, child: new Text('Item $index')); + }).toList(), + ), + ); + } + + // keepScrollOffset: true (the default). The scroll offset is restored + // when the ListView is recreated with a new ScrollController. + + // The initialScrollOffset is used in this case, because there's no saved + // scroll offset. + ScrollController controller = new ScrollController(initialScrollOffset: 200.0); + await tester.pumpWidget(buildFrame(controller)); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 2')), Offset.zero); + + controller.jumpTo(2000.0); + await tester.pump(); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero); + + // The initialScrollOffset isn't used in this case, because the scrolloffset + // can be restored. + controller = new ScrollController(initialScrollOffset: 25.0); + await tester.pumpWidget(buildFrame(controller)); + expect(controller.offset, 2000.0); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero); + + // keepScrollOffset: false. The scroll offset is -not- restored + // when the ListView is recreated with a new ScrollController and + // the initialScrollOffset is used. + + controller = new ScrollController(keepScrollOffset: false, initialScrollOffset: 100.0); + await tester.pumpWidget(buildFrame(controller)); + expect(controller.offset, 100.0); + expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 1')), Offset.zero); + + }); } From 21441aa36652b8f3567cf3095f79f09eb7e05537 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 8 Jun 2017 18:13:49 -0700 Subject: [PATCH 070/110] Roll engine to 4d423b62b99b9d943a26fad776769cfe2c7f016d (#10591) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 9164762ecfb76..74aed139c89b4 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -1f765cdba7aee9d0a953043209b05d2c59270391 +4d423b62b99b9d943a26fad776769cfe2c7f016d From 4bde698ffcd060f5be9e561263f5053127dc14d6 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 9 Jun 2017 10:54:47 -0700 Subject: [PATCH 071/110] Roll engine to ffe8181ffe7432b61a67323c80fd8025704e4695 (#10597) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 74aed139c89b4..056abe2f242e8 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -4d423b62b99b9d943a26fad776769cfe2c7f016d +ffe8181ffe7432b61a67323c80fd8025704e4695 From 09eba82a9efaf705178f13ed34b8f2c93b456f44 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Fri, 9 Jun 2017 15:06:03 -0700 Subject: [PATCH 072/110] Add indicatorWeight, indicatorPadding to TabBar (#10600) --- .../flutter/lib/src/material/constants.dart | 3 + packages/flutter/lib/src/material/tabs.dart | 117 ++++++++++++------ packages/flutter/test/material/tabs_test.dart | 65 ++++++++++ 3 files changed, 144 insertions(+), 41 deletions(-) diff --git a/packages/flutter/lib/src/material/constants.dart b/packages/flutter/lib/src/material/constants.dart index 2558975639124..22288167fbc42 100644 --- a/packages/flutter/lib/src/material/constants.dart +++ b/packages/flutter/lib/src/material/constants.dart @@ -28,5 +28,8 @@ const int kRadialReactionAlpha = 0x33; /// The duration of the horizontal scroll animation that occurs when a tab is tapped. const Duration kTabScrollDuration = const Duration(milliseconds: 300); +/// The horizontal padding included by [Tab]s. +const EdgeInsets kTabLabelPadding = const EdgeInsets.symmetric(horizontal: 12.0); + /// The padding added around material list items. const EdgeInsets kMaterialListPadding = const EdgeInsets.symmetric(vertical: 8.0); diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 3b850f41a3cd1..a67bf0509c2a2 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math' as math; import 'dart:ui' show lerpDouble; import 'package:flutter/foundation.dart'; @@ -20,10 +21,8 @@ import 'theme.dart'; const double _kTabHeight = 46.0; const double _kTextAndIconTabHeight = 72.0; -const double _kTabIndicatorHeight = 2.0; const double _kMinTabWidth = 72.0; const double _kMaxTabWidth = 264.0; -const EdgeInsets _kTabLabelPadding = const EdgeInsets.symmetric(horizontal: 12.0); /// A material design [TabBar] tab. If both [icon] and [text] are /// provided, the text is displayed below the icon. @@ -82,7 +81,7 @@ class Tab extends StatelessWidget { } return new Container( - padding: _kTabLabelPadding, + padding: kTabLabelPadding, height: height, constraints: const BoxConstraints(minWidth: _kMinTabWidth), child: new Center(child: label), @@ -238,30 +237,39 @@ double _indexChangeProgress(TabController controller) { } class _IndicatorPainter extends CustomPainter { - _IndicatorPainter(this.controller) : super(repaint: controller.animation); + _IndicatorPainter({ + this.controller, + this.indicatorWeight, + this.indicatorPadding, + List initialTabOffsets, + }) : _tabOffsets = initialTabOffsets, super(repaint: controller.animation); - TabController controller; - List tabOffsets; - Color color; - Rect currentRect; + final TabController controller; + final double indicatorWeight; + final EdgeInsets indicatorPadding; + List _tabOffsets; + Color _color; + Rect _currentRect; - // tabOffsets[index] is the offset of the left edge of the tab at index, and - // tabOffsets[tabOffsets.length] is the right edge of the last tab. - int get maxTabIndex => tabOffsets.length - 2; + // _tabOffsets[index] is the offset of the left edge of the tab at index, and + // _tabOffsets[_tabOffsets.length] is the right edge of the last tab. + int get maxTabIndex => _tabOffsets.length - 2; Rect indicatorRect(Size tabBarSize, int tabIndex) { - assert(tabOffsets != null && tabIndex >= 0 && tabIndex <= maxTabIndex); - final double tabLeft = tabOffsets[tabIndex]; - final double tabRight = tabOffsets[tabIndex + 1]; - final double tabTop = tabBarSize.height - _kTabIndicatorHeight; - return new Rect.fromLTWH(tabLeft, tabTop, tabRight - tabLeft, _kTabIndicatorHeight); + assert(_tabOffsets != null && tabIndex >= 0 && tabIndex <= maxTabIndex); + double tabLeft = _tabOffsets[tabIndex]; + double tabRight = _tabOffsets[tabIndex + 1]; + tabLeft = math.min(tabLeft + indicatorPadding.left, tabRight); + tabRight = math.max(tabRight - indicatorPadding.right, tabLeft); + final double tabTop = tabBarSize.height - indicatorWeight; + return new Rect.fromLTWH(tabLeft, tabTop, tabRight - tabLeft, indicatorWeight); } @override void paint(Canvas canvas, Size size) { if (controller.indexIsChanging) { final Rect targetRect = indicatorRect(size, controller.index); - currentRect = Rect.lerp(targetRect, currentRect ?? targetRect, _indexChangeProgress(controller)); + _currentRect = Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller)); } else { final int currentIndex = controller.index; final Rect left = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null; @@ -271,21 +279,21 @@ class _IndicatorPainter extends CustomPainter { final double index = controller.index.toDouble(); final double value = controller.animation.value; if (value == index - 1.0) - currentRect = left ?? middle; + _currentRect = left ?? middle; else if (value == index + 1.0) - currentRect = right ?? middle; + _currentRect = right ?? middle; else if (value == index) - currentRect = middle; + _currentRect = middle; else if (value < index) - currentRect = left == null ? middle : Rect.lerp(middle, left, index - value); + _currentRect = left == null ? middle : Rect.lerp(middle, left, index - value); else - currentRect = right == null ? middle : Rect.lerp(middle, right, value - index); + _currentRect = right == null ? middle : Rect.lerp(middle, right, value - index); } - assert(currentRect != null); - canvas.drawRect(currentRect, new Paint()..color = color); + assert(_currentRect != null); + canvas.drawRect(_currentRect, new Paint()..color = _color); } - static bool tabOffsetsNotEqual(List a, List b) { + static bool _tabOffsetsNotEqual(List a, List b) { assert(a != null && b != null && a.length == b.length); for(int i = 0; i < a.length; i++) { if (a[i] != b[i]) @@ -297,9 +305,9 @@ class _IndicatorPainter extends CustomPainter { @override bool shouldRepaint(_IndicatorPainter old) { return controller != old.controller || - tabOffsets?.length != old.tabOffsets?.length || - tabOffsetsNotEqual(tabOffsets, old.tabOffsets) || - currentRect != old.currentRect; + _tabOffsets?.length != old._tabOffsets?.length || + _tabOffsetsNotEqual(_tabOffsets, old._tabOffsets) || + _currentRect != old._currentRect; } } @@ -400,18 +408,26 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// /// If a [TabController] is not provided, then there must be a /// [DefaultTabController] ancestor. + /// + /// The [indicatorWeight] parameter defaults to 2, and cannot be null. + /// + /// The [indicatorPadding] parameter defaults to [EdgeInsets.zero], and cannot be null. TabBar({ Key key, @required this.tabs, this.controller, this.isScrollable: false, this.indicatorColor, + this.indicatorWeight: 2.0, + this.indicatorPadding: EdgeInsets.zero, this.labelColor, this.labelStyle, this.unselectedLabelColor, this.unselectedLabelStyle, }) : assert(tabs != null && tabs.length > 1), assert(isScrollable != null), + assert(indicatorWeight != null && indicatorWeight > 0.0), + assert(indicatorPadding != null), super(key: key); /// Typically a list of [Tab] widgets. @@ -434,6 +450,20 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// is null then the value of the Theme's indicatorColor property is used. final Color indicatorColor; + /// The thickness of the line that appears below the selected tab. The value + /// of this parameter must be greater than zero. + /// + /// The default value of [indicatorWeight] is 2.0. + final double indicatorWeight; + + /// The horizontal padding for the line that appears below the selected tab. + /// For [isScrollable] tab bars, specifying [kDefaultTabLabelPadding] will align + /// the indicator with the tab's text for [Tab] widgets and all but the + /// shortest [Tab.text] values. + /// + /// The default value of [indicatorPadding] is [EdgeInsets.zero]. + final EdgeInsets indicatorPadding; + /// The color of selected tab labels. /// /// Unselected tab labels are rendered with the same color rendered at 70% @@ -472,10 +502,10 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { if (item is Tab) { final Tab tab = item; if (tab.text != null && tab.icon != null) - return const Size.fromHeight(_kTextAndIconTabHeight + _kTabIndicatorHeight); + return new Size.fromHeight(_kTextAndIconTabHeight + indicatorWeight); } } - return const Size.fromHeight(_kTabHeight + _kTabIndicatorHeight); + return new Size.fromHeight(_kTabHeight + indicatorWeight); } @override @@ -515,8 +545,13 @@ class _TabBarState extends State { _controller.animation.addListener(_handleTabControllerAnimationTick); _controller.addListener(_handleTabControllerTick); _currentIndex = _controller.index; - final List offsets = _indicatorPainter?.tabOffsets; - _indicatorPainter = new _IndicatorPainter(_controller)..tabOffsets = offsets; + final List offsets = _indicatorPainter?._tabOffsets; + _indicatorPainter = new _IndicatorPainter( + controller: _controller, + indicatorWeight: widget.indicatorWeight, + indicatorPadding: widget.indicatorPadding, + initialTabOffsets: offsets, + ); } } @@ -543,14 +578,14 @@ class _TabBarState extends State { super.dispose(); } - // tabOffsets[index] is the offset of the left edge of the tab at index, and - // tabOffsets[tabOffsets.length] is the right edge of the last tab. - int get maxTabIndex => _indicatorPainter.tabOffsets.length - 2; + // _tabOffsets[index] is the offset of the left edge of the tab at index, and + // _tabOffsets[_tabOffsets.length] is the right edge of the last tab. + int get maxTabIndex => _indicatorPainter._tabOffsets.length - 2; double _tabScrollOffset(int index, double viewportWidth, double minExtent, double maxExtent) { if (!widget.isScrollable) return 0.0; - final List tabOffsets = _indicatorPainter.tabOffsets; + final List tabOffsets = _indicatorPainter._tabOffsets; assert(tabOffsets != null && index >= 0 && index <= maxTabIndex); final double tabCenter = (tabOffsets[index] + tabOffsets[index + 1]) / 2.0; return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent); @@ -610,7 +645,7 @@ class _TabBarState extends State { // Called each time layout completes. void _saveTabOffsets(List tabOffsets) { - _indicatorPainter?.tabOffsets = tabOffsets; + _indicatorPainter?._tabOffsets = tabOffsets; } void _handleTap(int index) { @@ -638,8 +673,8 @@ class _TabBarState extends State { // of a Hero (typically the AppBar), then we will not be able to find the // controller during a Hero transition. See https://github.com/flutter/flutter/issues/213. if (_controller != null) { - _indicatorPainter.color = widget.indicatorColor ?? Theme.of(context).indicatorColor; - if (_indicatorPainter.color == Material.of(context).color) { + _indicatorPainter._color = widget.indicatorColor ?? Theme.of(context).indicatorColor; + if (_indicatorPainter._color == Material.of(context).color) { // ThemeData tries to avoid this by having indicatorColor avoid being the // primaryColor. However, it's possible that the tab bar is on a // Material that isn't the primaryColor. In that case, if the indicator @@ -647,7 +682,7 @@ class _TabBarState extends State { // automatic transitions of the theme will likely look ugly as the // indicator color suddenly snaps to white at one end, but it's not clear // how to avoid that any further. - _indicatorPainter.color = Colors.white; + _indicatorPainter._color = Colors.white; } if (_controller.index != _currentIndex) { @@ -697,7 +732,7 @@ class _TabBarState extends State { Widget tabBar = new CustomPaint( painter: _indicatorPainter, child: new Padding( - padding: const EdgeInsets.only(bottom: _kTabIndicatorHeight), + padding: new EdgeInsets.only(bottom: widget.indicatorWeight), child: new _TabStyle( animation: kAlwaysDismissedAnimation, selected: false, diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 9961768f2cc66..988e25870c150 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import '../rendering/mock_canvas.dart'; import '../rendering/recording_canvas.dart'; class StateMarker extends StatefulWidget { @@ -835,4 +836,68 @@ void main() { expect(find.text('TAB #19'), findsOneWidget); expect(tester.getTopRight(find.widgetWithText(Tab, 'TAB #19')).dx, 800.0); }); + + testWidgets('TabBar with indicatorWeight, indicatorPadding', (WidgetTester tester) async { + const Color color = const Color(0xFF00FF00); + const double height = 100.0; + const double weight = 8.0; + const double padLeft = 8.0; + const double padRight = 4.0; + + final List tabs = new List.generate(4, (int index) { + return new Container( + key: new ValueKey(index), + height: height, + ); + }); + + final TabController controller = new TabController( + vsync: const TestVSync(), + length: tabs.length, + ); + + await tester.pumpWidget( + new Material( + child: new Column( + children: [ + new TabBar( + indicatorWeight: 8.0, + indicatorColor: color, + indicatorPadding: const EdgeInsets.only(left: padLeft, right: padRight), + controller: controller, + tabs: tabs, + ), + new Flexible(child: new Container()), + ], + ), + ), + ); + + final RenderBox tabBarBox = tester.firstRenderObject(find.byType(TabBar)); + + // Selected tab dimensions + double tabWidth = tester.getSize(find.byKey(const ValueKey(0))).width; + double tabLeft = tester.getTopLeft(find.byKey(const ValueKey(0))).dx; + double tabRight = tabLeft + tabWidth; + + expect(tabBarBox, paints..rect( + style: PaintingStyle.fill, + color: color, + rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight) + )); + + // Select tab 3 + controller.index = 3; + await tester.pumpAndSettle(); + + tabWidth = tester.getSize(find.byKey(const ValueKey(3))).width; + tabLeft = tester.getTopLeft(find.byKey(const ValueKey(3))).dx; + tabRight = tabLeft + tabWidth; + + expect(tabBarBox, paints..rect( + style: PaintingStyle.fill, + color: color, + rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight) + )); + }); } From 0c2546c657e1af2c955a986b493961ede360b69e Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Fri, 9 Jun 2017 16:50:55 -0700 Subject: [PATCH 073/110] Factor out some common code in PaintingContext (#10607) ...and remove some highly specialised methods now that they can just be implemented directly by the previous callers. --- .../flutter/lib/src/rendering/object.dart | 189 +++++------------- .../flutter/lib/src/rendering/proxy_box.dart | 52 ++++- packages/flutter/test/material/page_test.dart | 4 +- .../test/rendering/recording_canvas.dart | 11 +- 4 files changed, 111 insertions(+), 145 deletions(-) diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 81856d505a569..6eb23ead8b21a 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'dart:developer'; -import 'dart:ui' as ui show ImageFilter, PictureRecorder; +import 'dart:ui' as ui show PictureRecorder; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -46,8 +46,8 @@ typedef void PaintingContextCallback(PaintingContext context, Offset offset); /// A place to paint. /// -/// Rather than holding a canvas directly, render objects paint using a painting -/// context. The painting context has a canvas, which receives the +/// Rather than holding a canvas directly, [RenderObject]s paint using a painting +/// context. The painting context has a [Canvas], which receives the /// individual draw operations, and also has functions for painting child /// render objects. /// @@ -242,7 +242,7 @@ class PaintingContext { _currentLayer?.willChangeHint = true; } - /// Adds a composited layer to the recording. + /// Adds a composited leaf layer to the recording. /// /// After calling this function, the [canvas] property will change to refer to /// a new [Canvas] that draws on top of the given layer. @@ -250,12 +250,46 @@ class PaintingContext { /// A [RenderObject] that uses this function is very likely to require its /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs /// ancestor render objects that this render object will include a composited - /// layer, which causes them to use composited clips, for example. + /// layer, which, for example, causes them to use composited clips. + /// + /// See also: + /// + /// * [pushLayer], for adding a layer and using its canvas to paint with that + /// layer. void addLayer(Layer layer) { _stopRecordingIfNeeded(); _appendLayer(layer); } + /// Appends the given layer to the recording, and calls the `painter` callback + /// with that layer, providing the [childPaintBounds] as the paint bounds of + /// the child. Canvas recording commands are not guaranteed to be stored + /// outside of the paint bounds. + /// + /// The given layer must be an unattached orphan. (Providing a newly created + /// object, rather than reusing an existing layer, satisfies that + /// requirement.) + /// + /// The `offset` is the offset to pass to the `painter`. + /// + /// If the `childPaintBounds` are not specified then the current layer's + /// bounds are used. This is appropriate if the child layer does not apply any + /// transformation or clipping to its contents. + /// + /// See also: + /// + /// * [addLayer], for pushing a leaf layer whose canvas is not used. + void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset, { Rect childPaintBounds }) { + assert(!childLayer.attached); + assert(childLayer.parent == null); + assert(painter != null); + _stopRecordingIfNeeded(); + _appendLayer(childLayer); + final PaintingContext childContext = new PaintingContext._(childLayer, childPaintBounds ?? _paintBounds); + painter(childContext, offset); + childContext._stopRecordingIfNeeded(); + } + /// Clip further painting using a rectangle. /// /// * `needsCompositing` is whether the child needs compositing. Typically @@ -269,12 +303,7 @@ class PaintingContext { void pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter) { final Rect offsetClipRect = clipRect.shift(offset); if (needsCompositing) { - _stopRecordingIfNeeded(); - final ClipRectLayer clipLayer = new ClipRectLayer(clipRect: offsetClipRect); - _appendLayer(clipLayer); - final PaintingContext childContext = new PaintingContext._(clipLayer, offsetClipRect); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); + pushLayer(new ClipRectLayer(clipRect: offsetClipRect), painter, offset, childPaintBounds: offsetClipRect); } else { canvas.save(); canvas.clipRect(offsetClipRect); @@ -299,12 +328,7 @@ class PaintingContext { final Rect offsetBounds = bounds.shift(offset); final RRect offsetClipRRect = clipRRect.shift(offset); if (needsCompositing) { - _stopRecordingIfNeeded(); - final ClipRRectLayer clipLayer = new ClipRRectLayer(clipRRect: offsetClipRRect); - _appendLayer(clipLayer); - final PaintingContext childContext = new PaintingContext._(clipLayer, offsetBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); + pushLayer(new ClipRRectLayer(clipRRect: offsetClipRRect), painter, offset, childPaintBounds: offsetBounds); } else { canvas.saveLayer(offsetBounds, _defaultPaint); canvas.clipRRect(offsetClipRRect); @@ -329,12 +353,7 @@ class PaintingContext { final Rect offsetBounds = bounds.shift(offset); final Path offsetClipPath = clipPath.shift(offset); if (needsCompositing) { - _stopRecordingIfNeeded(); - final ClipPathLayer clipLayer = new ClipPathLayer(clipPath: offsetClipPath); - _appendLayer(clipLayer); - final PaintingContext childContext = new PaintingContext._(clipLayer, offsetBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); + pushLayer(new ClipPathLayer(clipPath: offsetClipPath), painter, offset, childPaintBounds: offsetBounds); } else { canvas.saveLayer(bounds.shift(offset), _defaultPaint); canvas.clipPath(clipPath.shift(offset)); @@ -356,13 +375,12 @@ class PaintingContext { final Matrix4 effectiveTransform = new Matrix4.translationValues(offset.dx, offset.dy, 0.0) ..multiply(transform)..translate(-offset.dx, -offset.dy); if (needsCompositing) { - _stopRecordingIfNeeded(); - final TransformLayer transformLayer = new TransformLayer(transform: effectiveTransform); - _appendLayer(transformLayer); - final Rect transformedPaintBounds = MatrixUtils.inverseTransformRect(effectiveTransform, _paintBounds); - final PaintingContext childContext = new PaintingContext._(transformLayer, transformedPaintBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); + pushLayer( + new TransformLayer(transform: effectiveTransform), + painter, + offset, + childPaintBounds: MatrixUtils.inverseTransformRect(effectiveTransform, _paintBounds), + ); } else { canvas.save(); canvas.transform(effectiveTransform.storage); @@ -384,112 +402,9 @@ class PaintingContext { /// A [RenderObject] that uses this function is very likely to require its /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs /// ancestor render objects that this render object will include a composited - /// layer, which causes them to use composited clips, for example. + /// layer, which, for example, causes them to use composited clips. void pushOpacity(Offset offset, int alpha, PaintingContextCallback painter) { - _stopRecordingIfNeeded(); - final OpacityLayer opacityLayer = new OpacityLayer(alpha: alpha); - _appendLayer(opacityLayer); - final PaintingContext childContext = new PaintingContext._(opacityLayer, _paintBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); - } - - /// Apply a mask derived from a shader to further painting. - /// - /// * `offset` is the offset from the origin of the canvas' coordinate system - /// to the origin of the caller's coordinate system. - /// * `shader` is the shader that will generate the mask. The shader operates - /// in the coordinate system of the caller. - /// * `maskRect` is the region of the canvas (in the coodinate system of the - /// caller) in which to apply the mask. - /// * `blendMode` is the [BlendMode] to use when applying the shader to - /// the painting done by `painter`. - /// * `painter` is a callback that will paint with the mask applied. This - /// function calls the `painter` synchronously. - /// - /// A [RenderObject] that uses this function is very likely to require its - /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs - /// ancestor render objects that this render object will include a composited - /// layer, which causes them to use composited clips, for example. - void pushShaderMask(Offset offset, Shader shader, Rect maskRect, BlendMode blendMode, PaintingContextCallback painter) { - _stopRecordingIfNeeded(); - final ShaderMaskLayer shaderLayer = new ShaderMaskLayer( - shader: shader, - maskRect: maskRect.shift(offset), - blendMode: blendMode, - ); - _appendLayer(shaderLayer); - final PaintingContext childContext = new PaintingContext._(shaderLayer, _paintBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); - } - - /// Push a backdrop filter. - /// - /// This function applies a filter to the existing painted content and then - /// synchronously calls the painter to paint on top of the filtered backdrop. - /// - /// A [RenderObject] that uses this function is very likely to require its - /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs - /// ancestor render objects that this render object will include a composited - /// layer, which causes them to use composited clips, for example. - // TODO(abarth): I don't quite understand how this API is supposed to work. - void pushBackdropFilter(Offset offset, ui.ImageFilter filter, PaintingContextCallback painter) { - _stopRecordingIfNeeded(); - final BackdropFilterLayer backdropFilterLayer = new BackdropFilterLayer(filter: filter); - _appendLayer(backdropFilterLayer); - final PaintingContext childContext = new PaintingContext._(backdropFilterLayer, _paintBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); - } - - /// Clip using a physical model layer. - /// - /// * `offset` is the offset from the origin of the canvas' coordinate system - /// to the origin of the caller's coordinate system. - /// * `bounds` is the region of the canvas (in the caller's coodinate system) - /// into which `painter` will paint in. - /// * `clipRRect` is the rounded-rectangle (in the caller's coodinate system) - /// to use to clip the painting done by `painter`. - /// * `elevation` is the z-coordinate at which to place this material. - /// * `color` is the background color. - /// * `painter` is a callback that will paint with the `clipRRect` applied. This - /// function calls the `painter` synchronously. - void pushPhysicalModel(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, double elevation, Color color, PaintingContextCallback painter) { - final Rect offsetBounds = bounds.shift(offset); - final RRect offsetClipRRect = clipRRect.shift(offset); - if (needsCompositing) { - _stopRecordingIfNeeded(); - final PhysicalModelLayer physicalModel = new PhysicalModelLayer( - clipRRect: offsetClipRRect, - elevation: elevation, - color: color, - ); - _appendLayer(physicalModel); - final PaintingContext childContext = new PaintingContext._(physicalModel, offsetBounds); - painter(childContext, offset); - childContext._stopRecordingIfNeeded(); - } else { - if (elevation != 0) { - // The drawShadow call doesn't add the region of the shadow to the - // picture's bounds, so we draw a hardcoded amount of extra space to - // account for the maximum potential area of the shadow. - // TODO(jsimmons): remove this when Skia does it for us. - canvas.drawRect(offsetBounds.inflate(20.0), - new Paint()..color=const Color(0)); - canvas.drawShadow( - new Path()..addRRect(offsetClipRRect), - const Color(0xFF000000), - elevation.toDouble(), - color.alpha != 0xFF, - ); - } - canvas.drawRRect(offsetClipRRect, new Paint()..color=color); - canvas.saveLayer(offsetBounds, _defaultPaint); - canvas.clipRRect(offsetClipRRect); - painter(this, offset); - canvas.restore(); - } + pushLayer(new OpacityLayer(alpha: alpha), painter, offset); } } @@ -2082,8 +1997,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// creates at least one composited layer. For example, videos should return /// true if they use hardware decoders. /// - /// You must call markNeedsCompositingBitsUpdate() if the value of this - /// getter changes. + /// You must call [markNeedsCompositingBitsUpdate] if the value of this getter + /// changes. (This is implied when [adoptChild] or [dropChild] are called.) @protected bool get alwaysNeedsCompositing => false; diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 757db32661aa2..08f0db9477513 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -12,6 +12,7 @@ import 'package:vector_math/vector_math_64.dart'; import 'box.dart'; import 'debug.dart'; +import 'layer.dart'; import 'object.dart'; import 'semantics.dart'; @@ -837,8 +838,15 @@ class RenderShaderMask extends RenderProxyBox { void paint(PaintingContext context, Offset offset) { if (child != null) { assert(needsCompositing); - final Shader shader = _shaderCallback(offset & size); - context.pushShaderMask(offset, shader, Offset.zero & size, _blendMode, super.paint); + context.pushLayer( + new ShaderMaskLayer( + shader: _shaderCallback(offset & size), + maskRect: offset & size, + blendMode: _blendMode, + ), + super.paint, + offset, + ); } } } @@ -878,7 +886,7 @@ class RenderBackdropFilter extends RenderProxyBox { void paint(PaintingContext context, Offset offset) { if (child != null) { assert(needsCompositing); - context.pushBackdropFilter(offset, _filter, super.paint); + context.pushLayer(new BackdropFilterLayer(filter: _filter), super.paint, offset); } } } @@ -1308,11 +1316,47 @@ class RenderPhysicalModel extends _RenderCustomClip { return super.hitTest(result, position: position); } + static final Paint _defaultPaint = new Paint(); + static final Paint _transparentPaint = new Paint()..color = const Color(0x00000000); + @override void paint(PaintingContext context, Offset offset) { if (child != null) { _updateClip(); - context.pushPhysicalModel(needsCompositing, offset, _clip.outerRect, _clip, elevation, color, super.paint); + final RRect offsetClipRRect = _clip.shift(offset); + final Rect offsetBounds = offsetClipRRect.outerRect; + if (needsCompositing) { + final PhysicalModelLayer physicalModel = new PhysicalModelLayer( + clipRRect: offsetClipRRect, + elevation: elevation, + color: color, + ); + context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds); + } else { + final Canvas canvas = context.canvas; + if (elevation != 0.0) { + // The drawShadow call doesn't add the region of the shadow to the + // picture's bounds, so we draw a hardcoded amount of extra space to + // account for the maximum potential area of the shadow. + // TODO(jsimmons): remove this when Skia does it for us. + canvas.drawRect( + offsetBounds.inflate(20.0), + _transparentPaint, + ); + canvas.drawShadow( + new Path()..addRRect(offsetClipRRect), + const Color(0xFF000000), + elevation, + color.alpha != 0xFF, + ); + } + canvas.drawRRect(offsetClipRRect, new Paint()..color = color); + canvas.saveLayer(offsetBounds, _defaultPaint); + canvas.clipRRect(offsetClipRRect); + super.paint(context, offset); + canvas.restore(); + assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false'); + } } } diff --git a/packages/flutter/test/material/page_test.dart b/packages/flutter/test/material/page_test.dart index 9dbb93ac8704c..4545b42ac68df 100644 --- a/packages/flutter/test/material/page_test.dart +++ b/packages/flutter/test/material/page_test.dart @@ -96,10 +96,10 @@ void main() { expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true); // Page 2 is coming in from the right. expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true); - // The shadow should be drawn to one screen width to the left of where + // The shadow should be drawn to one screen width to the left of where // the page 2 box is. `paints` tests relative to the painter's given canvas // rather than relative to the screen so assert that it's one screen - // width to the left of 0 offset box rect and nothing is drawn inside the + // width to the left of 0 offset box rect and nothing is drawn inside the // box's rect. expect(box, paints..rect( rect: new Rect.fromLTWH(-800.0, 0.0, 800.0, 600.0) diff --git a/packages/flutter/test/rendering/recording_canvas.dart b/packages/flutter/test/rendering/recording_canvas.dart index 481306fe9ca24..dbcef517d711d 100644 --- a/packages/flutter/test/rendering/recording_canvas.dart +++ b/packages/flutter/test/rendering/recording_canvas.dart @@ -38,6 +38,12 @@ class TestRecordingCanvas implements Canvas { invocations.add(new _MethodCall(#save)); } + @override + void saveLayer(Rect bounds, Paint paint) { + _saveCount += 1; + invocations.add(new _MethodCall(#saveLayer, [bounds, paint])); + } + @override void restore() { _saveCount -= 1; @@ -77,8 +83,9 @@ class TestRecordingPaintingContext implements PaintingContext { } class _MethodCall implements Invocation { - _MethodCall(this._name); + _MethodCall(this._name, [ this._arguments = const [] ]); final Symbol _name; + final List _arguments; @override bool get isAccessor => false; @override @@ -92,5 +99,5 @@ class _MethodCall implements Invocation { @override Map get namedArguments => {}; @override - List get positionalArguments => []; + List get positionalArguments => _arguments; } From 81eb140411697d22b72baefa9077b63a398af920 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 9 Jun 2017 17:30:04 -0700 Subject: [PATCH 074/110] Mark all iOS devicelab tests flaky (#10609) Host test runner is flaky. These should be re-enabled once the host machine has been deflaked. --- dev/devicelab/manifest.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index cccac3fc86c34..02ea757e7cf63 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -140,12 +140,14 @@ tasks: Checks that platform channels work on iOS. stage: devicelab_ios required_agent_capabilities: ["has-ios-device"] + flaky: true platform_channel_sample_test_ios: description: > Runs a driver test on the Platform Channel sample app on iOS. stage: devicelab_ios required_agent_capabilities: ["has-ios-device"] + flaky: true complex_layout_scroll_perf_ios__timeline_summary: description: > @@ -153,6 +155,7 @@ tasks: iOS. stage: devicelab_ios required_agent_capabilities: ["has-ios-device"] + flaky: true # flutter_gallery_ios__start_up: # description: > @@ -181,6 +184,7 @@ tasks: Measures the IPA size of a basic material app. stage: devicelab_ios required_agent_capabilities: ["has-ios-device"] + flaky: true # microbenchmarks_ios: # description: > @@ -202,6 +206,7 @@ tasks: Runs end-to-end Flutter tests on iOS. stage: devicelab_ios required_agent_capabilities: ["has-ios-device"] + flaky: true ios_sample_catalog_generator: description: > From dbaf12b8ad033f479c7c77b352429739df025ea3 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Mon, 12 Jun 2017 11:24:09 -0700 Subject: [PATCH 075/110] Do not read or write state if PageStorageKeys cannot be found (#10612) --- .../flutter/lib/src/widgets/page_storage.dart | 56 +++++++++++-------- .../test/widgets/page_storage_test.dart | 2 +- .../flutter/test/widgets/page_view_test.dart | 4 +- .../remember_scroll_position_test.dart | 1 + .../test/widgets/scroll_controller_test.dart | 15 +++-- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 90cb7afbcecf7..a9acc70e1b4d0 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -39,21 +39,19 @@ class PageStorageKey extends ValueKey { } class _StorageEntryIdentifier { - _StorageEntryIdentifier(this.clientType, this.keys) { - assert(clientType != null); + _StorageEntryIdentifier(this.keys) { assert(keys != null); } - final Type clientType; final List> keys; + bool get isNotEmpty => keys.isNotEmpty; + @override bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final _StorageEntryIdentifier typedOther = other; - if (clientType != typedOther.clientType || keys.length != typedOther.keys.length) - return false; for (int index = 0; index < keys.length; index += 1) { if (keys[index] != typedOther.keys[index]) return false; @@ -62,11 +60,11 @@ class _StorageEntryIdentifier { } @override - int get hashCode => hashValues(clientType, hashList(keys)); + int get hashCode => hashList(keys); @override String toString() { - return 'StorageEntryIdentifier($clientType, ${keys?.join(":")})'; + return 'StorageEntryIdentifier(${keys?.join(":")})'; } } @@ -94,31 +92,45 @@ class PageStorageBucket { } _StorageEntryIdentifier _computeIdentifier(BuildContext context) { - return new _StorageEntryIdentifier(context.widget.runtimeType, _allKeys(context)); + return new _StorageEntryIdentifier(_allKeys(context)); } Map _storage; - /// Write the given data into this page storage bucket using an identifier - /// computed from the given context. The identifier is based on the keys - /// found in the path from context to the root of the widget tree for this - /// page. Keys are collected until the widget tree's root is reached or - /// a GlobalKey is found. + /// Write the given data into this page storage bucket using the + /// specified identifier or an identifier computed from the given context. + /// The computed identifier is based on the [PageStorageKey]s + /// found in the path from context to the [PageStorage] widget that + /// owns this page storage bucket. /// - /// An explicit identifier can be used in cases where the list of keys - /// is not stable. For example if the path concludes with a GlobalKey - /// that's created by a stateful widget, if the stateful widget is - /// recreated when it's exposed by [Navigator.pop], then its storage - /// identifier will change. + /// If an explicit identifier is not provided and no [PageStorageKey]s + /// are found, then the `data` is not saved. void writeState(BuildContext context, dynamic data, { Object identifier }) { _storage ??= {}; - _storage[identifier ?? _computeIdentifier(context)] = data; + if (identifier != null) { + _storage[identifier] = data; + } else { + final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context); + if (contextIdentifier.isNotEmpty) + _storage[contextIdentifier] = data; + } } - /// Read given data from into this page storage bucket using an identifier - /// computed from the given context. More about [identifier] in [writeState]. + /// Read given data from into this page storage bucket using the specified + /// identifier or an identifier computed from the given context. + /// The computed identifier is based on the [PageStorageKey]s + /// found in the path from context to the [PageStorage] widget that + /// owns this page storage bucket. + /// + /// If an explicit identifier is not provided and no [PageStorageKey]s + /// are found, then null is returned. dynamic readState(BuildContext context, { Object identifier }) { - return _storage != null ? _storage[identifier ?? _computeIdentifier(context)] : null; + if (_storage == null) + return null; + if (identifier != null) + return _storage[identifier]; + final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context); + return contextIdentifier.isNotEmpty ? _storage[contextIdentifier] : null; } } diff --git a/packages/flutter/test/widgets/page_storage_test.dart b/packages/flutter/test/widgets/page_storage_test.dart index d52e173cda3bf..fbe0d08881c3d 100644 --- a/packages/flutter/test/widgets/page_storage_test.dart +++ b/packages/flutter/test/widgets/page_storage_test.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; void main() { testWidgets('PageStorage read and write', (WidgetTester tester) async { - final Key builderKey = const Key('builderKey'); + final Key builderKey = const PageStorageKey('builderKey'); StateSetter setState; int storedValue = 0; diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index 0089e2dd2c827..07f4ea8922d44 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -407,6 +407,7 @@ void main() { new PageStorage( bucket: bucket, child: new PageView( + key: const PageStorageKey('PageView'), controller: controller, children: [ const Placeholder(), @@ -431,6 +432,7 @@ void main() { new PageStorage( bucket: bucket, child: new PageView( + key: const PageStorageKey('PageView'), controller: controller, children: [ const Placeholder(), @@ -447,7 +449,7 @@ void main() { new PageStorage( bucket: bucket, child: new PageView( - key: const Key('Check it again against your list and see consistency!'), + key: const PageStorageKey('Check it again against your list and see consistency!'), controller: controller2, children: [ const Placeholder(), diff --git a/packages/flutter/test/widgets/remember_scroll_position_test.dart b/packages/flutter/test/widgets/remember_scroll_position_test.dart index 393addb45048a..58f039c6ab675 100644 --- a/packages/flutter/test/widgets/remember_scroll_position_test.dart +++ b/packages/flutter/test/widgets/remember_scroll_position_test.dart @@ -18,6 +18,7 @@ class ThePositiveNumbers extends StatelessWidget { @override Widget build(BuildContext context) { return new ListView.builder( + key: const PageStorageKey('ThePositiveNumbers'), itemExtent: 100.0, controller: _controller, itemBuilder: (BuildContext context, int index) { diff --git a/packages/flutter/test/widgets/scroll_controller_test.dart b/packages/flutter/test/widgets/scroll_controller_test.dart index 5ae0e4d5eb0f4..388e347707afc 100644 --- a/packages/flutter/test/widgets/scroll_controller_test.dart +++ b/packages/flutter/test/widgets/scroll_controller_test.dart @@ -266,12 +266,15 @@ void main() { Widget buildFrame(ScrollController controller) { return new PageStorage( bucket: bucket, - child: new ListView( - key: new UniqueKey(), // it's a different ListView every time - controller: controller, - children: new List.generate(50, (int index) { - return new Container(height: 100.0, child: new Text('Item $index')); - }).toList(), + child: new KeyedSubtree( + key: const PageStorageKey('ListView'), + child: new ListView( + key: new UniqueKey(), // it's a different ListView every time + controller: controller, + children: new List.generate(50, (int index) { + return new Container(height: 100.0, child: new Text('Item $index')); + }).toList(), + ), ), ); } From 37e32d5aeec9e4ab79c7d5987db97c4bb9cd8181 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Mon, 12 Jun 2017 12:23:13 -0700 Subject: [PATCH 076/110] Switch `flutter --version --json` to be `flutter --version --machine` (#10627) --- .../lib/src/runner/flutter_command_runner.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 51f313050d58e..b187a2525ff4e 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -60,7 +60,7 @@ class FlutterCommandRunner extends CommandRunner { argParser.addFlag('version', negatable: false, help: 'Reports the version of this tool.'); - argParser.addFlag('json', + argParser.addFlag('machine', negatable: false, hide: true); argParser.addFlag('color', @@ -260,7 +260,7 @@ class FlutterCommandRunner extends CommandRunner { if (globalResults['version']) { flutterUsage.sendCommand('version'); String status; - if (globalResults['json']) { + if (globalResults['machine']) { status = const JsonEncoder.withIndent(' ').convert(FlutterVersion.instance.toJson()); } else { status = FlutterVersion.instance.toString(); @@ -269,8 +269,8 @@ class FlutterCommandRunner extends CommandRunner { return; } - if (globalResults['json']) { - printError('The --json flag is only valid with the --version flag.'); + if (globalResults['machine']) { + printError('The --machine flag is only valid with the --version flag.'); throw new ProcessExit(2); } From a84877222c8c349ce9c4bdbb4929cea47b1ffb7b Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 12 Jun 2017 14:50:04 -0700 Subject: [PATCH 077/110] Run flutter_tools tests serially (#10643) We suspect maybe recent failures are caused by race conditions from running flutter_tools tests in parallel. --- dev/bots/test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 4c19d12ec391a..5c567235b2a27 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -118,7 +118,7 @@ Future _pubRunTest( String workingDirectory, { String testPath, }) { - final List args = ['run', 'test', '-rexpanded']; + final List args = ['run', 'test', '-j1', '-rexpanded']; if (testPath != null) args.add(testPath); return _runCommand(pub, args, workingDirectory: workingDirectory); From 123e9e013dce74b2c5270f124b46cc8c65edcbfb Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Mon, 12 Jun 2017 15:44:33 -0700 Subject: [PATCH 078/110] Allow TabBars, TabBarViews, TabControllers, with zero or one tabs (#10608) --- .../lib/src/material/tab_controller.dart | 37 +++++--- packages/flutter/lib/src/material/tabs.dart | 28 ++++-- packages/flutter/test/material/tabs_test.dart | 95 +++++++++++++++++++ 3 files changed, 139 insertions(+), 21 deletions(-) diff --git a/packages/flutter/lib/src/material/tab_controller.dart b/packages/flutter/lib/src/material/tab_controller.dart index ebe89d408239a..7508357265053 100644 --- a/packages/flutter/lib/src/material/tab_controller.dart +++ b/packages/flutter/lib/src/material/tab_controller.dart @@ -16,6 +16,8 @@ import 'constants.dart'; /// A stateful widget that builds a [TabBar] or a [TabBarView] can create /// a TabController and share it directly. /// +/// ## Sample code +/// /// ```dart /// class _MyDemoState extends State with SingleTickerProviderStateMixin { /// final List myTabs = [ @@ -62,12 +64,18 @@ import 'constants.dart'; /// inherited widget. class TabController extends ChangeNotifier { /// Creates an object that manages the state required by [TabBar] and a [TabBarView]. + /// + /// The [length] cannot be null or negative. Typically its a value greater than one, i.e. + /// typically there are two or more tabs. + /// + /// The `initialIndex` must be valid given [length] and cannot be null. If [length] is + /// zero, then `initialIndex` must be 0 (the default). TabController({ int initialIndex: 0, @required this.length, @required TickerProvider vsync }) - : assert(length != null && length > 1), - assert(initialIndex != null && initialIndex >= 0 && initialIndex < length), + : assert(length != null && length >= 0), + assert(initialIndex != null && initialIndex >= 0 && (length == 0 || initialIndex < length)), _index = initialIndex, _previousIndex = initialIndex, - _animationController = new AnimationController( + _animationController = length < 2 ? null : new AnimationController( value: initialIndex.toDouble(), upperBound: (length - 1).toDouble(), vsync: vsync @@ -81,18 +89,21 @@ class TabController extends ChangeNotifier { /// selected tab is changed, the animation's value equals [index]. The /// animation's value can be [offset] by +/- 1.0 to reflect [TabBarView] /// drag scrolling. - Animation get animation => _animationController.view; + /// + /// If length is zero or one, [index] animations don't happen and the value + /// of this property is [kAlwaysCompleteAnimation]. + Animation get animation => _animationController?.view ?? kAlwaysCompleteAnimation; final AnimationController _animationController; - /// The total number of tabs. Must be greater than one. + /// The total number of tabs. Typically greater than one. final int length; void _changeIndex(int value, { Duration duration, Curve curve }) { assert(value != null); - assert(value >= 0 && value < length); + assert(value >= 0 && (value < length || length == 0)); assert(duration == null ? curve == null : true); assert(_indexIsChangingCount >= 0); - if (value == _index) + if (value == _index || length < 2) return; _previousIndex = index; _index = value; @@ -118,6 +129,9 @@ class TabController extends ChangeNotifier { /// [indexIsChanging] to false, and notifies listeners. /// /// To change the currently selected tab and play the [animation] use [animateTo]. + /// + /// The value of [index] must be valid given [length]. If [length] is zero, + /// then [index] will also be zero. int get index => _index; int _index; set index(int value) { @@ -148,8 +162,9 @@ class TabController extends ChangeNotifier { /// drags left or right. A value between -1.0 and 0.0 implies that the /// TabBarView has been dragged to the left. Similarly a value between /// 0.0 and 1.0 implies that the TabBarView has been dragged to the right. - double get offset => _animationController.value - _index.toDouble(); + double get offset => length > 1 ? _animationController.value - _index.toDouble() : 0.0; set offset(double value) { + assert(length > 1); assert(value != null); assert(value >= -1.0 && value <= 1.0); assert(!indexIsChanging); @@ -160,7 +175,7 @@ class TabController extends ChangeNotifier { @override void dispose() { - _animationController.dispose(); + _animationController?.dispose(); super.dispose(); } } @@ -220,7 +235,7 @@ class _TabControllerScope extends InheritedWidget { class DefaultTabController extends StatefulWidget { /// Creates a default tab controller for the given [child] widget. /// - /// The [length] argument must be great than one. + /// The [length] argument is typically greater than one. /// /// The [initialIndex] argument must not be null. const DefaultTabController({ @@ -231,7 +246,7 @@ class DefaultTabController extends StatefulWidget { }) : assert(initialIndex != null), super(key: key); - /// The total number of tabs. Must be greater than one. + /// The total number of tabs. Typically greater than one. final int length; /// The initial index of the selected tab. diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index a67bf0509c2a2..2a56826c49bd9 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -389,22 +389,23 @@ class _TabBarScrollController extends ScrollController { /// A material design widget that displays a horizontal row of tabs. /// -/// Typically created as part of an [AppBar] and in conjuction with a -/// [TabBarView]. +/// Typically created as the [AppBar.bottom] part of an [AppBar] and in +/// conjuction with a [TabBarView]. /// /// If a [TabController] is not provided, then there must be a -/// [DefaultTabController] ancestor. +/// [DefaultTabController] ancestor. The tab controller's [TabController.length] +/// must equal the length of the [tabs] list. /// /// Requires one of its ancestors to be a [Material] widget. /// /// See also: /// -/// * [TabBarView], which displays the contents that the tab bar is selecting -/// between. +/// * [TabBarView], which displays page views that correspond to each tab. class TabBar extends StatefulWidget implements PreferredSizeWidget { /// Creates a material design tab bar. /// - /// The [tabs] argument must not be null and must have more than one widget. + /// The [tabs] argument cannot be null and its length must match the [controller]'s + /// [TabController.length]. /// /// If a [TabController] is not provided, then there must be a /// [DefaultTabController] ancestor. @@ -424,13 +425,15 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { this.labelStyle, this.unselectedLabelColor, this.unselectedLabelStyle, - }) : assert(tabs != null && tabs.length > 1), + }) : assert(tabs != null), assert(isScrollable != null), assert(indicatorWeight != null && indicatorWeight > 0.0), assert(indicatorPadding != null), super(key: key); - /// Typically a list of [Tab] widgets. + /// Typically a list of two or more [Tab] widgets. + /// + /// The length of this list must match the [controller]'s [TabController.length]. final List tabs; /// This widget's selection and animation state. @@ -667,6 +670,12 @@ class _TabBarState extends State { @override Widget build(BuildContext context) { + if (_controller.length == 0) { + return new Container( + height: _kTabHeight + widget.indicatorWeight, + ); + } + final List wrappedTabs = new List.from(widget.tabs, growable: false); // If the controller was provided by DefaultTabController and we're part @@ -774,8 +783,7 @@ class TabBarView extends StatefulWidget { Key key, @required this.children, this.controller, - }) : assert(children != null && children.length > 1), - super(key: key); + }) : assert(children != null), super(key: key); /// This widget's selection and animation state. /// diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 988e25870c150..4893b5f3ee300 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -900,4 +900,99 @@ void main() { rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight) )); }); + + testWidgets('TabBar etc with zero tabs', (WidgetTester tester) async { + final TabController controller = new TabController( + vsync: const TestVSync(), + length: 0, + ); + + await tester.pumpWidget( + new Material( + child: new Column( + children: [ + new TabBar( + controller: controller, + tabs: const [], + ), + new Flexible( + child: new TabBarView( + controller: controller, + children: const [], + ), + ), + ], + ), + ), + ); + + expect(controller.index, 0); + expect(tester.getSize(find.byType(TabBar)), const Size(800.0, 48.0)); + expect(tester.getSize(find.byType(TabBarView)), const Size(800.0, 600.0 - 48.0)); + + // A fling in the TabBar or TabBarView, shouldn't do anything. + + await(tester.fling(find.byType(TabBar), const Offset(-100.0, 0.0), 5000.0)); + await(tester.pumpAndSettle()); + + await(tester.fling(find.byType(TabBarView), const Offset(100.0, 0.0), 5000.0)); + await(tester.pumpAndSettle()); + + expect(controller.index, 0); + }); + + testWidgets('TabBar etc with one tab', (WidgetTester tester) async { + final TabController controller = new TabController( + vsync: const TestVSync(), + length: 1, + ); + + await tester.pumpWidget( + new Material( + child: new Column( + children: [ + new TabBar( + controller: controller, + tabs: const [const Tab(text: 'TAB')], + ), + new Flexible( + child: new TabBarView( + controller: controller, + children: const [const Text('PAGE')], + ), + ), + ], + ), + ), + ); + + expect(controller.index, 0); + expect(find.text('TAB'), findsOneWidget); + expect(find.text('PAGE'), findsOneWidget); + expect(tester.getSize(find.byType(TabBar)), const Size(800.0, 48.0)); + expect(tester.getSize(find.byType(TabBarView)), const Size(800.0, 600.0 - 48.0)); + + // The one tab spans the app's width + expect(tester.getTopLeft(find.widgetWithText(Tab, 'TAB')).dx, 0); + expect(tester.getTopRight(find.widgetWithText(Tab, 'TAB')).dx, 800); + + // A fling in the TabBar or TabBarView, shouldn't move the tab. + + await(tester.fling(find.byType(TabBar), const Offset(-100.0, 0.0), 5000.0)); + await(tester.pump(const Duration(milliseconds: 50))); + expect(tester.getTopLeft(find.widgetWithText(Tab, 'TAB')).dx, 0); + expect(tester.getTopRight(find.widgetWithText(Tab, 'TAB')).dx, 800); + await(tester.pumpAndSettle()); + + await(tester.fling(find.byType(TabBarView), const Offset(100.0, 0.0), 5000.0)); + await(tester.pump(const Duration(milliseconds: 50))); + expect(tester.getTopLeft(find.widgetWithText(Tab, 'TAB')).dx, 0); + expect(tester.getTopRight(find.widgetWithText(Tab, 'TAB')).dx, 800); + await(tester.pumpAndSettle()); + + expect(controller.index, 0); + expect(find.text('TAB'), findsOneWidget); + expect(find.text('PAGE'), findsOneWidget); + }); + } From 9ac16680d2c5b5a2589465db8578062d0eac4f60 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 12 Jun 2017 16:52:35 -0700 Subject: [PATCH 079/110] Analyze sample code (#10619) --- dev/bots/analyze-sample-code.dart | 266 +++++++++++++++++ dev/bots/test.dart | 9 + .../cupertino/cupertino_buttons_demo.dart | 18 +- .../demo/cupertino/cupertino_dialog_demo.dart | 6 +- packages/flutter/lib/foundation.dart | 9 + .../src/animation/animation_controller.dart | 26 +- packages/flutter/lib/src/animation/tween.dart | 2 +- .../flutter/lib/src/cupertino/colors.dart | 12 +- .../flutter/lib/src/cupertino/switch.dart | 20 ++ .../flutter/lib/src/material/app_bar.dart | 5 + .../lib/src/material/circle_avatar.dart | 3 + packages/flutter/lib/src/material/colors.dart | 282 +++++++++--------- .../flutter/lib/src/material/popup_menu.dart | 8 +- .../flutter/lib/src/painting/box_fit.dart | 17 +- .../flutter/lib/src/painting/box_painter.dart | 40 +-- .../flutter/lib/src/painting/edge_insets.dart | 22 +- .../lib/src/physics/gravity_simulation.dart | 24 ++ .../lib/src/widgets/animated_cross_fade.dart | 2 +- packages/flutter/lib/src/widgets/async.dart | 3 + packages/flutter/lib/src/widgets/basic.dart | 2 +- packages/flutter/lib/src/widgets/binding.dart | 16 +- .../flutter/lib/src/widgets/framework.dart | 39 ++- .../lib/src/widgets/gesture_detector.dart | 8 +- 23 files changed, 612 insertions(+), 227 deletions(-) create mode 100644 dev/bots/analyze-sample-code.dart diff --git a/dev/bots/analyze-sample-code.dart b/dev/bots/analyze-sample-code.dart new file mode 100644 index 0000000000000..7c63073d4ebbb --- /dev/null +++ b/dev/bots/analyze-sample-code.dart @@ -0,0 +1,266 @@ +// Copyright 2017 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:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script)))); +final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); + +class Line { + const Line(this.filename, this.line, this.indent); + final String filename; + final int line; + final int indent; + Line get next => this + 1; + Line operator +(int count) { + if (count == 0) + return this; + return new Line(filename, line + count, indent); + } + @override + String toString([int column]) { + if (column != null) + return '$filename:$line:${column + indent}'; + return '$filename:$line'; + } +} + +class Section { + const Section(this.start, this.preamble, this.code, this.postamble); + final Line start; + final String preamble; + final List code; + final String postamble; + Iterable get strings sync* { + if (preamble != null) + yield preamble; + yield* code; + if (postamble != null) + yield postamble; + } + List get lines { + final List result = new List.generate(code.length, (int index) => start + index); + if (preamble != null) + result.insert(0, null); + if (postamble != null) + result.add(null); + return result; + } +} + +const String kDartDocPrefix = '///'; +const String kDartDocPrefixWithSpace = '$kDartDocPrefix '; + +/// To run this: bin/cache/dart-sdk/bin/dart dev/bots/analyze-sample-code.dart +Future main() async { + final Directory temp = Directory.systemTemp.createTempSync('analyze_sample_code_'); + int exitCode = 1; + bool keepMain = false; + try { + final File mainDart = new File(path.join(temp.path, 'main.dart')); + final File pubSpec = new File(path.join(temp.path, 'pubspec.yaml')); + final Directory flutterPackage = new Directory(path.join(_flutterRoot, 'packages', 'flutter', 'lib')); + final List
sections =
[]; + int sampleCodeSections = 0; + for (FileSystemEntity file in flutterPackage.listSync(recursive: true, followLinks: false)) { + if (file is File && path.extension(file.path) == '.dart') { + final List lines = file.readAsLinesSync(); + bool inPreamble = false; + bool inSampleSection = false; + bool inDart = false; + bool foundDart = false; + int lineNumber = 0; + final List block = []; + Line startLine; + for (String line in lines) { + lineNumber += 1; + final String trimmedLine = line.trim(); + if (inPreamble) { + if (line.isEmpty) { + inPreamble = false; + processBlock(startLine, block, sections); + } else if (!line.startsWith('// ')) { + throw '${file.path}:$lineNumber: Unexpected content in sample code preamble.'; + } else { + block.add(line.substring(3)); + } + } else if (inSampleSection) { + if (!trimmedLine.startsWith(kDartDocPrefix) || trimmedLine.startsWith('/// ##')) { + if (inDart) + throw '${file.path}:$lineNumber: Dart section inexplicably unterminated.'; + if (!foundDart) + throw '${file.path}:$lineNumber: No dart block found in sample code section'; + inSampleSection = false; + } else { + if (inDart) { + if (trimmedLine == '/// ```') { + inDart = false; + processBlock(startLine, block, sections); + } else if (trimmedLine == kDartDocPrefix) { + block.add(''); + } else { + final int index = line.indexOf(kDartDocPrefixWithSpace); + if (index < 0) + throw '${file.path}:$lineNumber: Dart section inexplicably did not contain "$kDartDocPrefixWithSpace" prefix.'; + block.add(line.substring(index + 4)); + } + } else if (trimmedLine == '/// ```dart') { + assert(block.isEmpty); + startLine = new Line(file.path, lineNumber + 1, line.indexOf(kDartDocPrefixWithSpace) + kDartDocPrefixWithSpace.length); + inDart = true; + foundDart = true; + } + } + } else if (line == '// Examples can assume:') { + assert(block.isEmpty); + startLine = new Line(file.path, lineNumber + 1, 3); + inPreamble = true; + } else if (trimmedLine == '/// ## Sample code') { + inSampleSection = true; + foundDart = false; + sampleCodeSections += 1; + } + } + } + } + final List buffer = []; + buffer.add('// generated code'); + buffer.add('import \'dart:math\' as math;'); + buffer.add('import \'dart:ui\' as ui;'); + for (FileSystemEntity file in flutterPackage.listSync(recursive: false, followLinks: false)) { + if (file is File && path.extension(file.path) == '.dart') { + buffer.add(''); + buffer.add('// ${file.path}'); + buffer.add('import \'package:flutter/${path.basename(file.path)}\';'); + } + } + buffer.add(''); + final List lines = new List.filled(buffer.length, null, growable: true); + for (Section section in sections) { + buffer.addAll(section.strings); + lines.addAll(section.lines); + } + mainDart.writeAsStringSync(buffer.join('\n')); + pubSpec.writeAsStringSync(''' +name: analyze_sample_code +dependencies: + flutter: + sdk: flutter +'''); + print('Found $sampleCodeSections sample code sections.'); + final Process process = await Process.start( + _flutter, + ['analyze', '--no-preamble', mainDart.path], + workingDirectory: temp.path, + ); + stderr.addStream(process.stderr); + final List errors = await process.stdout.transform(UTF8.decoder).transform(const LineSplitter()).toList(); + if (errors.first.startsWith('Running "flutter packages get" in ')) + errors.removeAt(0); + if (errors.first.startsWith('Analyzing ')) + errors.removeAt(0); + if (errors.last.endsWith(' issues found.') || errors.last.endsWith(' issue found.')) + errors.removeLast(); + int errorCount = 0; + for (String error in errors) { + const String kBullet = ' • '; + const String kAt = ' at main.dart:'; + const String kColon = ':'; + final int start = error.indexOf(kBullet); + final int end = error.indexOf(kAt); + if (start >= 0 && end >= 0) { + final String message = error.substring(start + kBullet.length, end); + final int colon2 = error.indexOf(kColon, end + kAt.length); + if (colon2 < 0) + throw 'failed to parse error message: $error'; + final String line = error.substring(end + kAt.length, colon2); + final int bullet2 = error.indexOf(kBullet, colon2); + if (bullet2 < 0) + throw 'failed to parse error message: $error'; + final String column = error.substring(colon2 + kColon.length, bullet2); + final int lineNumber = int.parse(line, radix: 10, onError: (String source) => throw 'failed to parse error message: $error'); + final int columnNumber = int.parse(column, radix: 10, onError: (String source) => throw 'failed to parse error message: $error'); + if (lineNumber < 0 || lineNumber >= lines.length) + throw 'failed to parse error message: $error'; + final Line actualLine = lines[lineNumber - 1]; + final String errorCode = error.substring(bullet2 + kBullet.length); + if (errorCode == 'unused_element') { + // We don't really care if sample code isn't used! + } else if (actualLine == null) { + if (errorCode == 'missing_identifier' && lineNumber > 1 && buffer[lineNumber - 2].endsWith(',')) { + final Line actualLine = lines[lineNumber - 2]; + print('${actualLine.toString(buffer[lineNumber - 2].length - 1)}: unexpected comma at end of sample code'); + errorCount += 1; + } else { + print('${mainDart.path}:${lineNumber - 1}:$columnNumber: $message'); + keepMain = true; + errorCount += 1; + } + } else { + print('${actualLine.toString(columnNumber)}: $message ($errorCode)'); + errorCount += 1; + } + } else { + print('?? $error'); + errorCount += 1; + } + } + exitCode = await process.exitCode; + if (exitCode == 1 && errorCount == 0) + exitCode = 0; + if (exitCode == 0) + print('No errors!'); + } finally { + if (keepMain) { + print('Kept ${temp.path} because it had errors (see above).'); + } else { + temp.deleteSync(recursive: true); + } + } + exit(exitCode); +} + +int _expressionId = 0; + +void processBlock(Line line, List block, List
sections) { + if (block.isEmpty) + throw '$line: Empty ```dart block in sample code.'; + if (block.first.startsWith('new ') || block.first.startsWith('const ')) { + _expressionId += 1; + sections.add(new Section(line, 'dynamic expression$_expressionId = ', block.toList(), ';')); + } else if (block.first.startsWith('class ') || block.first.startsWith('const ')) { + sections.add(new Section(line, null, block.toList(), null)); + } else { + final List buffer = []; + int subblocks = 0; + Line subline; + for (int index = 0; index < block.length; index += 1) { + if (block[index] == '' || block[index] == '// ...') { + if (subline == null) + throw '${line + index}: Unexpected blank line or "// ..." line near start of subblock in sample code.'; + subblocks += 1; + processBlock(subline, buffer, sections); + assert(buffer.isEmpty); + subline = null; + } else if (block[index].startsWith('// ')) { + if (buffer.length > 1) // don't include leading comments + buffer.add('/${block[index]}'); // so that it doesn't start with "// " and get caught in this again + } else { + subline ??= line + index; + buffer.add(block[index]); + } + } + if (subblocks > 0) { + if (subline != null) + processBlock(subline, buffer, sections); + } else { + sections.add(new Section(line, null, block.toList(), null)); + } + } + block.clear(); +} diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 5c567235b2a27..dd62802334aff 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -1,3 +1,7 @@ +// Copyright 2017 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:convert'; import 'dart:io'; @@ -37,6 +41,11 @@ Future main() async { options: ['--flutter-repo'], ); + // Analyze all the sample code in the repo + await _runCommand(dart, [path.join(flutterRoot, 'dev', 'bots', 'analyze-sample-code.dart')], + workingDirectory: flutterRoot, + ); + // Try with the --watch analyzer, to make sure it returns success also. // The --benchmark argument exits after one run. await _runFlutterAnalyze(flutterRoot, diff --git a/examples/flutter_gallery/lib/demo/cupertino/cupertino_buttons_demo.dart b/examples/flutter_gallery/lib/demo/cupertino/cupertino_buttons_demo.dart index cbc6d7bccfa65..d9165900d1e82 100644 --- a/examples/flutter_gallery/lib/demo/cupertino/cupertino_buttons_demo.dart +++ b/examples/flutter_gallery/lib/demo/cupertino/cupertino_buttons_demo.dart @@ -5,8 +5,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -const Color _kBlue = const Color(0xFF007AFF); - class CupertinoButtonsDemo extends StatefulWidget { static const String routeName = '/cupertino/buttons'; @@ -27,15 +25,17 @@ class _CupertinoButtonDemoState extends State { children: [ const Padding( padding: const EdgeInsets.all(16.0), - child: const Text('iOS themed buttons are flat. They can have borders or backgrounds but ' - 'only when necessary.'), + child: const Text( + 'iOS themed buttons are flat. They can have borders or backgrounds but ' + 'only when necessary.' + ), ), new Expanded( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: [ new Text(_pressedCount > 0 - ? 'Button pressed $_pressedCount time${_pressedCount == 1 ? '' : 's'}' + ? 'Button pressed $_pressedCount time${_pressedCount == 1 ? "" : "s"}' : ' '), const Padding(padding: const EdgeInsets.all(12.0)), new Align( @@ -46,7 +46,7 @@ class _CupertinoButtonDemoState extends State { new CupertinoButton( child: const Text('Cupertino Button'), onPressed: () { - setState(() {_pressedCount++;}); + setState(() { _pressedCount += 1; }); } ), const CupertinoButton( @@ -59,15 +59,15 @@ class _CupertinoButtonDemoState extends State { const Padding(padding: const EdgeInsets.all(12.0)), new CupertinoButton( child: const Text('With Background'), - color: _kBlue, + color: CupertinoColors.activeBlue, onPressed: () { - setState(() {_pressedCount++;}); + setState(() { _pressedCount += 1; }); } ), const Padding(padding: const EdgeInsets.all(12.0)), const CupertinoButton( child: const Text('Disabled'), - color: _kBlue, + color: CupertinoColors.activeBlue, onPressed: null, ), ], diff --git a/examples/flutter_gallery/lib/demo/cupertino/cupertino_dialog_demo.dart b/examples/flutter_gallery/lib/demo/cupertino/cupertino_dialog_demo.dart index cadff8aafdb3c..d16158c6add54 100644 --- a/examples/flutter_gallery/lib/demo/cupertino/cupertino_dialog_demo.dart +++ b/examples/flutter_gallery/lib/demo/cupertino/cupertino_dialog_demo.dart @@ -5,8 +5,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -const Color _kBlue = const Color(0xFF007AFF); - class CupertinoDialogDemo extends StatefulWidget { static const String routeName = '/cupertino/dialog'; @@ -44,7 +42,7 @@ class _CupertinoDialogDemoState extends State { children: [ new CupertinoButton( child: const Text('Alert'), - color: _kBlue, + color: CupertinoColors.activeBlue, onPressed: () { showDemoDialog( context: context, @@ -69,7 +67,7 @@ class _CupertinoDialogDemoState extends State { const Padding(padding: const EdgeInsets.all(8.0)), new CupertinoButton( child: const Text('Alert with Title'), - color: _kBlue, + color: CupertinoColors.activeBlue, padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0), onPressed: () { showDemoDialog( diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index 6221f086f631e..aee7024c1b3e3 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -16,6 +16,15 @@ export 'package:meta/meta.dart' show protected, required; +// Examples can assume: +// bool _first; +// bool _lights; +// bool _visible; +// double _volume; +// dynamic _selection; +// dynamic _last; +// dynamic _calculation; + export 'src/foundation/assertions.dart'; export 'src/foundation/basic_types.dart'; export 'src/foundation/binding.dart'; diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index 6e04b18326dc6..26c629ea63937 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -15,6 +15,9 @@ import 'listener_helpers.dart'; export 'package:flutter/scheduler.dart' show TickerFuture, TickerCanceled; +// Examples can assume: +// AnimationController _controller; + /// The direction in which an animation is running. enum _AnimationDirection { /// The animation is running from beginning to end. @@ -44,17 +47,20 @@ const Tolerance _kFlingTolerance = const Tolerance( /// * Define the [upperBound] and [lowerBound] values of an animation. /// * Create a [fling] animation effect using a physics simulation. /// -/// By default, an [AnimationController] linearly produces values that range from 0.0 to 1.0, during -/// a given duration. The animation controller generates a new value whenever the device running -/// your app is ready to display a new frame (typically, this rate is around 60 values per second). +/// By default, an [AnimationController] linearly produces values that range +/// from 0.0 to 1.0, during a given duration. The animation controller generates +/// a new value whenever the device running your app is ready to display a new +/// frame (typically, this rate is around 60 values per second). /// -/// An AnimationController needs a [TickerProvider], which is configured using the `vsync` argument -/// on the constructor. If you are creating an AnimationController from a [State], then you can use -/// the [TickerProviderStateMixin] and [SingleTickerProviderStateMixin] classes to obtain a suitable -/// [TickerProvider]. The widget test framework [WidgetTester] object can be used as a ticker provider -/// in the context of tests. In other contexts, you will have to either pass a [TickerProvider] from -/// a higher level (e.g. indirectly from a [State] that mixes in [TickerProviderStateMixin]), or -/// create a custom [TickerProvider] subclass. +/// An AnimationController needs a [TickerProvider], which is configured using +/// the `vsync` argument on the constructor. If you are creating an +/// AnimationController from a [State], then you can use the +/// [TickerProviderStateMixin] and [SingleTickerProviderStateMixin] classes to +/// obtain a suitable [TickerProvider]. The widget test framework [WidgetTester] +/// object can be used as a ticker provider in the context of tests. In other +/// contexts, you will have to either pass a [TickerProvider] from a higher +/// level (e.g. indirectly from a [State] that mixes in +/// [TickerProviderStateMixin]), or create a custom [TickerProvider] subclass. /// /// The methods that start animations return a [TickerFuture] object which /// completes when the animation completes successfully, and never throws an diff --git a/packages/flutter/lib/src/animation/tween.dart b/packages/flutter/lib/src/animation/tween.dart index 1db4175c6ac10..6a43000545511 100644 --- a/packages/flutter/lib/src/animation/tween.dart +++ b/packages/flutter/lib/src/animation/tween.dart @@ -97,7 +97,7 @@ class _ChainedEvaluation extends Animatable { /// `_animation`: /// /// ```dart -/// _animation = new Tween( +/// Animation _animation = new Tween( /// begin: const Offset(100.0, 50.0), /// end: const Offset(200.0, 300.0), /// ).animate(_controller); diff --git a/packages/flutter/lib/src/cupertino/colors.dart b/packages/flutter/lib/src/cupertino/colors.dart index 17ab67e52fede..8765b3e6ee4e9 100644 --- a/packages/flutter/lib/src/cupertino/colors.dart +++ b/packages/flutter/lib/src/cupertino/colors.dart @@ -4,7 +4,8 @@ import 'dart:ui' show Color; -/// [Color] constants that describe colors commonly used in iOS applications. +/// A palette of [Color] constants that describe colors commonly used when +/// matching the iOS platform aesthetics. class CupertinoColors { CupertinoColors._(); @@ -18,9 +19,18 @@ class CupertinoColors { static const Color activeGreen = const Color(0xFF4CD964); /// Opaque white color. Used for backgrounds and fonts against dark backgrounds. + /// + /// See also: + /// + /// * [Colors.white], the same color, in the material design palette. + /// * [black], opaque black in the [CupertinoColors] palette. static const Color white = const Color(0xFFFFFFFF); /// Opaque black color. Used for texts against light backgrounds. + /// + /// See also: + /// + /// * [Colors.black], the same color, in the material design palette. static const Color black = const Color(0xFF000000); /// Used in iOS 10 for light background fills such as the chat bubble background. diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 8c91132ea8a6f..9d5501e7f3e96 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -22,8 +22,28 @@ import 'thumb_painter.dart'; /// that use a switch will listen for the [onChanged] callback and rebuild the /// switch with a new [value] to update the visual appearance of the switch. /// +/// ## Sample code +/// +/// This sample shows how to use a [CupertinoSwitch] in a [ListTile]. The +/// [MergeSemantics] is used to turn the entire [ListTile] into a single item +/// for accessibility tools. +/// +/// ```dart +/// new MergeSemantics( +/// child: new ListTile( +/// title: new Text('Lights'), +/// trailing: new CupertinoSwitch( +/// value: _lights, +/// onChanged: (bool value) { setState(() { _lights = value; }); }, +/// ), +/// onTap: () { setState(() { _lights = !_lights; }); }, +/// ), +/// ) +/// ``` +/// /// See also: /// +/// * [Switch], the material design equivalent. /// * class CupertinoSwitch extends StatefulWidget { /// Creates an iOS-style switch. diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 09fd27d2586ef..aecefbda92a09 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -21,6 +21,11 @@ import 'tabs.dart'; import 'theme.dart'; import 'typography.dart'; +// Examples can assume: +// void _airDress() { } +// void _restitchDress() { } +// void _repairDress() { } + const double _kLeadingWidth = kToolbarHeight; // So the leading button is square. // Bottom justify the kToolbarHeight child which may overflow the top. diff --git a/packages/flutter/lib/src/material/circle_avatar.dart b/packages/flutter/lib/src/material/circle_avatar.dart index cd45511f91fe6..10872846f5f42 100644 --- a/packages/flutter/lib/src/material/circle_avatar.dart +++ b/packages/flutter/lib/src/material/circle_avatar.dart @@ -9,6 +9,9 @@ import 'constants.dart'; import 'theme.dart'; import 'typography.dart'; +// Examples can assume: +// String userAvatarUrl; + /// A circle that represents a user. /// /// Typically used with a user's profile image, or, in the absence of diff --git a/packages/flutter/lib/src/material/colors.dart b/packages/flutter/lib/src/material/colors.dart index 14a1fa798768f..f11c210791594 100644 --- a/packages/flutter/lib/src/material/colors.dart +++ b/packages/flutter/lib/src/material/colors.dart @@ -105,7 +105,7 @@ class MaterialAccentColor extends ColorSwatch { /// using an integer for the specific color desired, as follows: /// /// ```dart -/// Colors.green[400] // Selects a mid-range green. +/// Color selection = Colors.green[400]; // Selects a mid-range green. /// ``` /// /// Each [ColorSwatch] constant is a color and can used directly. For example: @@ -368,10 +368,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.red[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.red[400], + /// ) /// ``` /// /// See also: @@ -411,10 +411,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.redAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.redAccent[400], + /// ) /// ``` /// /// See also: @@ -448,10 +448,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.pink[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.pink[400], + /// ) /// ``` /// /// See also: @@ -491,10 +491,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.pinkAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.pinkAccent[400], + /// ) /// ``` /// /// See also: @@ -528,10 +528,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.purple[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.purple[400], + /// ) /// ``` /// /// See also: @@ -571,10 +571,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.purpleAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.purpleAccent[400], + /// ) /// ``` /// /// See also: @@ -608,10 +608,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.deepPurple[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.deepPurple[400], + /// ) /// ``` /// /// See also: @@ -651,10 +651,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.deepPurpleAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.deepPurpleAccent[400], + /// ) /// ``` /// /// See also: @@ -688,10 +688,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.indigo[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.indigo[400], + /// ) /// ``` /// /// See also: @@ -731,10 +731,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.indigoAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.indigoAccent[400], + /// ) /// ``` /// /// See also: @@ -770,10 +770,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.blue[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.blue[400], + /// ) /// ``` /// /// See also: @@ -813,10 +813,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.blueAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.blueAccent[400], + /// ) /// ``` /// /// See also: @@ -850,10 +850,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.lightBlue[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.lightBlue[400], + /// ) /// ``` /// /// See also: @@ -893,10 +893,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.lightBlueAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.lightBlueAccent[400], + /// ) /// ``` /// /// See also: @@ -932,10 +932,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.cyan[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.cyan[400], + /// ) /// ``` /// /// See also: @@ -975,10 +975,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.cyanAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.cyanAccent[400], + /// ) /// ``` /// /// See also: @@ -1012,10 +1012,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.teal[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.teal[400], + /// ) /// ``` /// /// See also: @@ -1055,10 +1055,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.tealAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.tealAccent[400], + /// ) /// ``` /// /// See also: @@ -1095,10 +1095,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.green[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.green[400], + /// ) /// ``` /// /// See also: @@ -1141,10 +1141,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.greenAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.greenAccent[400], + /// ) /// ``` /// /// See also: @@ -1178,10 +1178,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.lightGreen[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.lightGreen[400], + /// ) /// ``` /// /// See also: @@ -1221,10 +1221,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.lightGreenAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.lightGreenAccent[400], + /// ) /// ``` /// /// See also: @@ -1258,10 +1258,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.lime[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.lime[400], + /// ) /// ``` /// /// See also: @@ -1301,10 +1301,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.limeAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.limeAccent[400], + /// ) /// ``` /// /// See also: @@ -1338,10 +1338,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.yellow[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.yellow[400], + /// ) /// ``` /// /// See also: @@ -1381,10 +1381,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.yellowAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.yellowAccent[400], + /// ) /// ``` /// /// See also: @@ -1418,10 +1418,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.amber[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.amber[400], + /// ) /// ``` /// /// See also: @@ -1461,10 +1461,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.amberAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.amberAccent[400], + /// ) /// ``` /// /// See also: @@ -1500,10 +1500,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.orange[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.orange[400], + /// ) /// ``` /// /// See also: @@ -1543,10 +1543,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.orangeAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.orangeAccent[400], + /// ) /// ``` /// /// See also: @@ -1582,10 +1582,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.deepOrange[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.deepOrange[400], + /// ) /// ``` /// /// See also: @@ -1625,10 +1625,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.deepOrangeAccent[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.deepOrangeAccent[400], + /// ) /// ``` /// /// See also: @@ -1661,10 +1661,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.brown[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.brown[400], + /// ) /// ``` /// /// See also: @@ -1707,10 +1707,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.grey[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.grey[400], + /// ) /// ``` /// /// See also: @@ -1754,10 +1754,10 @@ class Colors { /// ## Sample code /// /// ```dart - /// new Icon( - /// icon: Icons.widgets, - /// color: Colors.blueGrey[400], - /// ), + /// new Icon( + /// Icons.widgets, + /// color: Colors.blueGrey[400], + /// ) /// ``` /// /// See also: diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 01b64d3fb387d..f0200708014f5 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -16,6 +16,10 @@ import 'list_tile.dart'; import 'material.dart'; import 'theme.dart'; +// Examples can assume: +// enum Commands { heroAndScholar, hurricaneCame } +// dynamic _heroAndScholar; + const Duration _kMenuDuration = const Duration(milliseconds: 300); const double _kBaselineOffsetFromBottom = 20.0; const double _kMenuCloseIntervalEnd = 2.0 / 3.0; @@ -133,7 +137,7 @@ class _PopupMenuDividerState extends State { /// const PopupMenuItem( /// value: WhyFarther.harder, /// child: const Text('Working a lot harder'), -/// ), +/// ) /// ``` /// /// See the example at [PopupMenuButton] for how this example could be used in a @@ -616,6 +620,8 @@ typedef List> PopupMenuItemBuilder(BuildContext context); /// the selected menu item. If child is null then a standard 'navigation/more_vert' /// icon is created. /// +/// ## Sample code +/// /// This example shows a menu with four items, selecting between an enum's /// values and setting a `_selection` field based on the selection. /// diff --git a/packages/flutter/lib/src/painting/box_fit.dart b/packages/flutter/lib/src/painting/box_fit.dart index 5d538fc47d64e..99ec3f3147745 100644 --- a/packages/flutter/lib/src/painting/box_fit.dart +++ b/packages/flutter/lib/src/painting/box_fit.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:math' as math; -import 'dart:ui' show Image; // to disambiguate mentions of Image in the dartdocs import 'package:flutter/foundation.dart'; @@ -105,16 +104,18 @@ class FittedSizes { /// /// ## Sample code /// -/// This example paints an [Image] `image` onto the [Rect] `outputRect` on a -/// [Canvas] `canvas`, using a [Paint] paint, applying the [BoxFit] algorithm +/// This function paints a [dart:ui.Image] `image` onto the [Rect] `outputRect` on a +/// [Canvas] `canvas`, using a [Paint] `paint`, applying the [BoxFit] algorithm /// `fit`: /// /// ```dart -/// final Size imageSize = new Size(image.width.toDouble(), image.height.toDouble()); -/// final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size); -/// final Rect inputSubrect = FractionalOffset.center.inscribe(sizes.source, Offset.zero & imageSize); -/// final Rect outputSubrect = FractionalOffset.center.inscribe(sizes.destination, outputRect); -/// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint); +/// void paintImage(ui.Image image, Rect outputRect, Canvas canvas, Paint paint, BoxFit fit) { +/// final Size imageSize = new Size(image.width.toDouble(), image.height.toDouble()); +/// final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size); +/// final Rect inputSubrect = FractionalOffset.center.inscribe(sizes.source, Offset.zero & imageSize); +/// final Rect outputSubrect = FractionalOffset.center.inscribe(sizes.destination, outputRect); +/// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint); +/// } /// ``` /// /// See also: diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index 19af89bd2a94b..6df8d4f45b40d 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -328,18 +328,21 @@ class BorderSide { /// /// ## Sample code /// +/// All four borders the same, two-pixel wide solid white: +/// /// ```dart -/// // All four borders the same, two-pixel wide solid white: /// new Border.all(width: 2.0, color: const Color(0xFFFFFFFF)) /// ``` /// +/// The border for a material design divider: +/// /// ```dart -/// // The border for a material design divider: /// new Border(bottom: new BorderSide(color: Theme.of(context).dividerColor)) /// ``` /// +/// A 1990s-era "OK" button: +/// /// ```dart -/// // A 1990s-era "OK" button: /// new Container( /// decoration: const BoxDecoration( /// border: const Border( @@ -1013,21 +1016,24 @@ class LinearGradient extends Gradient { /// /// ## Sample code /// +/// This function draws a gradient that looks like a sun in a blue sky. +/// /// ```dart -/// // This gradient looks like a sun in a blue sky. -/// var gradient = new RadialGradient( -/// center: const FractionalOffset(0.7, 0.2), // near the top right -/// radius: 0.2, -/// colors: [ -/// const Color(0xFFFFFF00), // yellow sun -/// const Color(0xFF0099FF), // blue sky -/// ], -/// stops: [0.4, 1.0], -/// ); -/// // rect is the area we are painting over -/// var paint = new Paint() -/// ..shader = gradient.createShader(rect); -/// canvas.drawRect(rect, paint); +/// void paintSky(Canvas canvas, Rect rect) { +/// var gradient = new RadialGradient( +/// center: const FractionalOffset(0.7, 0.2), // near the top right +/// radius: 0.2, +/// colors: [ +/// const Color(0xFFFFFF00), // yellow sun +/// const Color(0xFF0099FF), // blue sky +/// ], +/// stops: [0.4, 1.0], +/// ); +/// // rect is the area we are painting over +/// var paint = new Paint() +/// ..shader = gradient.createShader(rect); +/// canvas.drawRect(rect, paint); +/// } /// ``` /// /// See also: diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart index 43de4f51312f0..b6b013bd60435 100644 --- a/packages/flutter/lib/src/painting/edge_insets.dart +++ b/packages/flutter/lib/src/painting/edge_insets.dart @@ -26,14 +26,21 @@ enum Axis { /// /// Here are some examples of how to create [EdgeInsets] instances: /// +/// Typical eight-pixel margin on all sides: +/// /// ```dart -/// // typical 8-pixel margin on all sides /// const EdgeInsets.all(8.0) +/// ``` +/// +/// Eight pixel margin above and below, no horizontal margins: /// -/// // 8-pixel margin above and below, no horizontal margins +/// ```dart /// const EdgeInsets.symmetric(vertical: 8.0) +/// ``` /// -/// // left-margin indent of 40 pixels +/// Left margin indent of 40 pixels: +/// +/// ```dart /// const EdgeInsets.only(left: 40.0) /// ``` /// @@ -49,8 +56,9 @@ class EdgeInsets { /// /// ## Sample code /// + /// Typical eight-pixel margin on all sides: + /// /// ```dart - /// // typical 8-pixel margin on all sides /// const EdgeInsets.all(8.0) /// ``` const EdgeInsets.all(double value) @@ -60,8 +68,9 @@ class EdgeInsets { /// /// ## Sample code /// + /// Left margin indent of 40 pixels: + /// /// ```dart - /// // left-margin indent of 40 pixels /// const EdgeInsets.only(left: 40.0) /// ``` const EdgeInsets.only({ @@ -75,8 +84,9 @@ class EdgeInsets { /// /// ## Sample code /// + /// Eight pixel margin above and below, no horizontal margins: + /// /// ```dart - /// // 8-pixel margin above and below, no horizontal margins /// const EdgeInsets.symmetric(vertical: 8.0) /// ``` const EdgeInsets.symmetric({ double vertical: 0.0, diff --git a/packages/flutter/lib/src/physics/gravity_simulation.dart b/packages/flutter/lib/src/physics/gravity_simulation.dart index 5e75c4b4aa881..2873d65d62d09 100644 --- a/packages/flutter/lib/src/physics/gravity_simulation.dart +++ b/packages/flutter/lib/src/physics/gravity_simulation.dart @@ -8,6 +8,30 @@ import 'simulation.dart'; /// /// Models a particle that follows Newton's second law of motion. The simulation /// ends when the position reaches a defined point. +/// +/// ## Sample code +/// +/// This method triggers an [AnimationController] (a previously constructed +/// `_controller` field) to simulate a fall of 300 pixels. +/// +/// ```dart +/// void _startFall() { +/// _controller.animateWith(new GravitySimulation( +/// 10.0, // acceleration, pixels per second per second +/// 0.0, // starting position, pixels +/// 300.0, // ending position, pixels +/// 0.0, // starting velocity, pixels per second +/// )); +/// } +/// ``` +/// +/// This [AnimationController] could be used with an [AnimatedBuilder] to +/// animate the position of a child as if it was falling. +/// +/// See also: +/// +/// * [Curves.bounceOut], a [Curve] that has a similar aesthetics but includes +/// a bouncing effect. class GravitySimulation extends Simulation { /// Creates a [GravitySimulation] using the given arguments, which are, /// respectively: an acceleration that is to be applied continually over time; diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart index a0231e47201ef..21a7799410630 100644 --- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart +++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart @@ -50,7 +50,7 @@ enum CrossFadeState { /// duration: const Duration(seconds: 3), /// firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0), /// secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0), -/// crossFadeState: _on ? CrossFadeState.showFirst : CrossFadeState.showSecond, +/// crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond, /// ) /// ``` /// diff --git a/packages/flutter/lib/src/widgets/async.dart b/packages/flutter/lib/src/widgets/async.dart index 5dd926788a813..da98ac9a866bf 100644 --- a/packages/flutter/lib/src/widgets/async.dart +++ b/packages/flutter/lib/src/widgets/async.dart @@ -12,6 +12,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart' show required; +// Examples can assume: +// dynamic _lot; + /// Base class for widgets that build themselves based on interaction with /// a specified [Stream]. /// diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index f2a75a75f8967..d91093862b738 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -350,7 +350,7 @@ class CustomPaint extends SingleChildRenderObjectWidget { /// child: new Align( /// alignment: FractionalOffset.topCenter, /// heightFactor: 0.5, -/// child: new Image(...), +/// child: new Image.network(userAvatarUrl), /// ), /// ) /// ``` diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 6bc6363af51f1..53bbc07c72be8 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -35,14 +35,14 @@ export 'dart:ui' show AppLifecycleState, Locale; /// lifecycle messages. See [didChangeAppLifecycleState]. /// /// ```dart -/// class Reactor extends StatefulWidget { -/// const Reactor({ Key key }) : super(key: key); +/// class AppLifecycleReactor extends StatefulWidget { +/// const AppLifecycleReactor({ Key key }) : super(key: key); /// /// @override -/// _ReactorState createState() => new _ReactorState(); +/// _AppLifecycleReactorState createState() => new _AppLifecycleReactorState(); /// } /// -/// class _ReactorState extends State with WidgetsBindingObserver { +/// class _AppLifecycleReactorState extends State with WidgetsBindingObserver { /// @override /// void initState() { /// super.initState(); @@ -104,14 +104,14 @@ abstract class WidgetsBindingObserver { /// rotated (or otherwise changes dimensions). /// /// ```dart - /// class Reactor extends StatefulWidget { - /// const Reactor({ Key key }) : super(key: key); + /// class MetricsReactor extends StatefulWidget { + /// const MetricsReactor({ Key key }) : super(key: key); /// /// @override - /// _ReactorState createState() => new _ReactorState(); + /// _MetricsReactorState createState() => new _MetricsReactorState(); /// } /// - /// class _ReactorState extends State with WidgetsBindingObserver { + /// class _MetricsReactorState extends State with WidgetsBindingObserver { /// @override /// void initState() { /// super.initState(); diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 4639251c9f4ea..2a64d1adda025 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -17,6 +17,15 @@ export 'package:flutter/foundation.dart' show FlutterError, debugPrint, debugPri export 'package:flutter/foundation.dart' show VoidCallback, ValueChanged, ValueGetter, ValueSetter; export 'package:flutter/rendering.dart' show RenderObject, RenderBox, debugDumpRenderTree, debugDumpLayerTree; +// Examples can assume: +// BuildContext context; +// void setState(VoidCallback fn) { } + +// Examples can assume: +// abstract class RenderFrogJar extends RenderObject { } +// abstract class FrogJar extends RenderObjectWidget { } +// abstract class FrogJarParentData extends ParentData { Size size; } + // KEYS /// A [Key] is an identifier for [Widget]s and [Element]s. @@ -643,20 +652,20 @@ abstract class StatelessWidget extends Widget { /// /// ## Sample code /// -/// The following is a skeleton of a stateful widget subclass called `GreenFrog`: +/// The following is a skeleton of a stateful widget subclass called `YellowBird`: /// /// ```dart -/// class GreenFrog extends StatefulWidget { -/// const GreenFrog({ Key key }) : super(key: key); +/// class YellowBird extends StatefulWidget { +/// const YellowBird({ Key key }) : super(key: key); /// /// @override -/// _GreenFrogState createState() => new _GreenFrogState(); +/// _YellowBirdState createState() => new _YellowBirdState(); /// } /// -/// class _GreenFrogState extends State { +/// class _YellowBirdState extends State { /// @override /// Widget build(BuildContext context) { -/// return new Container(color: const Color(0xFF2DBD3A)); +/// return new Container(color: const Color(0xFFFFE306)); /// } /// } /// ``` @@ -665,15 +674,15 @@ abstract class StatelessWidget extends Widget { /// represented as private member fields. Also, normally widgets have more /// constructor arguments, each of which corresponds to a `final` property. /// -/// The next example shows the more generic widget `Frog` which can be given a +/// The next example shows the more generic widget `Bird` which can be given a /// color and a child, and which has some internal state with a method that /// can be called to mutate it: /// /// ```dart -/// class Frog extends StatefulWidget { -/// const Frog({ +/// class Bird extends StatefulWidget { +/// const Bird({ /// Key key, -/// this.color: const Color(0xFF2DBD3A), +/// this.color: const Color(0xFFFFE306), /// this.child, /// }) : super(key: key); /// @@ -681,10 +690,10 @@ abstract class StatelessWidget extends Widget { /// /// final Widget child; /// -/// _FrogState createState() => new _FrogState(); +/// _BirdState createState() => new _BirdState(); /// } /// -/// class _FrogState extends State { +/// class _BirdState extends State { /// double _size = 1.0; /// /// void grow() { @@ -695,7 +704,7 @@ abstract class StatelessWidget extends Widget { /// Widget build(BuildContext context) { /// return new Container( /// color: widget.color, -/// transform: new Matrix4.diagonalValues(_size, _size, 1.0), +/// transform: new Matrix4.diagonal3Values(_size, _size, 1.0), /// child: widget.child, /// ); /// } @@ -1309,7 +1318,7 @@ abstract class ProxyWidget extends Widget { /// /// ```dart /// class FrogSize extends ParentDataWidget { -/// Pond({ +/// FrogSize({ /// Key key, /// @required this.size, /// @required Widget child, @@ -1425,7 +1434,7 @@ abstract class ParentDataWidget extends ProxyWidge /// /// ```dart /// class FrogColor extends InheritedWidget { -/// const FrogColor( +/// const FrogColor({ /// Key key, /// @required this.color, /// @required Widget child, diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 42d61a36364b8..b6d9ed9f40f02 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -408,10 +408,10 @@ class GestureDetector extends StatelessWidget { /// () => new TapGestureRecognizer(), /// (TapGestureRecognizer instance) { /// instance -/// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }, -/// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }, -/// ..onTap = () { setState(() { _last = 'tap'; }); }, -/// ..onTapCancel = () { setState(() { _last = 'cancel'; }); }, +/// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); } +/// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); } +/// ..onTap = () { setState(() { _last = 'tap'; }); } +/// ..onTapCancel = () { setState(() { _last = 'cancel'; }); }; /// }, /// ), /// }, From a9f1cb8c0af863c207d8a5e588cd269d7297f2c4 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 12 Jun 2017 16:53:01 -0700 Subject: [PATCH 080/110] More documentation (#10606) - How do you handle a tap on text? - Why is AnimatedOpacity expensive? - Why would you use a gesture arena team? ...and other minor fixes --- .../flutter_gallery/lib/gallery/drawer.dart | 14 +++ .../flutter/lib/src/gestures/multidrag.dart | 2 +- .../flutter/lib/src/gestures/recognizer.dart | 2 + packages/flutter/lib/src/gestures/team.dart | 24 +++- .../flutter/lib/src/painting/text_span.dart | 107 +++++++++++++++--- .../lib/src/services/haptic_feedback.dart | 2 +- packages/flutter/lib/src/widgets/basic.dart | 8 +- .../lib/src/widgets/implicit_animations.dart | 20 +++- packages/flutter/lib/src/widgets/text.dart | 28 ++++- 9 files changed, 178 insertions(+), 29 deletions(-) diff --git a/examples/flutter_gallery/lib/gallery/drawer.dart b/examples/flutter_gallery/lib/gallery/drawer.dart index 900719c18e118..992b6a700fba6 100644 --- a/examples/flutter_gallery/lib/gallery/drawer.dart +++ b/examples/flutter_gallery/lib/gallery/drawer.dart @@ -11,6 +11,20 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class LinkTextSpan extends TextSpan { + + // Beware! + // + // This class is only safe because the TapGestureRecognizer is not + // given a deadline and therefore never allocates any resources. + // + // In any other situation -- setting a deadline, using any of the less trivial + // recognizers, etc -- you would have to manage the gesture recognizer's + // lifetime and call dispose() when the TextSpan was no longer being rendered. + // + // Since TextSpan itself is @immutable, this means that you would have to + // manage the recognizer from outside the TextSpan, e.g. in the State of a + // stateful widget that then hands the recognizer to the TextSpan. + LinkTextSpan({ TextStyle style, String url, String text }) : super( style: style, text: text ?? url, diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index 57f2ac0949501..1bab27bfeb57f 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -92,7 +92,7 @@ abstract class MultiDragPointerState { /// Called when the gesture was rejected. /// - /// [dispose()] will be called immediately following this. + /// The [dispose] method will be called immediately following this. @protected @mustCallSuper void rejected() { diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index 93cb880aef4b0..c9107e0bda019 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -147,7 +147,9 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer { /// is shortly after creating the recognizer. GestureArenaTeam get team => _team; GestureArenaTeam _team; + /// The [team] can only be set once. set team(GestureArenaTeam value) { + assert(value != null); assert(_entries.isEmpty); assert(_trackedPointers.isEmpty); assert(_team == null); diff --git a/packages/flutter/lib/src/gestures/team.dart b/packages/flutter/lib/src/gestures/team.dart index fbef0b85b1be3..b23f3e743ee22 100644 --- a/packages/flutter/lib/src/gestures/team.dart +++ b/packages/flutter/lib/src/gestures/team.dart @@ -80,15 +80,33 @@ class _CombiningGestureArenaMember extends GestureArenaMember { } } -/// A group of [GestureArenaMember] objects that are competing as a unit in the [GestureArenaManager]. +/// A group of [GestureArenaMember] objects that are competing as a unit in the +/// [GestureArenaManager]. /// /// Normally, a recognizer competes directly in the [GestureArenaManager] to /// recognize a sequence of pointer events as a gesture. With a /// [GestureArenaTeam], recognizers can compete in the arena in a group with /// other recognizers. /// -/// To assign a gesture recognizer to a team, see -/// [OneSequenceGestureRecognizer.team]. +/// When gesture recognizers are in a team together, then once there are no +/// other competing gestures in the arena, the first gesture to have been added +/// to the team automatically wins, instead of the gestures continuing to +/// compete against each other. +/// +/// For example, [Slider] uses this to support both a +/// [HorizontalDragGestureRecognizer] and a [TapGestureRecognizer], but without +/// the drag recognizer having to wait until the user has dragged outside the +/// slop region of the tap gesture before triggering. Since they compete as a +/// team, as soon as any other recognizers are out of the arena, the drag +/// recognizer wins, even if the user has not actually dragged yet. On the other +/// hand, if the tap can win outright, before the other recognizers are taken +/// out of the arena (e.g. if the slider is in a vertical scrolling list and the +/// user places their finger on the touch surface then lifts it, so that neither +/// the horizontal nor vertical drag recognizers can claim victory) the tap +/// recognizer still actually wins, despite being in the team. +/// +/// To assign a gesture recognizer to a team, set +/// [OneSequenceGestureRecognizer.team] to an instance of [GestureArenaTeam]. class GestureArenaTeam { final Map _combiners = {}; diff --git a/packages/flutter/lib/src/painting/text_span.dart b/packages/flutter/lib/src/painting/text_span.dart index 0b5f824d632f4..0a8258fe90743 100644 --- a/packages/flutter/lib/src/painting/text_span.dart +++ b/packages/flutter/lib/src/painting/text_span.dart @@ -11,13 +11,13 @@ import 'package:flutter/services.dart'; import 'basic_types.dart'; import 'text_style.dart'; -// TODO(abarth): Should this be somewhere more general? +// TODO(ianh): This should be on List itself. bool _deepEquals(List a, List b) { if (a == null) return b == null; if (b == null || a.length != b.length) return false; - for (int i = 0; i < a.length; ++i) { + for (int i = 0; i < a.length; i += 1) { if (a[i] != b[i]) return false; } @@ -40,11 +40,25 @@ bool _deepEquals(List a, List b) { /// span in a widget, use a [RichText]. For text with a single style, consider /// using the [Text] widget. /// +/// ## Sample code +/// +/// The text "Hello world!", in black: +/// +/// ```dart +/// new TextSpan( +/// text: 'Hello world!', +/// style: new TextStyle(color: Colors.black), +/// ) +/// ``` +/// +/// _There is some more detailed sample code in the documentation for the +/// [recognizer] property._ +/// /// See also: /// -/// * [Text] -/// * [RichText] -/// * [TextPainter] +/// * [Text], a widget for showing uniformly-styled text. +/// * [RichText], a widget for finer control of text rendering. +/// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas]. @immutable class TextSpan { /// Creates a [TextSpan] with the given values. @@ -55,7 +69,7 @@ class TextSpan { this.style, this.text, this.children, - this.recognizer + this.recognizer, }); /// The style to apply to the [text] and the [children]. @@ -80,11 +94,75 @@ class TextSpan { /// A gesture recognizer that will receive events that hit this text span. /// - /// [TextSpan] itself does not implement hit testing or event - /// dispatch. The owner of the [TextSpan] tree to which the object - /// belongs is responsible for dispatching events. + /// [TextSpan] itself does not implement hit testing or event dispatch. The + /// object that manages the [TextSpan] painting is also responsible for + /// dispatching events. In the rendering library, that is the + /// [RenderParagraph] object, which corresponds to the [RichText] widget in + /// the widgets layer. + /// + /// [TextSpan] also does not manage the lifetime of the gesture recognizer. + /// The code that owns the [GestureRecognizer] object must call + /// [GestureRecognizer.dispose] when the [TextSpan] object is no longer used. + /// + /// ## Sample code + /// + /// This example shows how to manage the lifetime of a gesture recognizer + /// provided to a [TextSpan] object. It defines a [BuzzingText] widget which + /// uses the [HapticFeedback] class to vibrate the device when the user + /// long-presses the "find the" span, which is underlined in wavy green. The + /// hit-testing is handled by the [RichText] widget. + /// + /// ```dart + /// class BuzzingText extends StatefulWidget { + /// @override + /// _BuzzingTextState createState() => new _BuzzingTextState(); + /// } + /// + /// class _BuzzingTextState extends State { + /// LongPressGestureRecognizer _longPressRecognizer; + /// + /// @override + /// void initState() { + /// super.initState(); + /// _longPressRecognizer = new LongPressGestureRecognizer() + /// ..onLongPress = _handlePress; + /// } + /// + /// @override + /// void dispose() { + /// _longPressRecognizer.dispose(); + /// super.dispose(); + /// } /// - /// For an example, see [RenderParagraph] in the Flutter rendering library. + /// void _handlePress() { + /// HapticFeedback.vibrate(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return new RichText( + /// text: new TextSpan( + /// text: 'Can you ', + /// style: new TextStyle(color: Colors.black), + /// children: [ + /// new TextSpan( + /// text: 'find the', + /// style: new TextStyle( + /// color: Colors.green, + /// decoration: TextDecoration.underline, + /// decorationStyle: TextDecorationStyle.wavy, + /// ), + /// recognizer: _longPressRecognizer, + /// ), + /// new TextSpan( + /// text: ' secret?', + /// ), + /// ], + /// ), + /// ); + /// } + /// } + /// ``` final GestureRecognizer recognizer; /// Apply the [style], [text], and [children] of this object to the @@ -111,7 +189,8 @@ class TextSpan { builder.pop(); } - /// Walks this text span and its decendants in pre-order and calls [visitor] for each span that has text. + /// Walks this text span and its decendants in pre-order and calls [visitor] + /// for each span that has text. bool visitTextSpan(bool visitor(TextSpan span)) { if (text != null) { if (!visitor(this)) @@ -162,6 +241,7 @@ class TextSpan { } /// Returns the UTF-16 code unit at the given index in the flattened string. + /// /// Returns null if the index is out of bounds. int codeUnitAt(int index) { if (index < 0) @@ -208,8 +288,9 @@ class TextSpan { /// valid configuration. Otherwise, returns true. /// /// This is intended to be used as follows: + /// /// ```dart - /// assert(myTextSpan.debugAssertIsValid()); + /// assert(myTextSpan.debugAssertIsValid()); /// ``` bool debugAssertIsValid() { assert(() { @@ -238,7 +319,7 @@ class TextSpan { bool operator ==(dynamic other) { if (identical(this, other)) return true; - if (other is! TextSpan) + if (other.runtimeType != runtimeType) return false; final TextSpan typedOther = other; return typedOther.text == text diff --git a/packages/flutter/lib/src/services/haptic_feedback.dart b/packages/flutter/lib/src/services/haptic_feedback.dart index cd15407b82f61..fa41878bd9d06 100644 --- a/packages/flutter/lib/src/services/haptic_feedback.dart +++ b/packages/flutter/lib/src/services/haptic_feedback.dart @@ -18,7 +18,7 @@ class HapticFeedback { /// On iOS devices that support haptic feedback, this uses the default system /// vibration value (`kSystemSoundID_Vibrate`). /// - /// On Android, this uses the platform haptic feedback API to simulates a + /// On Android, this uses the platform haptic feedback API to simulate a /// short tap on a virtual keyboard. static Future vibrate() async { await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate'); diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index d91093862b738..298824cf3da86 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -3005,7 +3005,7 @@ class Flow extends MultiChildRenderObjectWidget { /// which is less verbose and integrates with [DefaultTextStyle] for default /// styling. /// -/// Example: +/// ## Sample code /// /// ```dart /// new RichText( @@ -3022,9 +3022,9 @@ class Flow extends MultiChildRenderObjectWidget { /// /// See also: /// -/// * [Text] -/// * [TextSpan] -/// * [DefaultTextStyle] +/// * [TextSpan], which is used to describe the text in a paragraph. +/// * [Text], which automatically applies the ambient styles described by a +/// [DefaultTextStyle] to a single string. class RichText extends LeafRenderObjectWidget { /// Creates a paragraph of rich text. /// diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 821674dedd80d..4e74a904e3272 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -601,7 +601,8 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState { } } -/// Animated version of [DefaultTextStyle] which automatically -/// transitions the default text style (the text style to apply to -/// descendant [Text] widgets without explicit style) over a given -/// duration whenever the given style changes. +/// Animated version of [DefaultTextStyle] which automatically transitions the +/// default text style (the text style to apply to descendant [Text] widgets +/// without explicit style) over a given duration whenever the given style +/// changes. class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget { /// Creates a widget that animates the default text style implicitly. /// @@ -708,6 +709,15 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState Date: Mon, 12 Jun 2017 19:46:38 -0700 Subject: [PATCH 081/110] Fix analyzer errors in sample code (#10648) --- packages/flutter/lib/foundation.dart | 5 +++-- .../lib/src/material/tab_controller.dart | 20 +++++++++++++------ packages/flutter/lib/src/widgets/text.dart | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index aee7024c1b3e3..704a8b755f7be 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -17,13 +17,14 @@ export 'package:meta/meta.dart' show required; // Examples can assume: +// String _name; // bool _first; // bool _lights; // bool _visible; // double _volume; -// dynamic _selection; -// dynamic _last; // dynamic _calculation; +// dynamic _last; +// dynamic _selection; export 'src/foundation/assertions.dart'; export 'src/foundation/basic_types.dart'; diff --git a/packages/flutter/lib/src/material/tab_controller.dart b/packages/flutter/lib/src/material/tab_controller.dart index 7508357265053..1c7b8eb10ebcf 100644 --- a/packages/flutter/lib/src/material/tab_controller.dart +++ b/packages/flutter/lib/src/material/tab_controller.dart @@ -14,12 +14,24 @@ import 'constants.dart'; /// The selected tab's index can be changed with [animateTo]. /// /// A stateful widget that builds a [TabBar] or a [TabBarView] can create -/// a TabController and share it directly. +/// a [TabController] and share it directly. +/// +/// When the [TabBar] and [TabBarView] don't have a convenient stateful +/// ancestor, a [TabController] can be shared with the [DefaultTabController] +/// inherited widget. /// /// ## Sample code /// +/// This widget introduces a [Scaffold] with an [AppBar] and a [TabBar]. +/// /// ```dart -/// class _MyDemoState extends State with SingleTickerProviderStateMixin { +/// class MyTabbedPage extends StatefulWidget { +/// const MyTabbedPage({ Key key }) : super(key: key); +/// @override +/// _MyTabbedPageState createState() => new _MyTabbedPageState(); +/// } +/// +/// class _MyTabbedPageState extends State with SingleTickerProviderStateMixin { /// final List myTabs = [ /// new Tab(text: 'LEFT'), /// new Tab(text: 'RIGHT'), @@ -58,10 +70,6 @@ import 'constants.dart'; /// } /// } /// ``` -/// -/// When the [TabBar] and [TabBarView] don't have a convenient stateful -/// ancestor, a TabController can be shared with the [DefaultTabController] -/// inherited widget. class TabController extends ChangeNotifier { /// Creates an object that manages the state required by [TabBar] and a [TabBarView]. /// diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index 5c104cd2cc4c4..d56fa01579d76 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -143,7 +143,7 @@ class DefaultTextStyle extends InheritedWidget { /// /// ```dart /// new Text( -/// 'Hello, $name! How are you?', +/// 'Hello, $_name! How are you?', /// textAlign: TextAlign.center, /// overflow: TextOverflow.ellipsis, /// style: new TextStyle(fontWeight: FontWeight.bold), From d74a5883d9f01db0b5cda7505639b540cd37a780 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 12 Jun 2017 21:47:31 -0700 Subject: [PATCH 082/110] Allow multi-line text fields with no line limit (#10576) --- .../flutter/lib/src/material/text_field.dart | 21 ++++++- .../lib/src/material/text_form_field.dart | 9 ++- .../lib/src/painting/text_painter.dart | 5 ++ .../flutter/lib/src/painting/text_style.dart | 8 ++- .../flutter/lib/src/rendering/editable.dart | 63 ++++++++++++++----- .../flutter/lib/src/rendering/paragraph.dart | 20 ++++-- packages/flutter/lib/src/widgets/basic.dart | 10 ++- .../lib/src/widgets/editable_text.dart | 18 ++++-- packages/flutter/lib/src/widgets/text.dart | 36 ++++++++++- .../test/material/text_field_test.dart | 48 +++++++++----- .../test/rendering/paragraph_test.dart | 34 +++++++++- 11 files changed, 219 insertions(+), 53 deletions(-) diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index e9a7bc8090260..46fb6fe34c0cc 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -62,6 +62,12 @@ class TextField extends StatefulWidget { /// To remove the decoration entirely (including the extra padding introduced /// by the decoration to save space for the labels), set the [decoration] to /// null. + /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is 1, meaning this is a single-line + /// text field. If it is not null, it must be greater than zero. + /// + /// The [keyboardType], [autofocus], and [obscureText] arguments must not be null. const TextField({ Key key, this.controller, @@ -76,7 +82,11 @@ class TextField extends StatefulWidget { this.onChanged, this.onSubmitted, this.inputFormatters, - }) : super(key: key); + }) : assert(keyboardType != null), + assert(autofocus != null), + assert(obscureText != null), + assert(maxLines == null || maxLines > 0), + super(key: key); /// Controls the text being edited. /// @@ -98,6 +108,8 @@ class TextField extends StatefulWidget { final InputDecoration decoration; /// The type of keyboard to use for editing the text. + /// + /// Defaults to [TextInputType.text]. Cannot be null. final TextInputType keyboardType; /// The style to use for the text being edited. @@ -116,7 +128,7 @@ class TextField extends StatefulWidget { /// If true, the keyboard will open as soon as this text field obtains focus. /// Otherwise, the keyboard is only shown after the user taps the text field. /// - /// Defaults to false. + /// Defaults to false. Cannot be null. // See https://github.com/flutter/flutter/issues/7035 for the rationale for this // keyboard behavior. final bool autofocus; @@ -126,13 +138,16 @@ class TextField extends StatefulWidget { /// When this is set to true, all the characters in the text field are /// replaced by U+2022 BULLET characters (•). /// - /// Defaults to false. + /// Defaults to false. Cannot be null. final bool obscureText; /// The maximum number of lines for the text to span, wrapping if necessary. /// /// If this is 1 (the default), the text will not wrap, but will scroll /// horizontally instead. + /// + /// If this is null, there is no limit to the number of lines. If it is not + /// null, the value must be greater than zero. final int maxLines; /// Called when the text being edited changes. diff --git a/packages/flutter/lib/src/material/text_form_field.dart b/packages/flutter/lib/src/material/text_form_field.dart index 8ef46d915f59f..49726958c6b7d 100644 --- a/packages/flutter/lib/src/material/text_form_field.dart +++ b/packages/flutter/lib/src/material/text_form_field.dart @@ -30,7 +30,8 @@ import 'text_field.dart'; class TextFormField extends FormField { /// Creates a [FormField] that contains a [TextField]. /// - /// For a documentation about the various parameters, see [TextField]. + /// For documentation about the various parameters, see the [TextField] class + /// and [new TextField], the constructor. TextFormField({ Key key, TextEditingController controller, @@ -44,7 +45,11 @@ class TextFormField extends FormField { FormFieldSetter onSaved, FormFieldValidator validator, List inputFormatters, - }) : super( + }) : assert(keyboardType != null), + assert(autofocus != null), + assert(obscureText != null), + assert(maxLines == null || maxLines > 0), + super( key: key, initialValue: controller != null ? controller.value.text : '', onSaved: onSaved, diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 8afa2858e716e..03a3ca9355fc3 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -35,6 +35,8 @@ class TextPainter { /// /// The text argument is optional but [text] must be non-null before calling /// [layout]. + /// + /// The [maxLines] property, if non-null, must be greater than zero. TextPainter({ TextSpan text, TextAlign textAlign, @@ -43,6 +45,7 @@ class TextPainter { String ellipsis, }) : assert(text == null || text.debugAssertIsValid()), assert(textScaleFactor != null), + assert(maxLines == null || maxLines > 0), _text = text, _textAlign = textAlign, _textScaleFactor = textScaleFactor, @@ -134,7 +137,9 @@ class TextPainter { /// After this is set, you must call [layout] before the next call to [paint]. int get maxLines => _maxLines; int _maxLines; + /// The value may be null. If it is not null, then it must be greater than zero. set maxLines(int value) { + assert(value == null || value > 0); if (_maxLines == value) return; _maxLines = value; diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 5fcabe8d7bbb1..d56f82b3cfebf 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -234,12 +234,18 @@ class TextStyle { } /// The style information for paragraphs, encoded for use by `dart:ui`. + /// + /// The `textScaleFactor` argument must not be null. If omitted, it defaults + /// to 1.0. The other arguments may be null. The `maxLines` argument, if + /// specified and non-null, must be greater than zero. ui.ParagraphStyle getParagraphStyle({ TextAlign textAlign, double textScaleFactor: 1.0, String ellipsis, int maxLines, - }) { + }) { + assert(textScaleFactor != null); + assert(maxLines == null || maxLines > 0); return new ui.ParagraphStyle( textAlign: textAlign, fontWeight: fontWeight, diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 8622994a44090..cde9cc98293d7 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -84,6 +84,15 @@ class TextSelectionPoint { /// responsibility of higher layers and not handled by this object. class RenderEditable extends RenderBox { /// Creates a render object that implements the visual aspects of a text field. + /// + /// If [showCursor] is not specified, then it defaults to hiding the cursor. + /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is 1, meaning this is a single-line + /// text field. If it is not null, it must be greater than zero. + /// + /// The [offset] is required and must not be null. You can use [new + /// ViewportOffset.zero] if you have no need for scrolling. RenderEditable({ TextSpan text, TextAlign textAlign, @@ -96,7 +105,7 @@ class RenderEditable extends RenderBox { @required ViewportOffset offset, this.onSelectionChanged, this.onCaretChanged, - }) : assert(maxLines != null), + }) : assert(maxLines == null || maxLines > 0), assert(textScaleFactor != null), assert(offset != null), _textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor), @@ -180,13 +189,21 @@ class RenderEditable extends RenderBox { } /// The maximum number of lines for the text to span, wrapping if necessary. + /// /// If this is 1 (the default), the text will not wrap, but will extend /// indefinitely instead. + /// + /// If this is null, there is no limit to the number of lines. + /// + /// When this is not null, the intrinsic height of the render object is the + /// height of one line of text multiplied by this value. In other words, this + /// also controls the height of the actual editing widget. int get maxLines => _maxLines; int _maxLines; + /// The value may be null. If it is not null, then it must be greater than zero. set maxLines(int value) { - assert(value != null); - if (_maxLines == value) + assert(value == null || value > 0); + if (maxLines == value) return; _maxLines = value; markNeedsTextLayout(); @@ -261,7 +278,7 @@ class RenderEditable extends RenderBox { super.detach(); } - bool get _isMultiline => maxLines > 1; + bool get _isMultiline => maxLines != 1; Axis get _viewportAxis => _isMultiline ? Axis.vertical : Axis.horizontal; @@ -359,14 +376,30 @@ class RenderEditable extends RenderBox { // This does not required the layout to be updated. double get _preferredLineHeight => _textPainter.preferredLineHeight; + double _preferredHeight(double width) { + if (maxLines != null) + return _preferredLineHeight * maxLines; + if (width == double.INFINITY) { + final String text = _textPainter.text.toPlainText(); + int lines = 1; + for (int index = 0; index < text.length; index += 1) { + if (text.codeUnitAt(index) == 0x0A) // count explicit line breaks + lines += 1; + } + return _preferredLineHeight * lines; + } + _layoutText(width); + return math.max(_preferredLineHeight, _textPainter.height); + } + @override double computeMinIntrinsicHeight(double width) { - return _preferredLineHeight; + return _preferredHeight(width); } @override double computeMaxIntrinsicHeight(double width) { - return _preferredLineHeight * maxLines; + return _preferredHeight(width); } @override @@ -434,7 +467,7 @@ class RenderEditable extends RenderBox { return; final double caretMargin = _kCaretGap + _kCaretWidth; final double availableWidth = math.max(0.0, constraintWidth - caretMargin); - final double maxWidth = _maxLines > 1 ? availableWidth : double.INFINITY; + final double maxWidth = _isMultiline ? availableWidth : double.INFINITY; _textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth); _textLayoutLastWidth = constraintWidth; } @@ -444,9 +477,7 @@ class RenderEditable extends RenderBox { _layoutText(constraints.maxWidth); _caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, _preferredLineHeight - 2.0 * _kCaretHeightOffset); _selectionRects = null; - size = new Size(constraints.maxWidth, constraints.constrainHeight( - _textPainter.height.clamp(_preferredLineHeight, _preferredLineHeight * _maxLines) - )); + size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth))); final Size contentSize = new Size(_textPainter.width + _kCaretGap + _kCaretWidth, _textPainter.height); final double _maxScrollExtent = _getMaxScrollExtent(contentSize); _hasVisualOverflow = _maxScrollExtent > 0.0; @@ -506,13 +537,13 @@ class RenderEditable extends RenderBox { @override void debugFillDescription(List description) { super.debugFillDescription(description); - description.add('cursorColor: $_cursorColor'); - description.add('showCursor: $_showCursor'); - description.add('maxLines: $_maxLines'); - description.add('selectionColor: $_selectionColor'); + description.add('cursorColor: $cursorColor'); + description.add('showCursor: $showCursor'); + description.add('maxLines: $maxLines'); + description.add('selectionColor: $selectionColor'); description.add('textScaleFactor: $textScaleFactor'); - description.add('selection: $_selection'); - description.add('offset: $_offset'); + description.add('selection: $selection'); + description.add('offset: $offset'); } @override diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 03d537005cd2f..5262cdd4df3b4 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -31,7 +31,11 @@ const String _kEllipsis = '\u2026'; class RenderParagraph extends RenderBox { /// Creates a paragraph render object. /// - /// The [text], [overflow], and [softWrap] arguments must not be null. + /// The [text], [overflow], [softWrap], and [textScaleFactor] arguments must + /// not be null. + /// + /// The [maxLines] property may be null (and indeed defaults to null), but if + /// it is not null, it must be greater than zero. RenderParagraph(TextSpan text, { TextAlign textAlign, bool softWrap: true, @@ -43,6 +47,7 @@ class RenderParagraph extends RenderBox { assert(softWrap != null), assert(overflow != null), assert(textScaleFactor != null), + assert(maxLines == null || maxLines > 0), _softWrap = softWrap, _overflow = overflow, _textPainter = new TextPainter( @@ -77,7 +82,11 @@ class RenderParagraph extends RenderBox { /// Whether the text should break at soft line breaks. /// - /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space. + /// If false, the glyphs in the text will be positioned as if there was + /// unlimited horizontal space. + /// + /// If [softWrap] is false, [overflow] and [textAlign] may have unexpected + /// effects. bool get softWrap => _softWrap; bool _softWrap; set softWrap(bool value) { @@ -116,9 +125,11 @@ class RenderParagraph extends RenderBox { /// An optional maximum number of lines for the text to span, wrapping if necessary. /// If the text exceeds the given number of lines, it will be truncated according - /// to [overflow]. + /// to [overflow] and [softWrap]. int get maxLines => _textPainter.maxLines; + /// The value may be null. If it is not null, then it must be greater than zero. set maxLines(int value) { + assert(value == null || value > 0); if (_textPainter.maxLines == value) return; _textPainter.maxLines = value; @@ -127,8 +138,7 @@ class RenderParagraph extends RenderBox { } void _layoutText({ double minWidth: 0.0, double maxWidth: double.INFINITY }) { - final bool wrap = _softWrap || (_overflow == TextOverflow.ellipsis && maxLines == null); - _textPainter.layout(minWidth: minWidth, maxWidth: wrap ? maxWidth : double.INFINITY); + _textPainter.layout(minWidth: minWidth, maxWidth: _softWrap ? maxWidth : double.INFINITY); } void _layoutTextWithConstraints(BoxConstraints constraints) { diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 298824cf3da86..9f0ed934312f4 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -3028,7 +3028,11 @@ class Flow extends MultiChildRenderObjectWidget { class RichText extends LeafRenderObjectWidget { /// Creates a paragraph of rich text. /// - /// The [text], [softWrap], and [overflow] arguments must not be null. + /// The [text], [softWrap], [overflow], nad [textScaleFactor] arguments must + /// not be null. + /// + /// The [maxLines] property may be null (and indeed defaults to null), but if + /// it is not null, it must be greater than zero. const RichText({ Key key, @required this.text, @@ -3041,6 +3045,7 @@ class RichText extends LeafRenderObjectWidget { assert(softWrap != null), assert(overflow != null), assert(textScaleFactor != null), + assert(maxLines == null || maxLines > 0), super(key: key); /// The text to display in this widget. @@ -3066,6 +3071,9 @@ class RichText extends LeafRenderObjectWidget { /// An optional maximum number of lines for the text to span, wrapping if necessary. /// If the text exceeds the given number of lines, it will be truncated according /// to [overflow]. + /// + /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the + /// edge of the box. final int maxLines; @override diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index dddff7ef4b279..a4c8e7d0a7b40 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -128,6 +128,10 @@ class TextEditingController extends ValueNotifier { class EditableText extends StatefulWidget { /// Creates a basic text input control. /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is 1, meaning this is a single-line + /// text field. If it is not null, it must be greater than zero. + /// /// The [controller], [focusNode], [style], and [cursorColor] arguments must /// not be null. EditableText({ @@ -152,9 +156,9 @@ class EditableText extends StatefulWidget { assert(obscureText != null), assert(style != null), assert(cursorColor != null), - assert(maxLines != null), + assert(maxLines == null || maxLines > 0), assert(autofocus != null), - inputFormatters = maxLines == 1 + inputFormatters = maxLines == 1 ? ( [BlacklistingTextInputFormatter.singleLineFormatter] ..addAll(inputFormatters ?? const Iterable.empty()) @@ -192,8 +196,12 @@ class EditableText extends StatefulWidget { final Color cursorColor; /// The maximum number of lines for the text to span, wrapping if necessary. + /// /// If this is 1 (the default), the text will not wrap, but will scroll /// horizontally instead. + /// + /// If this is null, there is no limit to the number of lines. If it is not + /// null, the value must be greater than zero. final int maxLines; /// Whether this input field should focus itself if nothing else is already focused. @@ -218,7 +226,7 @@ class EditableText extends StatefulWidget { /// Called when the user indicates that they are done editing the text in the field. final ValueChanged onSubmitted; - /// Optional input validation and formatting overrides. Formatters are run + /// Optional input validation and formatting overrides. Formatters are run /// in the provided order when the text input changes. final List inputFormatters; @@ -340,7 +348,7 @@ class EditableTextState extends State implements TextInputClient { } bool get _hasFocus => widget.focusNode.hasFocus; - bool get _isMultiline => widget.maxLines > 1; + bool get _isMultiline => widget.maxLines != 1; // Calculate the new scroll offset so the cursor remains visible. double _getScrollOffsetForCaret(Rect caretRect) { @@ -417,7 +425,7 @@ class EditableTextState extends State implements TextInputClient { void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, bool longPress) { widget.controller.selection = selection; - // Note that this will show the keyboard for all selection changes on the + // This will show the keyboard for all selection changes on the // EditableWidget, not just changes triggered by user gestures. requestKeyboard(); diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index d56fa01579d76..a9b4313538c3f 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -14,6 +14,14 @@ class DefaultTextStyle extends InheritedWidget { /// /// Consider using [DefaultTextStyle.merge] to inherit styling information /// from the current default text style for a given [BuildContext]. + /// + /// The [style] and [child] arguments are required and must not be null. + /// + /// The [softWrap] and [overflow] arguments must not be null (though they do + /// have default values). + /// + /// The [maxLines] property may be null (and indeed defaults to null), but if + /// it is not null, it must be greater than zero. const DefaultTextStyle({ Key key, @required this.style, @@ -25,6 +33,7 @@ class DefaultTextStyle extends InheritedWidget { }) : assert(style != null), assert(softWrap != null), assert(overflow != null), + assert(maxLines == null || maxLines > 0), assert(child != null), super(key: key, child: child); @@ -48,6 +57,15 @@ class DefaultTextStyle extends InheritedWidget { /// for the [BuildContext] where the widget is inserted, and any of the other /// arguments that are not null replace the corresponding properties on that /// same default text style. + /// + /// This constructor cannot be used to override the [maxLines] property of the + /// ancestor with the value null, since null here is used to mean "defer to + /// ancestor". To replace a non-null [maxLines] from an ancestor with the null + /// value (to remove the restriction on number of lines), manually obtain the + /// ambient [DefaultTextStyle] using [DefaultTextStyle.of], then create a new + /// [DefaultTextStyle] using the [new DefaultTextStyle] constructor directly. + /// See the source below for an example of how to do this (since that's + /// essentially what this constructor does). static Widget merge({ Key key, TextStyle style, @@ -91,6 +109,12 @@ class DefaultTextStyle extends InheritedWidget { /// An optional maximum number of lines for the text to span, wrapping if necessary. /// If the text exceeds the given number of lines, it will be truncated according /// to [overflow]. + /// + /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the + /// edge of the box. + /// + /// If this is non-null, it will override even explicit null values of + /// [Text.maxLines]. final int maxLines; /// The closest instance of this class that encloses the given context. @@ -213,9 +237,17 @@ class Text extends StatelessWidget { /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope. final double textScaleFactor; - /// An optional maximum number of lines the text is allowed to take up. + /// An optional maximum number of lines for the text to span, wrapping if necessary. /// If the text exceeds the given number of lines, it will be truncated according /// to [overflow]. + /// + /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the + /// edge of the box. + /// + /// If this is null, but there is an ambient [DefaultTextStyle] that specifies + /// an explicit number for its [DefaultTextStyle.maxLines], then the + /// [DefaultTextStyle] value will take precedence. You can use a [RichText] + /// widget directly to entirely override the [DefaultTextStyle]. final int maxLines; @override @@ -232,7 +264,7 @@ class Text extends StatelessWidget { maxLines: maxLines ?? defaultTextStyle.maxLines, text: new TextSpan( style: effectiveTextStyle, - text: data + text: data, ) ); } diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index d727d3dc17e05..2fbdd0249c71b 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -46,7 +46,7 @@ void main() { 'First line of text is ' 'Second line goes until ' 'Third line of stuff '; - const String kFourLines = + const String kMoreThanFourLines = kThreeLines + 'Fourth line won\'t display and ends at'; @@ -462,7 +462,7 @@ void main() { ); } - await tester.pumpWidget(builder(3)); + await tester.pumpWidget(builder(null)); RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey)); @@ -470,28 +470,44 @@ void main() { final Size emptyInputSize = inputBox.size; await tester.enterText(find.byType(TextField), 'No wrapping here.'); - await tester.pumpWidget(builder(3)); + await tester.pumpWidget(builder(null)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, equals(emptyInputSize)); - await tester.enterText(find.byType(TextField), kThreeLines); await tester.pumpWidget(builder(3)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, greaterThan(emptyInputSize)); final Size threeLineInputSize = inputBox.size; + await tester.enterText(find.byType(TextField), kThreeLines); + await tester.pumpWidget(builder(null)); + expect(findInputBox(), equals(inputBox)); + expect(inputBox.size, greaterThan(emptyInputSize)); + + await tester.enterText(find.byType(TextField), kThreeLines); + await tester.pumpWidget(builder(null)); + expect(findInputBox(), equals(inputBox)); + expect(inputBox.size, threeLineInputSize); + // An extra line won't increase the size because we max at 3. - await tester.enterText(find.byType(TextField), kFourLines); + await tester.enterText(find.byType(TextField), kMoreThanFourLines); await tester.pumpWidget(builder(3)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, threeLineInputSize); - // But now it will. - await tester.enterText(find.byType(TextField), kFourLines); + // But now it will... but it will max at four + await tester.enterText(find.byType(TextField), kMoreThanFourLines); await tester.pumpWidget(builder(4)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, greaterThan(threeLineInputSize)); + + final Size fourLineInputSize = inputBox.size; + + // Now it won't max out until the end + await tester.pumpWidget(builder(null)); + expect(findInputBox(), equals(inputBox)); + expect(inputBox.size, greaterThan(fourLineInputSize)); }); testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async { @@ -594,7 +610,7 @@ void main() { await tester.pumpWidget(builder()); await tester.pump(const Duration(seconds: 1)); - await tester.enterText(find.byType(TextField), kFourLines); + await tester.enterText(find.byType(TextField), kMoreThanFourLines); await tester.pumpWidget(builder()); await tester.pump(const Duration(seconds: 1)); @@ -603,8 +619,8 @@ void main() { final RenderBox inputBox = findInputBox(); // Check that the last line of text is not displayed. - final Offset firstPos = textOffsetToPosition(tester, kFourLines.indexOf('First')); - final Offset fourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth')); + final Offset firstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First')); + final Offset fourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth')); expect(firstPos.dx, fourthPos.dx); expect(firstPos.dy, lessThan(fourthPos.dy)); expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue); @@ -622,8 +638,8 @@ void main() { await tester.pump(); // Now the first line is scrolled up, and the fourth line is visible. - Offset newFirstPos = textOffsetToPosition(tester, kFourLines.indexOf('First')); - Offset newFourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth')); + Offset newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First')); + Offset newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth')); expect(newFirstPos.dy, lessThan(firstPos.dy)); expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse); @@ -633,7 +649,7 @@ void main() { // Long press the 'i' in 'Fourth line' to select the word. await tester.pump(const Duration(seconds: 1)); - final Offset untilPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth line')+8); + final Offset untilPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth line')+8); gesture = await tester.startGesture(untilPos, pointer: 7); await tester.pump(const Duration(seconds: 1)); await gesture.up(); @@ -645,7 +661,7 @@ void main() { // Drag the left handle to the first line, just after 'First'. final Offset handlePos = endpoints[0].point + const Offset(-1.0, 1.0); - final Offset newHandlePos = textOffsetToPosition(tester, kFourLines.indexOf('First') + 5); + final Offset newHandlePos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First') + 5); gesture = await tester.startGesture(handlePos, pointer: 7); await tester.pump(const Duration(seconds: 1)); await gesture.moveTo(newHandlePos + const Offset(0.0, -10.0)); @@ -655,8 +671,8 @@ void main() { // The text should have scrolled up with the handle to keep the active // cursor visible, back to its original position. - newFirstPos = textOffsetToPosition(tester, kFourLines.indexOf('First')); - newFourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth')); + newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First')); + newFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth')); expect(newFirstPos.dy, firstPos.dy); expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue); expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse); diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart index 327157f9f701c..16ebea1594391 100644 --- a/packages/flutter/test/rendering/paragraph_test.dart +++ b/packages/flutter/test/rendering/paragraph_test.dart @@ -79,12 +79,13 @@ void main() { const TextSpan( text: 'This\n' // 4 characters * 10px font size = 40px width on the first line 'is a wrapping test. It should wrap at manual newlines, and if softWrap is true, also at spaces.', - style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0)), + style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), + ), maxLines: 1, softWrap: true, ); - void relayoutWith({int maxLines, bool softWrap, TextOverflow overflow}) { + void relayoutWith({ int maxLines, bool softWrap, TextOverflow overflow }) { paragraph ..maxLines = maxLines ..softWrap = softWrap @@ -147,5 +148,34 @@ void main() { relayoutWith(maxLines: 100, softWrap: true, overflow: TextOverflow.fade); expect(paragraph.debugHasOverflowShader, isFalse); }); + + + test('maxLines', () { + final RenderParagraph paragraph = new RenderParagraph( + const TextSpan( + text: 'How do you write like you\'re running out of time? Write day and night like you\'re running out of time?', + // 0123456789 0123456789 012 345 0123456 012345 01234 012345678 012345678 0123 012 345 0123456 012345 01234 + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), + ), + ); + layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0)); + void layoutAt(int maxLines) { + paragraph.maxLines = maxLines; + pumpFrame(); + } + + layoutAt(null); + expect(paragraph.size.height, 130.0); + + layoutAt(1); + expect(paragraph.size.height, 10.0); + + layoutAt(2); + expect(paragraph.size.height, 20.0); + + layoutAt(3); + expect(paragraph.size.height, 30.0); + }); } From b4ba972bf394a2269824e1eadaa1627bbc872d4b Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Mon, 12 Jun 2017 21:47:41 -0700 Subject: [PATCH 083/110] Rev engine to ffe8181ffe7432b61a67323c80fd8025704e4695 (#10651) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 056abe2f242e8..8171842911ff5 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -ffe8181ffe7432b61a67323c80fd8025704e4695 +4f5d6fab1187c1f5749a52ab5b4277d1bd51ee79 From bb119e95fa67d0e487ea142d1e1a5e93d0d33e85 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Mon, 12 Jun 2017 21:47:56 -0700 Subject: [PATCH 084/110] Make dividers one device pixel thick as defined in Material design spec (#10523) * Make dividers one device pixel thick as defined in Material design spec * Updated divider test to check stroke width * Clarified dividers with 0 height in the docs * Updated Divider.height docs according to PR feedback --- .../flutter/lib/src/material/divider.dart | 38 +++++++++++-------- .../lib/src/material/drawer_header.dart | 2 +- .../flutter/lib/src/material/list_tile.dart | 2 +- .../flutter/test/material/divider_test.dart | 4 +- .../flutter/test/material/drawer_test.dart | 2 +- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/flutter/lib/src/material/divider.dart b/packages/flutter/lib/src/material/divider.dart index 317190ee7b00b..c8dc1b58a41ef 100644 --- a/packages/flutter/lib/src/material/divider.dart +++ b/packages/flutter/lib/src/material/divider.dart @@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; -/// A one logical pixel thick horizontal line, with padding on either +/// A one device pixel thick horizontal line, with padding on either /// side. /// /// In the material design language, this represents a divider. @@ -26,19 +26,22 @@ import 'theme.dart'; class Divider extends StatelessWidget { /// Creates a material design divider. /// - /// The height must be at least 1.0 logical pixels. + /// The height must be positive. const Divider({ Key key, this.height: 16.0, this.indent: 0.0, this.color - }) : assert(height >= 1.0), + }) : assert(height >= 0.0), super(key: key); /// The divider's vertical extent. /// - /// The divider itself is always drawn as one logical pixel thick horizontal + /// The divider itself is always drawn as one device pixel thick horizontal /// line that is centered within the height specified by this value. + /// + /// A divider with a height of 0.0 is always drawn as a line with a height of + /// exactly one device pixel, without any padding around it. final double height; /// The amount of empty space to the left of the divider. @@ -58,19 +61,22 @@ class Divider extends StatelessWidget { @override Widget build(BuildContext context) { - final double bottom = (height ~/ 2.0).toDouble(); - return new Container( - height: 0.0, - margin: new EdgeInsets.only( - top: height - bottom - 1.0, - left: indent, - bottom: bottom + return new SizedBox( + height: height, + child: new Center( + child: new Container( + height: 0.0, + margin: new EdgeInsets.only(left: indent), + decoration: new BoxDecoration( + border: new Border( + bottom: new BorderSide( + color: color ?? Theme.of(context).dividerColor, + width: 0.0, + ), + ), + ), + ), ), - decoration: new BoxDecoration( - border: new Border( - bottom: new BorderSide(color: color ?? Theme.of(context).dividerColor) - ) - ) ); } } diff --git a/packages/flutter/lib/src/material/drawer_header.dart b/packages/flutter/lib/src/material/drawer_header.dart index b94f68eafaa8e..2167ce57c2d63 100644 --- a/packages/flutter/lib/src/material/drawer_header.dart +++ b/packages/flutter/lib/src/material/drawer_header.dart @@ -82,7 +82,7 @@ class DrawerHeader extends StatelessWidget { border: new Border( bottom: new BorderSide( color: theme.dividerColor, - width: 1.0 + width: 0.0 ) ) ), diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index b3416ae9f1c87..70e25030f74fe 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -294,7 +294,7 @@ class ListTile extends StatelessWidget { position: DecorationPosition.foreground, decoration: new BoxDecoration( border: new Border( - bottom: new BorderSide(color: dividerColor), + bottom: new BorderSide(color: dividerColor, width: 0.0), ), ), child: tile, diff --git a/packages/flutter/test/material/divider_test.dart b/packages/flutter/test/material/divider_test.dart index 72e52bacf7364..096a06571c888 100644 --- a/packages/flutter/test/material/divider_test.dart +++ b/packages/flutter/test/material/divider_test.dart @@ -4,11 +4,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../rendering/mock_canvas.dart'; void main() { testWidgets('Divider control test', (WidgetTester tester) async { await tester.pumpWidget(const Center(child: const Divider())); final RenderBox box = tester.firstRenderObject(find.byType(Divider)); - expect(box.size.height, 15.0); + expect(box.size.height, 16.0); + expect(find.byType(Divider), paints..path(strokeWidth: 0.0)); }); } diff --git a/packages/flutter/test/material/drawer_test.dart b/packages/flutter/test/material/drawer_test.dart index eebac15071b68..50d483ae996e4 100644 --- a/packages/flutter/test/material/drawer_test.dart +++ b/packages/flutter/test/material/drawer_test.dart @@ -48,7 +48,7 @@ void main() { box = tester.renderObject(find.byKey(containerKey)); expect(box.size.width, equals(drawerWidth - 2 * 16.0)); - expect(box.size.height, equals(drawerHeight - 2 * 16.0 - 1.0)); // bottom edge + expect(box.size.height, equals(drawerHeight - 2 * 16.0)); expect(find.text('header'), findsOneWidget); }); From e38f92df9a38d8dab604bdd3e67ceb9bdb117a6d Mon Sep 17 00:00:00 2001 From: xster Date: Mon, 12 Jun 2017 22:48:51 -0700 Subject: [PATCH 085/110] Create a CupertinoScaffold (#10543) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * works using pageview it’s a bit heavy and scrolls no documentation or test yet * docs and tests * more docs tweak * revert drive by page view changes * swap out the indexed stack for a custom thing * layout everything on the cheap tests pass * add more tests * move back to stack and add docs * review notes * build all the tabs once built --- packages/flutter/lib/cupertino.dart | 1 + .../lib/src/cupertino/bottom_tab_bar.dart | 37 ++- .../flutter/lib/src/cupertino/nav_bar.dart | 7 +- .../flutter/lib/src/cupertino/scaffold.dart | 229 ++++++++++++++++++ packages/flutter/lib/src/widgets/basic.dart | 13 +- .../lib/src/widgets/navigation_toolbar.dart | 2 +- .../flutter/test/cupertino/scaffold_test.dart | 203 ++++++++++++++++ 7 files changed, 478 insertions(+), 14 deletions(-) create mode 100644 packages/flutter/lib/src/cupertino/scaffold.dart create mode 100644 packages/flutter/test/cupertino/scaffold_test.dart diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index 2726576eb2cdb..064ad2b089a6c 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -14,6 +14,7 @@ export 'src/cupertino/colors.dart'; export 'src/cupertino/dialog.dart'; export 'src/cupertino/nav_bar.dart'; export 'src/cupertino/page.dart'; +export 'src/cupertino/scaffold.dart'; export 'src/cupertino/slider.dart'; export 'src/cupertino/switch.dart'; export 'src/cupertino/thumb_painter.dart'; diff --git a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart index c1e7efa392f14..e28125d9a18dd 100644 --- a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart +++ b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart @@ -31,7 +31,7 @@ const Color _kDefaultTabBarBorderColor = const Color(0x4C000000); /// default), it will produce a blurring effect to the content behind it. // // TODO(xster): document using with a CupertinoScaffold. -class CupertinoTabBar extends StatelessWidget { +class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { /// Creates a tab bar in the iOS style. CupertinoTabBar({ Key key, @@ -81,10 +81,14 @@ class CupertinoTabBar extends StatelessWidget { /// should configure itself to match the icon theme's size and color. final double iconSize; + /// True if the tab bar's background color has no transparency. + bool get opaque => backgroundColor.alpha == 0xFF; + @override - Widget build(BuildContext context) { - final bool addBlur = backgroundColor.alpha != 0xFF; + Size get preferredSize => const Size.fromHeight(_kTabBarHeight); + @override + Widget build(BuildContext context) { Widget result = new DecoratedBox( decoration: new BoxDecoration( border: const Border( @@ -120,7 +124,7 @@ class CupertinoTabBar extends StatelessWidget { ), ); - if (addBlur) { + if (!opaque) { // For non-opaque backgrounds, apply a blur effect. result = new ClipRect( child: new BackdropFilter( @@ -141,6 +145,7 @@ class CupertinoTabBar extends StatelessWidget { _wrapActiveItem( new Expanded( child: new GestureDetector( + behavior: HitTestBehavior.opaque, onTap: onTap == null ? null : () { onTap(index); }, child: new Padding( padding: const EdgeInsets.only(bottom: 4.0), @@ -175,4 +180,28 @@ class CupertinoTabBar extends StatelessWidget { ), ); } + + /// Create a clone of the current [CupertinoTabBar] but with provided + /// parameters overriden. + CupertinoTabBar copyWith({ + Key key, + List items, + Color backgroundColor, + Color activeColor, + Color inactiveColor, + Size iconSize, + int currentIndex, + ValueChanged onTap, + }) { + return new CupertinoTabBar( + key: key ?? this.key, + items: items ?? this.items, + backgroundColor: backgroundColor ?? this.backgroundColor, + activeColor: activeColor ?? this.activeColor, + inactiveColor: inactiveColor ?? this.inactiveColor, + iconSize: iconSize ?? this.iconSize, + currentIndex: currentIndex ?? this.currentIndex, + onTap: onTap ?? this.onTap, + ); + } } diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index 82ecc791d09c1..fe9bd9b9ef215 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -69,13 +69,14 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid /// The [title] remains black if it's a text as per iOS standard design. final Color actionsForegroundColor; + /// True if the nav bar's background color has no transparency. + bool get opaque => backgroundColor.alpha == 0xFF; + @override Size get preferredSize => const Size.fromHeight(_kNavBarHeight); @override Widget build(BuildContext context) { - final bool addBlur = backgroundColor.alpha != 0xFF; - Widget styledMiddle = middle; if (styledMiddle.runtimeType == Text || styledMiddle.runtimeType == DefaultTextStyle) { // Let the middle be black rather than `actionsForegroundColor` in case @@ -132,7 +133,7 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid ), ); - if (addBlur) { + if (!opaque) { // For non-opaque backgrounds, apply a blur effect. result = new ClipRect( child: new BackdropFilter( diff --git a/packages/flutter/lib/src/cupertino/scaffold.dart b/packages/flutter/lib/src/cupertino/scaffold.dart new file mode 100644 index 0000000000000..621ee019d215c --- /dev/null +++ b/packages/flutter/lib/src/cupertino/scaffold.dart @@ -0,0 +1,229 @@ +// Copyright 2017 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/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'bottom_tab_bar.dart'; +import 'nav_bar.dart'; + +/// Implements a basic iOS application's layout and behavior structure. +/// +/// The scaffold lays out the navigation bar on top, the tab bar at the bottom +/// and tabbed or untabbed content between or behind the bars. +/// +/// For tabbed scaffolds, the tab's active item and the actively showing tab +/// in the content area are automatically connected. +// TODO(xster): describe navigator handlings. +// TODO(xster): add an example. +class CupertinoScaffold extends StatefulWidget { + /// Construct a [CupertinoScaffold] without tabs. + /// + /// The [tabBar] and [rootTabPageBuilder] fields are not used in a [CupertinoScaffold] + /// without tabs. + // TODO(xster): document that page transitions will happen behind the navigation + // bar. + const CupertinoScaffold({ + Key key, + this.navigationBar, + @required this.child, + }) : assert(child != null), + tabBar = null, + rootTabPageBuilder = null, + super(key: key); + + /// Construct a [CupertinoScaffold] with tabs. + /// + /// A [tabBar] and a [rootTabPageBuilder] are required. The [CupertinoScaffold] + /// will automatically listen to the provided [CupertinoTabBar]'s tap callbacks + /// to change the active tab. + /// + /// Tabs' contents are built with the provided [rootTabPageBuilder] at the active + /// tab index. [rootTabPageBuilder] must be able to build the same number of + /// pages as the [tabBar.items.length]. Inactive tabs will be moved [Offstage] + /// and its animations disabled. + /// + /// The [child] field is not used in a [CupertinoScaffold] with tabs. + const CupertinoScaffold.tabbed({ + Key key, + this.navigationBar, + @required this.tabBar, + @required this.rootTabPageBuilder, + }) : assert(tabBar != null), + assert(rootTabPageBuilder != null), + child = null, + super(key: key); + + /// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the + /// top of the screen. + /// + /// If translucent, the main content may slide behind it. + /// Otherwise, the main content's top margin will be offset by its height. + // TODO(xster): document its page transition animation when ready + final PreferredSizeWidget navigationBar; + + /// The [tabBar] is a [CupertinoTabBar] drawn at the bottom of the screen + /// that lets the user switch between different tabs in the main content area + /// when present. + /// + /// This parameter is required and must be non-null when the [new CupertinoScaffold.tabbed] + /// constructor is used. + /// + /// When provided, [CupertinoTabBar.currentIndex] will be ignored and will + /// be managed by the [CupertinoScaffold] to show the currently selected page + /// as the active item index. If [CupertinoTabBar.onTap] is provided, it will + /// still be called. [CupertinoScaffold] automatically also listen to the + /// [CupertinoTabBar]'s `onTap` to change the [CupertinoTabBar]'s `currentIndex` + /// and change the actively displayed tab in [CupertinoScaffold]'s own + /// main content area. + /// + /// If translucent, the main content may slide behind it. + /// Otherwise, the main content's bottom margin will be offset by its height. + final CupertinoTabBar tabBar; + + /// An [IndexedWidgetBuilder] that's called when tabs become active. + /// + /// Used when a tabbed scaffold is constructed via the [new CupertinoScaffold.tabbed] + /// constructor and must be non-null. + /// + /// When the tab becomes inactive, its content is still cached in the widget + /// tree [Offstage] and its animations disabled. + /// + /// Content can slide under the [navigationBar] or the [tabBar] when they're + /// translucent. + final IndexedWidgetBuilder rootTabPageBuilder; + + /// Widget to show in the main content area when the scaffold is used without + /// tabs. + /// + /// Used when the default [new CupertinoScaffold] constructor is used and must + /// be non-null. + /// + /// Content can slide under the [navigationBar] or the [tabBar] when they're + /// translucent. + final Widget child; + + @override + _CupertinoScaffoldState createState() => new _CupertinoScaffoldState(); +} + +class _CupertinoScaffoldState extends State { + int _currentPage = 0; + + /// Pad the given middle widget with or without top and bottom offsets depending + /// on whether the middle widget should slide behind translucent bars. + Widget _padMiddle(Widget middle) { + double topPadding = MediaQuery.of(context).padding.top; + if (widget.navigationBar is CupertinoNavigationBar) { + final CupertinoNavigationBar top = widget.navigationBar; + if (top.opaque) + topPadding += top.preferredSize.height; + } + + double bottomPadding = 0.0; + if (widget.tabBar?.opaque ?? false) + bottomPadding = widget.tabBar.preferredSize.height; + + return new Padding( + padding: new EdgeInsets.only(top: topPadding, bottom: bottomPadding), + child: middle, + ); + } + + @override + Widget build(BuildContext context) { + final List stacked = []; + + // The main content being at the bottom is added to the stack first. + if (widget.child != null) { + stacked.add(_padMiddle(widget.child)); + } else if (widget.rootTabPageBuilder != null) { + stacked.add(_padMiddle(new _TabView( + currentTabIndex: _currentPage, + tabNumber: widget.tabBar.items.length, + rootTabPageBuilder: widget.rootTabPageBuilder, + ))); + } + + if (widget.navigationBar != null) { + stacked.add(new Align( + alignment: FractionalOffset.topCenter, + child: widget.navigationBar, + )); + } + + if (widget.tabBar != null) { + stacked.add(new Align( + alignment: FractionalOffset.bottomCenter, + // Override the tab bar's currentIndex to the current tab and hook in + // our own listener to update the _currentPage on top of a possibly user + // provided callback. + child: widget.tabBar.copyWith( + currentIndex: _currentPage, + onTap: (int newIndex) { + setState(() { + _currentPage = newIndex; + }); + // Chain the user's original callback. + if (widget.tabBar.onTap != null) + widget.tabBar.onTap(newIndex); + } + ), + )); + } + + return new Stack( + children: stacked, + ); + } +} + +/// An widget laying out multiple tabs with only one active tab being built +/// at a time and on stage. Off stage tabs' animations are stopped. +class _TabView extends StatefulWidget { + _TabView({ + @required this.currentTabIndex, + @required this.tabNumber, + @required this.rootTabPageBuilder, + }) : assert(currentTabIndex != null), + assert(tabNumber != null && tabNumber > 0), + assert(rootTabPageBuilder != null); + + final int currentTabIndex; + final int tabNumber; + final IndexedWidgetBuilder rootTabPageBuilder; + + @override + _TabViewState createState() => new _TabViewState(); +} + +class _TabViewState extends State<_TabView> { + List tabs; + + @override + void initState() { + super.initState(); + tabs = new List(widget.tabNumber); + } + + @override + Widget build(BuildContext context) { + return new Stack( + children: new List.generate(widget.tabNumber, (int index) { + final bool active = index == widget.currentTabIndex; + + // TODO(xster): lazily replace empty tabs with Navigators instead. + if (active || tabs[index] != null) + tabs[index] = widget.rootTabPageBuilder(context, index); + + return new Offstage( + offstage: !active, + child: new TickerMode( + enabled: active, + child: tabs[index] ?? new Container(), + ), + ); + }), + ); + } +} diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 9f0ed934312f4..e141999d5bd22 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -1914,12 +1914,13 @@ class ListBody extends MultiChildRenderObjectWidget { /// placed relative to the stack according to their top, right, bottom, and left /// properties. /// -/// The stack paints its children in order. If you want to change the order in -/// which the children paint, you can rebuild the stack with the children in -/// the new order. If you reorder the children in this way, consider giving the -/// children non-null keys. These keys will cause the framework to move the -/// underlying objects for the children to their new locations rather than -/// recreate them at their new location. +/// The stack paints its children in order with the first child being at the +/// bottom. If you want to change the order in which the children paint, you +/// can rebuild the stack with the children in the new order. If you reorder +/// the children in this way, consider giving the children non-null keys. +/// These keys will cause the framework to move the underlying objects for +/// the children to their new locations rather than recreate them at their +/// new location. /// /// For more details about the stack layout algorithm, see [RenderStack]. /// diff --git a/packages/flutter/lib/src/widgets/navigation_toolbar.dart b/packages/flutter/lib/src/widgets/navigation_toolbar.dart index a03497c76b890..37391f6c3d5f2 100644 --- a/packages/flutter/lib/src/widgets/navigation_toolbar.dart +++ b/packages/flutter/lib/src/widgets/navigation_toolbar.dart @@ -83,7 +83,7 @@ class _ToolbarLayout extends MultiChildLayoutDelegate { // If false the middle widget should be left justified within the space // between the leading and trailing widgets. // If true the middle widget is centered within the toolbar (not within the horizontal - // space bewteen the leading and trailing widgets). + // space between the leading and trailing widgets). // TODO(xster): document RTL once supported. final bool centerMiddle; diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart new file mode 100644 index 0000000000000..40514df5ffbae --- /dev/null +++ b/packages/flutter/test/cupertino/scaffold_test.dart @@ -0,0 +1,203 @@ +// Copyright 2017 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../rendering/rendering_tester.dart'; +import '../services/mocks_for_image_cache.dart'; + +List selectedTabs; + +void main() { + setUp(() { + selectedTabs = []; + }); + + testWidgets('Contents are behind translucent bar', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + // TODO(xster): change to a CupertinoPageRoute. + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return const CupertinoScaffold( + // Default nav bar is translucent. + navigationBar: const CupertinoNavigationBar( + middle: const Text('Title'), + ), + child: const Center(), + ); + }, + ); + }, + ), + ); + + expect(tester.getTopLeft(find.byType(Center)), const Offset(0.0, 0.0)); + }); + + testWidgets('Contents are between opaque bars', (WidgetTester tester) async { + final Center page1Center = const Center(); + + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + // TODO(xster): change to a CupertinoPageRoute. + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return new CupertinoScaffold.tabbed( + navigationBar: const CupertinoNavigationBar( + backgroundColor: CupertinoColors.white, + middle: const Text('Title'), + ), + tabBar: _buildTabBar(), + rootTabPageBuilder: (BuildContext context, int index) { + return index == 0 ? page1Center : new Stack(); + } + ); + }, + ); + }, + ), + ); + + expect(tester.getSize(find.byWidget(page1Center)).height, 600.0 - 44.0 - 50.0); + }); + + testWidgets('Tab switching', (WidgetTester tester) async { + final List tabsPainted = []; + + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + // TODO(xster): change to a CupertinoPageRoute. + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return new CupertinoScaffold.tabbed( + navigationBar: const CupertinoNavigationBar( + backgroundColor: CupertinoColors.white, + middle: const Text('Title'), + ), + tabBar: _buildTabBar(), + rootTabPageBuilder: (BuildContext context, int index) { + return new CustomPaint( + child: new Text('Page ${index + 1}'), + painter: new TestCallbackPainter( + onPaint: () { tabsPainted.add(index); } + ) + ); + } + ); + }, + ); + }, + ), + ); + + expect(tabsPainted, [0]); + RichText tab1 = tester.widget(find.descendant( + of: find.text('Tab 1'), + matching: find.byType(RichText), + )); + expect(tab1.text.style.color, CupertinoColors.activeBlue); + RichText tab2 = tester.widget(find.descendant( + of: find.text('Tab 2'), + matching: find.byType(RichText), + )); + expect(tab2.text.style.color, CupertinoColors.inactiveGray); + + await tester.tap(find.text('Tab 2')); + await tester.pump(); + + expect(tabsPainted, [0, 1]); + tab1 = tester.widget(find.descendant( + of: find.text('Tab 1'), + matching: find.byType(RichText), + )); + expect(tab1.text.style.color, CupertinoColors.inactiveGray); + tab2 = tester.widget(find.descendant( + of: find.text('Tab 2'), + matching: find.byType(RichText), + )); + expect(tab2.text.style.color, CupertinoColors.activeBlue); + + await tester.tap(find.text('Tab 1')); + await tester.pump(); + + expect(tabsPainted, [0, 1, 0]); + }); + + testWidgets('Tabs are lazy built and moved offstage when inactive', (WidgetTester tester) async { + final List tabsBuilt = []; + + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + // TODO(xster): change to a CupertinoPageRoute. + return new PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { + return new CupertinoScaffold.tabbed( + navigationBar: const CupertinoNavigationBar( + backgroundColor: CupertinoColors.white, + middle: const Text('Title'), + ), + tabBar: _buildTabBar(), + rootTabPageBuilder: (BuildContext context, int index) { + tabsBuilt.add(index); + return new Text('Page ${index + 1}'); + } + ); + }, + ); + }, + ), + ); + + expect(tabsBuilt, [0]); + expect(find.text('Page 1'), findsOneWidget); + expect(find.text('Page 2'), findsNothing); + + await tester.tap(find.text('Tab 2')); + await tester.pump(); + + // Both tabs are built but only one is onstage. + expect(tabsBuilt, [0, 0, 1]); + expect(find.text('Page 1', skipOffstage: false), isOffstage); + expect(find.text('Page 2'), findsOneWidget); + + await tester.tap(find.text('Tab 1')); + await tester.pump(); + + expect(tabsBuilt, [0, 0, 1, 0, 1]); + expect(find.text('Page 1'), findsOneWidget); + expect(find.text('Page 2', skipOffstage: false), isOffstage); + }); +} + +CupertinoTabBar _buildTabBar() { + return new CupertinoTabBar( + items: [ + const BottomNavigationBarItem( + icon: const ImageIcon(const TestImageProvider(24, 24)), + title: const Text('Tab 1'), + ), + const BottomNavigationBarItem( + icon: const ImageIcon(const TestImageProvider(24, 24)), + title: const Text('Tab 2'), + ), + ], + backgroundColor: CupertinoColors.white, + onTap: (int newTab) => selectedTabs.add(newTab), + ); +} From 272faba1c0ae2a7513bd020865f083adf83025ed Mon Sep 17 00:00:00 2001 From: Alexander Aprelev Date: Tue, 13 Jun 2017 08:28:16 -0700 Subject: [PATCH 086/110] Support 'be' channel so you can download dart-sdk from the tip. (#10625) * Support 'be' channel so you can download dart-sdk from the tip. This is triggered when you put 'hash/' into dart-sdk.version file. For example, 'hash/c0617d20158955d99d6447036237fe2639ba088c' * Add README.md * Fix grammar, spacing, 80 chars, nicer === --- bin/internal/README.md | 10 ++++++++++ bin/internal/update_dart_sdk.ps1 | 2 +- bin/internal/update_dart_sdk.sh | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 bin/internal/README.md diff --git a/bin/internal/README.md b/bin/internal/README.md new file mode 100644 index 0000000000000..e051a56be09da --- /dev/null +++ b/bin/internal/README.md @@ -0,0 +1,10 @@ +Dart SDK dependency +=================== + +The Dart SDK is downloaded from one of [the supported channels](https://www.dartlang.org/install/archive), +cached in `bin/cache/dart-sdk` and is used to run Flutter Dart code. + +The file `bin/internal/dart-sdk.version` determines the version of Dart SDK +that will be downloaded. Normally it points to the `dev` channel (for example, +`1.24.0-dev.6.7`), but it can also point to particular bleeding edge build +of Dart (for example, `hash/c0617d20158955d99d6447036237fe2639ba088c`). \ No newline at end of file diff --git a/bin/internal/update_dart_sdk.ps1 b/bin/internal/update_dart_sdk.ps1 index 8767d1d98f372..1f16cb6ebb685 100644 --- a/bin/internal/update_dart_sdk.ps1 +++ b/bin/internal/update_dart_sdk.ps1 @@ -29,7 +29,7 @@ if ((Test-Path $dartSdkStampPath) -and ($dartSdkVersion -eq (Get-Content $dartSd Write-Host "Downloading Dart SDK $dartSdkVersion..." $dartZipName = "dartsdk-windows-x64-release.zip" -$dartChannel = if ($dartSdkVersion.Contains("-dev.")) {"dev"} else {"stable"} +$dartChannel = if ($dartSdkVersion.Contains("-dev.")) {"dev"} else {if ($dartSdkVersion.Contains("hash/")) {"be"} else {"stable"}} $dartSdkUrl = "https://storage.googleapis.com/dart-archive/channels/$dartChannel/raw/$dartSdkVersion/sdk/$dartZipName" if (Test-Path $dartSdkPath) { diff --git a/bin/internal/update_dart_sdk.sh b/bin/internal/update_dart_sdk.sh index cbff31ca74bf7..80e8f6e4ed701 100755 --- a/bin/internal/update_dart_sdk.sh +++ b/bin/internal/update_dart_sdk.sh @@ -41,6 +41,9 @@ if [ ! -f "$DART_SDK_STAMP_PATH" ] || [ "$DART_SDK_VERSION" != `cat "$DART_SDK_S if [[ $DART_SDK_VERSION == *"-dev."* ]] then DART_CHANNEL="dev" + elif [[ $DART_SDK_VERSION == "hash/"* ]] + then + DART_CHANNEL="be" fi DART_SDK_URL="https://storage.googleapis.com/dart-archive/channels/$DART_CHANNEL/raw/$DART_SDK_VERSION/sdk/$DART_ZIP_NAME" From 1f4f75bb503a8fd844fea382fb4b7c5ad0ce99dd Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Tue, 13 Jun 2017 09:52:13 -0700 Subject: [PATCH 087/110] More documentation. (#10653) --- dev/bots/analyze-sample-code.dart | 4 +- .../flutter/lib/src/animation/animations.dart | 9 +- .../flutter/lib/src/material/dropdown.dart | 6 + .../lib/src/painting/fractional_offset.dart | 6 + .../flutter/lib/src/painting/text_span.dart | 4 +- .../flutter/lib/src/painting/text_style.dart | 145 +++++++++++++++++- packages/flutter/lib/src/rendering/box.dart | 82 ++++++++-- .../flutter/lib/src/rendering/proxy_box.dart | 10 +- .../lib/src/widgets/animated_cross_fade.dart | 18 ++- packages/flutter/lib/src/widgets/basic.dart | 27 +++- .../flutter/lib/src/widgets/container.dart | 44 ++++++ .../lib/src/widgets/gesture_detector.dart | 17 ++ .../widgets/size_changed_layout_notifier.dart | 38 +++-- 13 files changed, 358 insertions(+), 52 deletions(-) diff --git a/dev/bots/analyze-sample-code.dart b/dev/bots/analyze-sample-code.dart index 7c63073d4ebbb..7aff93c54a730 100644 --- a/dev/bots/analyze-sample-code.dart +++ b/dev/bots/analyze-sample-code.dart @@ -90,7 +90,7 @@ Future main() async { block.add(line.substring(3)); } } else if (inSampleSection) { - if (!trimmedLine.startsWith(kDartDocPrefix) || trimmedLine.startsWith('/// ##')) { + if (!trimmedLine.startsWith(kDartDocPrefix) || trimmedLine.startsWith('/// ## ')) { if (inDart) throw '${file.path}:$lineNumber: Dart section inexplicably unterminated.'; if (!foundDart) @@ -160,6 +160,8 @@ dependencies: ); stderr.addStream(process.stderr); final List errors = await process.stdout.transform(UTF8.decoder).transform(const LineSplitter()).toList(); + if (errors.first == 'Building flutter tool...') + errors.removeAt(0); if (errors.first.startsWith('Running "flutter packages get" in ')) errors.removeAt(0); if (errors.first.startsWith('Analyzing ')) diff --git a/packages/flutter/lib/src/animation/animations.dart b/packages/flutter/lib/src/animation/animations.dart index b763b792cdc6a..82e104a977e60 100644 --- a/packages/flutter/lib/src/animation/animations.dart +++ b/packages/flutter/lib/src/animation/animations.dart @@ -237,10 +237,11 @@ class ProxyAnimation extends Animation /// An animation that is the reverse of another animation. /// /// If the parent animation is running forward from 0.0 to 1.0, this animation -/// is running in reverse from 1.0 to 0.0. Notice that using a ReverseAnimation -/// is different from simply using a [Tween] with a begin of 1.0 and an end of -/// 0.0 because the tween does not change the status or direction of the -/// animation. +/// is running in reverse from 1.0 to 0.0. +/// +/// Using a [ReverseAnimation] is different from simply using a [Tween] with a +/// begin of 1.0 and an end of 0.0 because the tween does not change the status +/// or direction of the animation. class ReverseAnimation extends Animation with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin { diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index b0ff98253e949..a4a22b545539e 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -398,10 +398,16 @@ class DropdownButtonHideUnderline extends InheritedWidget { /// shows the currently selected item as well as an arrow that opens a menu for /// selecting another item. /// +/// The type `T` is the type of the values the dropdown menu represents. All the +/// entries in a given menu must represent values with consistent types. +/// Typically, an enum is used. Each [DropdownMenuItem] in [items] must be +/// specialized with that same type argument. +/// /// Requires one of its ancestors to be a [Material] widget. /// /// See also: /// +/// * [DropdownMenuItem], the class used to represent the [items]. /// * [DropdownButtonHideUnderline], which prevents its descendant dropdown buttons /// from displaying their underlines. /// * [RaisedButton], [FlatButton], ordinary buttons that trigger a single action. diff --git a/packages/flutter/lib/src/painting/fractional_offset.dart b/packages/flutter/lib/src/painting/fractional_offset.dart index e0b0c725e23e1..90b4549986a02 100644 --- a/packages/flutter/lib/src/painting/fractional_offset.dart +++ b/packages/flutter/lib/src/painting/fractional_offset.dart @@ -16,6 +16,12 @@ import 'basic_types.dart'; /// /// `FractionalOffset(0.5, 2.0)` represents a point half way across the [Size], /// below the bottom of the rectangle by the height of the [Size]. +/// +/// A variety of widgets use [FractionalOffset] in their configuration, most +/// notably: +/// +/// * [Align] positions a child according to a [FractionalOffset]. +/// * [FractionalTranslation] moves a child according to a [FractionalOffset]. @immutable class FractionalOffset { /// Creates a fractional offset. diff --git a/packages/flutter/lib/src/painting/text_span.dart b/packages/flutter/lib/src/painting/text_span.dart index 0a8258fe90743..a7f068d7d809e 100644 --- a/packages/flutter/lib/src/painting/text_span.dart +++ b/packages/flutter/lib/src/painting/text_span.dart @@ -98,7 +98,9 @@ class TextSpan { /// object that manages the [TextSpan] painting is also responsible for /// dispatching events. In the rendering library, that is the /// [RenderParagraph] object, which corresponds to the [RichText] widget in - /// the widgets layer. + /// the widgets layer; these objects do not bubble events in [TextSpan]s, so a + /// [recognizer] is only effective for events that directly hit the [text] of + /// that [TextSpan], not any of its [children]. /// /// [TextSpan] also does not manage the lifetime of the gesture recognizer. /// The code that owns the [GestureRecognizer] object must call diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index d56f82b3cfebf..c7595b8fc1552 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -9,6 +9,127 @@ import 'package:flutter/foundation.dart'; import 'basic_types.dart'; /// An immutable style in which paint text. +/// +/// ## Sample code +/// +/// ### Bold +/// +/// Here, a single line of text in a [Text] widget is given a specific style +/// override. The style is mixed with the ambient [DefaultTextStyle] by the +/// [Text] widget. +/// +/// ```dart +/// new Text( +/// 'No, we need bold strokes. We need this plan.', +/// style: new TextStyle(fontWeight: FontWeight.bold), +/// ) +/// ``` +/// +/// ### Italics +/// +/// As in the previous example, the [Text] widget is given a specific style +/// override which is implicitly mixed with the ambient [DefaultTextStyle]. +/// +/// ```dart +/// new Text( +/// 'Welcome to the present, we\'re running a real nation.', +/// style: new TextStyle(fontStyle: FontStyle.italic), +/// ) +/// ``` +/// +/// ### Opacity +/// +/// Each line here is progressively more opaque. The base color is +/// [Colors.black], and [Color.withOpacity] is used to create a derivative color +/// with the desired opacity. The root [TextSpan] for this [RichText] widget is +/// explicitly given the ambient [DefaultTextStyle], since [RichText] does not +/// do that automatically. The inner [TextStyle] objects are implicitly mixed +/// with the parent [TextSpan]'s [TextSpan.style]. +/// +/// ```dart +/// new RichText( +/// text: new TextSpan( +/// style: DefaultTextStyle.of(context).style, +/// children: [ +/// new TextSpan( +/// text: 'You don\'t have the votes.\n', +/// style: new TextStyle(color: Colors.black.withOpacity(0.6)), +/// ), +/// new TextSpan( +/// text: 'You don\'t have the votes!\n', +/// style: new TextStyle(color: Colors.black.withOpacity(0.8)), +/// ), +/// new TextSpan( +/// text: 'You\'re gonna need congressional approval and you don\'t have the votes!\n', +/// style: new TextStyle(color: Colors.black.withOpacity(1.0)), +/// ), +/// ], +/// ), +/// ) +/// ``` +/// +/// ### Size +/// +/// In this example, the ambient [DefaultTextStyle] is explicitly manipulated to +/// obtain a [TextStyle] that doubles the default font size. +/// +/// ```dart +/// new Text( +/// 'These are wise words, enterprising men quote \'em.', +/// style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0), +/// ) +/// ``` +/// +/// ### Line height +/// +/// The [height] property can be used to change the line height. Here, the line +/// height is set to 100 logical pixels, so that the text is very spaced out. +/// +/// ```dart +/// new Text( +/// 'Don\'t act surprised, you guys, cuz I wrote \'em!', +/// style: new TextStyle(height: 100.0), +/// ) +/// ``` +/// +/// ### Wavy red underline with black text +/// +/// Styles can be combined. In this example, the misspelt word is drawn in black +/// text and underlined with a wavy red line to indicate a spelling error. (The +/// remainder is styled according to the Flutter default text styles, not the +/// ambient [DefaultTextStyle], since no explicit style is given and [RichText] +/// does not automatically use the ambient [DefaultTextStyle].) +/// +/// ```dart +/// new RichText( +/// text: new TextSpan( +/// text: 'Don\'t tax the South ', +/// children: [ +/// new TextSpan( +/// text: 'cuz', +/// style: new TextStyle( +/// color: Colors.black, +/// decoration: TextDecoration.underline, +/// decorationColor: Colors.red, +/// decorationStyle: TextDecorationStyle.wavy, +/// ), +/// ), +/// new TextSpan( +/// text: ' we got it made in the shade', +/// ), +/// ], +/// ), +/// ) +/// ``` +/// +/// See also: +/// +/// * [Text], the widget for showing text in a single style. +/// * [DefaultTextStyle], the widget that specifies the default text styles for +/// [Text] widgets, configured using a [TextStyle]. +/// * [RichText], the widget for showing a paragraph of mix-style text. +/// * [TextSpan], the class that wraps a [TextStyle] for the purposes of +/// passing it to a [RichText]. @immutable class TextStyle { /// Creates a text style. @@ -25,10 +146,15 @@ class TextStyle { this.height, this.decoration, this.decorationColor, - this.decorationStyle + this.decorationStyle, }) : assert(inherit != null); - /// Whether null values are replaced with their value in an ancestor text style (e.g., in a [TextSpan] tree). + /// Whether null values are replaced with their value in an ancestor text + /// style (e.g., in a [TextSpan] tree). + /// + /// If this is false, properties that don't have explicit values will revert + /// to the defaults: white in color, a font size of 10 pixels, in a sans-serif + /// font face. final bool inherit; /// The color to use when painting the text. @@ -54,11 +180,13 @@ class TextStyle { /// A negative value can be used to bring the letters closer. final double letterSpacing; - /// The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word). - /// A negative value can be used to bring the words closer. + /// The amount of space (in logical pixels) to add at each sequence of + /// white-space (i.e. between each word). A negative value can be used to + /// bring the words closer. final double wordSpacing; - /// The common baseline that should be aligned between this text span and its parent text span, or, for the root text spans, with the line box. + /// The common baseline that should be aligned between this text span and its + /// parent text span, or, for the root text spans, with the line box. final TextBaseline textBaseline; /// The height of this text span, as a multiple of the font size. @@ -77,7 +205,8 @@ class TextStyle { /// The style in which to paint the text decorations (e.g., dashed). final TextDecorationStyle decorationStyle; - /// Creates a copy of this text style but with the given fields replaced with the new values. + /// Creates a copy of this text style but with the given fields replaced with + /// the new values. TextStyle copyWith({ Color color, String fontFamily, @@ -90,7 +219,7 @@ class TextStyle { double height, TextDecoration decoration, Color decorationColor, - TextDecorationStyle decorationStyle + TextDecorationStyle decorationStyle, }) { return new TextStyle( inherit: inherit, @@ -105,7 +234,7 @@ class TextStyle { height: height ?? this.height, decoration: decoration ?? this.decoration, decorationColor: decorationColor ?? this.decorationColor, - decorationStyle: decorationStyle ?? this.decorationStyle + decorationStyle: decorationStyle ?? this.decorationStyle, ); } diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index f4b869ca8f738..82afc4d3b14e2 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -23,18 +23,63 @@ class _DebugSize extends Size { /// Immutable layout constraints for [RenderBox] layout. /// -/// A size respects a [BoxConstraints] if, and only if, all of the following +/// A [Size] respects a [BoxConstraints] if, and only if, all of the following /// relations hold: /// -/// * `minWidth <= size.width <= maxWidth` -/// * `minHeight <= size.height <= maxHeight` +/// * [minWidth] <= [Size.width] <= [maxWidth] +/// * [minHeight] <= [Size.height] <= [maxHeight] /// /// The constraints themselves must satisfy these relations: /// -/// * `0.0 <= minWidth <= maxWidth <= double.INFINITY` -/// * `0.0 <= minHeight <= maxHeight <= double.INFINITY` +/// * 0.0 <= [minWidth] <= [maxWidth] <= [double.INFINITY] +/// * 0.0 <= [minHeight] <= [maxHeight] <= [double.INFINITY] /// /// [double.INFINITY] is a legal value for each constraint. +/// +/// ## The box layout model +/// +/// Render objects in the Flutter framework are laid out by a one-pass layout +/// model which walks down the render tree passing constraints, then walks back +/// up the render tree passing concrete geometry. +/// +/// For boxes, the constraints are [BoxConstraints], which, as described herein, +/// consist of four numbers: a minimum width [minWidth], a maximum width +/// [maxWidth], a minimum height [minHeight], and a maximum height [maxHeight]. +/// +/// The geometry for boxes consists of a [Size], which must satisfy the +/// constraints described above. +/// +/// Each [RenderBox] (the objects that provide the layout models for box +/// widgets) receives [BoxConstraints] from its parent, then lays out each of +/// its children, then picks a [Size] that satisfies the [BoxConstraints]. +/// +/// Render objects position their children independently of laying them out. +/// Frequently, the parent will use the children's sizes to determine their +/// position. A child does not know its position and will not necessarily be +/// laid out again, or repainted, if its position changes. +/// +/// ## Terminology +/// +/// When the minimum constraints and the maximum constraint in an axis are the +/// same, that axis is _tightly_ constrained. See: [new +/// BoxConstraints.tightFor], [new BoxConstraints.tightForFinite], [tighten], +/// [hasTightWidth], [hasTightHeight], [isTight]. +/// +/// An axis with a minimum constraint of 0.0 is _loose_ (regardless of the +/// maximum constraint; if it is also 0.0, then the axis is simultaneously tight +/// and loose!). See: [new BoxConstraints.loose], [loosen]. +/// +/// An axis whose maximum constraint is not infinite is _bounded_. See: +/// [hasBoundedWidth], [hasBoundedHeight]. +/// +/// An axis whose maximum constraint is infinite is _unbounded_. An axis is +/// _expanding_ if it is tightly infinite (its minimum and maximum constraints +/// are both infinite). See: [new BoxConstraints.expand]. +/// +/// A size is _constrained_ when it satisfies a [BoxConstraints] description. +/// See: [constrain], [constrainWidth], [constrainHeight], +/// [constrainDimensions], [constrainSizeAndAttemptToPreserveAspectRatio], +/// [isSatisfiedBy]. class BoxConstraints extends Constraints { /// Creates box constraints with the given constraints. const BoxConstraints({ @@ -68,6 +113,12 @@ class BoxConstraints extends Constraints { maxHeight = size.height; /// Creates box constraints that require the given width or height. + /// + /// See also: + /// + /// * [new BoxConstraints.tightForFinite], which is similar but instead of + /// being tight if the value is non-null, is tight if the value is not + /// infinite. const BoxConstraints.tightFor({ double width, double height @@ -76,7 +127,13 @@ class BoxConstraints extends Constraints { minHeight = height != null ? height : 0.0, maxHeight = height != null ? height : double.INFINITY; - /// Creates box constraints that require the given width or height, except if they are infinite. + /// Creates box constraints that require the given width or height, except if + /// they are infinite. + /// + /// See also: + /// + /// * [new BoxConstraints.tightFor], which is similar but instead of being + /// tight if the value is not infinite, is tight if the value is non-null. const BoxConstraints.tightForFinite({ double width: double.INFINITY, double height: double.INFINITY @@ -230,10 +287,10 @@ class BoxConstraints extends Constraints { /// Returns a size that attempts to meet the following conditions, in order: /// - /// - The size must satisfy these constraints. - /// - The aspect ratio of the returned size matches the aspect ratio of the + /// * The size must satisfy these constraints. + /// * The aspect ratio of the returned size matches the aspect ratio of the /// given size. - /// - The returned size as big as possible while still being equal to or + /// * The returned size as big as possible while still being equal to or /// smaller than the given size. Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) { if (isTight) { @@ -1698,9 +1755,10 @@ abstract class RenderBox extends RenderObject { /// Determines the set of render objects located at the given position. /// - /// Returns true if the given point is contained in this render object or one - /// of its descendants. Adds any render objects that contain the point to the - /// given hit test result. + /// Returns true, and adds any render objects that contain the point to the + /// given hit test result, if this render object or one of its descendants + /// absorbs the hit (preventing objects below this one from being hit). + /// Returns false if the hit can continue to other objects below this one. /// /// The caller is responsible for transforming [position] into the local /// coordinate space of the callee. The callee is responsible for checking diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 08f0db9477513..06872dc648a3f 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -544,7 +544,10 @@ class RenderAspectRatio extends RenderProxyBox { /// you would like a child that would otherwise attempt to expand infinitely to /// instead size itself to a more reasonable width. /// -/// This class is relatively expensive. Avoid using it where possible. +/// This class is relatively expensive, because it adds a speculative layout +/// pass before the final layout phase. Avoid using it where possible. In the +/// worst case, this render object can result in a layout that is O(N²) in the +/// depth of the tree. class RenderIntrinsicWidth extends RenderProxyBox { /// Creates a render object that sizes itself to its child's intrinsic width. RenderIntrinsicWidth({ @@ -650,7 +653,10 @@ class RenderIntrinsicWidth extends RenderProxyBox { /// you would like a child that would otherwise attempt to expand infinitely to /// instead size itself to a more reasonable height. /// -/// This class is relatively expensive. Avoid using it where possible. +/// This class is relatively expensive, because it adds a speculative layout +/// pass before the final layout phase. Avoid using it where possible. In the +/// worst case, this render object can result in a layout that is O(N²) in the +/// depth of the tree. class RenderIntrinsicHeight extends RenderProxyBox { /// Creates a render object that sizes itself to its child's intrinsic height. RenderIntrinsicHeight({ diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart index 21a7799410630..b64447099f179 100644 --- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart +++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart @@ -29,21 +29,27 @@ enum CrossFadeState { /// /// The animation is controlled through the [crossFadeState] parameter. /// [firstCurve] and [secondCurve] represent the opacity curves of the two -/// children. Note that [firstCurve] is inverted, i.e. it fades out when -/// providing a growing curve like [Curves.linear]. [sizeCurve] is the curve -/// used to animated between the size of the fading out child and the size of -/// the fading in child. +/// children. The [firstCurve] is inverted, i.e. it fades out when providing a +/// growing curve like [Curves.linear]. The [sizeCurve] is the curve used to +/// animated between the size of the fading out child and the size of the fading +/// in child. /// /// This widget is intended to be used to fade a pair of widgets with the same /// width. In the case where the two children have different heights, the /// animation crops overflowing children during the animation by aligning their /// top edge, which means that the bottom will be clipped. /// +/// The animation is automatically triggered when an existing +/// [AnimatedCrossFade] is rebuilt with a different value for the +/// [crossFadeState] property. +/// /// ## Sample code /// /// This code fades between two representations of the Flutter logo. It depends -/// on a global boolean `_on`; when `_on` is true, the first logo is shown, -/// otherwise the second logo is shown. +/// on a boolean field `_on`; when `_on` is true, the first logo is shown, +/// otherwise the second logo is shown. When the field changes state, the +/// [AnimatedCrossFade] widget cross-fades between the two forms of the logo +/// over three seconds. /// /// ```dart /// new AnimatedCrossFade( diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index e141999d5bd22..f492c84a13902 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -330,7 +330,7 @@ class CustomPaint extends SingleChildRenderObjectWidget { /// custom [clipper]. /// /// [ClipRect] is commonly used with these widgets, which commonly paint outside -/// their bounds. +/// their bounds: /// /// * [CustomPaint] /// * [CustomSingleChildLayout] @@ -342,8 +342,8 @@ class CustomPaint extends SingleChildRenderObjectWidget { /// /// ## Sample code /// -/// For example, use a clip to show the top half of an [Image], you can use a -/// [ClipRect] combined with an [Align]: +/// For example, by combining a [ClipRect] with an [Align], one can show just +/// the top half of an [Image]: /// /// ```dart /// new ClipRect( @@ -1693,7 +1693,10 @@ class AspectRatio extends SingleChildRenderObjectWidget { /// you would like a child that would otherwise attempt to expand infinitely to /// instead size itself to a more reasonable width. /// -/// This class is relatively expensive. Avoid using it where possible. +/// This class is relatively expensive, because it adds a speculative layout +/// pass before the final layout phase. Avoid using it where possible. In the +/// worst case, this widget can result in a layout that is O(N²) in the depth of +/// the tree. class IntrinsicWidth extends SingleChildRenderObjectWidget { /// Creates a widget that sizes its child to the child's intrinsic width. /// @@ -1724,7 +1727,10 @@ class IntrinsicWidth extends SingleChildRenderObjectWidget { /// you would like a child that would otherwise attempt to expand infinitely to /// instead size itself to a more reasonable height. /// -/// This class is relatively expensive. Avoid using it where possible. +/// This class is relatively expensive, because it adds a speculative layout +/// pass before the final layout phase. Avoid using it where possible. In the +/// worst case, this widget can result in a layout that is O(N²) in the depth of +/// the tree. class IntrinsicHeight extends SingleChildRenderObjectWidget { /// Creates a widget that sizes its child to the child's intrinsic height. /// @@ -2236,6 +2242,7 @@ class Positioned extends ParentDataWidget { /// ## Layout algorithm /// /// _This section describes how a [Flex] is rendered by the framework._ +/// _See [BoxConstraints] for an introduction to box layout models._ /// /// Layout for a [Flex] proceeds in six steps: /// @@ -2459,6 +2466,7 @@ class Flex extends MultiChildRenderObjectWidget { /// ## Layout algorithm /// /// _This section describes how a [Row] is rendered by the framework._ +/// _See [BoxConstraints] for an introduction to box layout models._ /// /// Layout for a [Row] proceeds in six steps: /// @@ -2583,6 +2591,7 @@ class Row extends Flex { /// ## Layout algorithm /// /// _This section describes how a [Column] is rendered by the framework._ +/// _See [BoxConstraints] for an introduction to box layout models._ /// /// Layout for a [Column] proceeds in six steps: /// @@ -3000,7 +3009,8 @@ class Flow extends MultiChildRenderObjectWidget { /// /// Text displayed in a [RichText] widget must be explicitly styled. When /// picking which style to use, consider using [DefaultTextStyle.of] the current -/// [BuildContext] to provide defaults. +/// [BuildContext] to provide defaults. For more details on how to style text in +/// a [RichText] widget, see the documentation for [TextStyle]. /// /// When all the text uses the same style, consider using the [Text] widget, /// which is less verbose and integrates with [DefaultTextStyle] for default @@ -3023,6 +3033,7 @@ class Flow extends MultiChildRenderObjectWidget { /// /// See also: /// +/// * [TextStyle], which discusses how to style text. /// * [TextSpan], which is used to describe the text in a paragraph. /// * [Text], which automatically applies the ambient styles described by a /// [DefaultTextStyle] to a single string. @@ -3323,6 +3334,10 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget { /// Rather than listening for raw pointer events, consider listening for /// higher-level gestures using [GestureDetector]. /// +/// ## Layout behavior +/// +/// _See [BoxConstraints] for an introduction to box layout models._ +/// /// If it has a child, this widget defers to the child for sizing behavior. If /// it does not have a child, it grows to fit the parent instead. class Listener extends SingleChildRenderObjectWidget { diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart index 98c0eec85fd5a..89bf1409b42ec 100644 --- a/packages/flutter/lib/src/widgets/container.dart +++ b/packages/flutter/lib/src/widgets/container.dart @@ -124,6 +124,50 @@ class DecoratedBox extends SingleChildRenderObjectWidget { /// `width`, `height`, and [constraints] arguments to the constructor override /// this. /// +/// ## Layout behavior +/// +/// _See [BoxConstraints] for an introduction to box layout models._ +/// +/// Since [Container] combines a number of other widgets each with their own +/// layout behavior, [Container]'s layout behaviour is somewhat complicated. +/// +/// tl;dr: [Container] tries, in order: to honor [alignment], to size itself to +/// the [child], to honor the `width`, `height`, and [constraints], to expand to +/// fit the parent, to be as small as possible. +/// +/// More specifically: +/// +/// If the widget has no child, no `height`, no `width`, no [constraints], +/// and the parent provides unbounded constraints, then [Container] tries to +/// size as small as possible. +/// +/// If the widget has no child and no [alignment], but a `height`, `width`, or +/// [constraints] are provided, then the [Container] tries to be as small as +/// possible given the combination of those constraints and the parent's +/// constraints. +/// +/// If the widget has no child, no `height`, no `width`, no [constraints], and +/// no [alignment], but the parent provides bounded constraints, then +/// [Container] expands to fit the constraints provided by the parent. +/// +/// If the widget has an [alignment], and the parent provides unbounded +/// constraints, then the [Container] tries to size itself around the child. +/// +/// If the widget has an [alignment], and the parent provides bounded +/// constraints, then the [Container] tries to expand to fit the parent, and +/// then positions the child within itself as per the [alignment]. +/// +/// Otherwise, the widget has a [child] but no `height`, no `width`, no +/// [constraints], and no [alignment], and the [Container] passes the +/// constraints from the parent to the child and sizes itself to match the +/// child. +/// +/// The [margin] and [padding] properties also affect the layout, as described +/// in the documentation for those properties. (Their effects merely augment the +/// rules described above.) The [decoration] can implicitly increase the +/// [padding] (e.g. borders in a [BoxDecoration] contribute to the [padding]); +/// see [Decoration.padding]. +/// /// ## Sample code /// /// This example shows a 48x48 green square (placed inside a [Center] widget in diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index b6d9ed9f40f02..5c681f35a2015 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -104,6 +104,23 @@ class GestureRecognizerFactoryWithHandlers extends /// Material design applications typically react to touches with ink splash /// effects. The [InkWell] class implements this effect and can be used in place /// of a [GestureDetector] for handling taps. +/// +/// ## Sample code +/// +/// This example makes a rectangle react to being tapped by setting the +/// `_lights` field: +/// +/// ```dart +/// new GestureDetector( +/// onTap: () { +/// setState(() { _lights = true; }); +/// }, +/// child: new Container( +/// color: Colors.yellow, +/// child: new Text('TURN LIGHTS ON'), +/// ), +/// ) +/// ``` class GestureDetector extends StatelessWidget { /// Creates a widget that detects gestures. /// diff --git a/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart b/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart index bc26360d8600b..32c3bbd75de4f 100644 --- a/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart +++ b/packages/flutter/lib/src/widgets/size_changed_layout_notifier.dart @@ -10,15 +10,15 @@ import 'package:flutter/widgets.dart'; /// this notification has changed, and that therefore any assumptions about that /// layout are no longer valid. /// -/// For example, sent by [SizeChangedLayoutNotifier] whenever -/// [SizeChangedLayoutNotifier] changes size. +/// For example, sent by the [SizeChangedLayoutNotifier] widget whenever that +/// widget changes size. /// -/// This notification for triggering repaints, but if you use this notification -/// to trigger rebuilds or relayouts, you'll create a backwards dependency in -/// the frame pipeline because [SizeChangedLayoutNotification]s are generated -/// during layout, which is after the build phase and in the middle of the -/// layout phase. This backwards dependency can lead to visual corruption or -/// lags. +/// This notification can be used for triggering repaints, but if you use this +/// notification to trigger rebuilds or relayouts, you'll create a backwards +/// dependency in the frame pipeline because [SizeChangedLayoutNotification]s +/// are generated during layout, which is after the build phase and in the +/// middle of the layout phase. This backwards dependency can lead to visual +/// corruption or lags. /// /// See [LayoutChangedNotification] for additional discussion of layout /// notifications such as this one. @@ -26,16 +26,28 @@ import 'package:flutter/widgets.dart'; /// See also: /// /// * [SizeChangedLayoutNotifier], which sends this notification. +/// * [LayoutChangedNotification], of which this is a subclass. class SizeChangedLayoutNotification extends LayoutChangedNotification { } /// A widget that automatically dispatches a [SizeChangedLayoutNotification] -/// when the layout of its child changes. -/// -/// Useful especially when having some complex, layout-changing animation within -/// [Material] that is also interactive. +/// when the layout dimensions of its child change. /// /// The notification is not sent for the initial layout (since the size doesn't /// change in that case, it's just established). +/// +/// To listen for the notification dispatched by this widget, use a +/// [NotificationListener]. +/// +/// The [Material] class listens for [LayoutChangedNotification]s, including +/// [SizeChangedLayoutNotification]s, to repaint [InkResponse] and [InkWell] ink +/// effects. When a widget is likely to change size, wrapping it in a +/// [SizeChangedLayoutNotifier] will cause the ink effects to correctly repaint +/// when the child changes size. +/// +/// See also: +/// +/// * [Notification], the base class for notifications that bubble through the +/// widget tree. class SizeChangedLayoutNotifier extends SingleChildRenderObjectWidget { /// Creates a [SizeChangedLayoutNotifier] that dispatches layout changed /// notifications when [child] changes layout size. @@ -73,6 +85,8 @@ class _RenderSizeChangedWithCallback extends RenderProxyBox { @override void performLayout() { super.performLayout(); + // Don't send the initial notification, or this will be SizeObserver all + // over again! if (_oldSize != null && size != _oldSize) onLayoutChangedCallback(); _oldSize = size; From 0774c5194b34902bf5c8f36b45e986ee8462d290 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 13 Jun 2017 10:51:17 -0700 Subject: [PATCH 088/110] Add SemanticsNode.isSelected flag (#10610) * Add SemanticsNode.isSelected flag * Adds example usage to TabBar See also https://github.com/flutter/engine/pull/3764 * Review comments * whitespace fixes * Fix doc ref and update engine roll --- packages/flutter/lib/src/material/tabs.dart | 11 +++- .../flutter/lib/src/rendering/proxy_box.dart | 26 ++++++-- .../flutter/lib/src/rendering/semantics.dart | 6 ++ packages/flutter/lib/src/widgets/basic.dart | 16 ++++- packages/flutter/test/material/tabs_test.dart | 59 +++++++++++++++++++ 5 files changed, 108 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 2a56826c49bd9..25bc23206f90a 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -730,9 +730,14 @@ class _TabBarState extends State { // then give all of the tabs equal flexibility so that their widths // reflect the intrinsic width of their labels. for (int index = 0; index < widget.tabs.length; index++) { - wrappedTabs[index] = new InkWell( - onTap: () { _handleTap(index); }, - child: wrappedTabs[index], + wrappedTabs[index] = new MergeSemantics( + child: new Semantics( + selected: index == _currentIndex, + child: new InkWell( + onTap: () { _handleTap(index); }, + child: wrappedTabs[index], + ), + ), ); if (!widget.isScrollable) wrappedTabs[index] = new Expanded(child: wrappedTabs[index]); diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 06872dc648a3f..86e59a3ec98dc 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2873,10 +2873,12 @@ class RenderSemanticsAnnotations extends RenderProxyBox { RenderBox child, bool container: false, bool checked, - String label + bool selected, + String label, }) : assert(container != null), _container = container, _checked = checked, + _selected = selected, _label = label, super(child); @@ -2900,8 +2902,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { markNeedsSemanticsUpdate(); } - /// If non-null, sets the "hasCheckedState" semantic to true and the - /// "isChecked" semantic to the given value. + /// If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and + /// the [SemanticsNode.isChecked] semantic to the given value. bool get checked => _checked; bool _checked; set checked(bool value) { @@ -2912,7 +2914,19 @@ class RenderSemanticsAnnotations extends RenderProxyBox { markNeedsSemanticsUpdate(onlyChanges: (value != null) == hadValue); } - /// If non-null, sets the "label" semantic to the given value. + /// If non-null, sets the [SemanticsNode.isSelected] semantic to the given + /// value. + bool get selected => _selected; + bool _selected; + set selected(bool value) { + if (selected == value) + return; + final bool hadValue = selected != null; + _selected = value; + markNeedsSemanticsUpdate(onlyChanges: (value != null) == hadValue); + } + + /// If non-null, sets the [SemanticsNode.label] semantic to the given value. String get label => _label; String _label; set label(String value) { @@ -2927,7 +2941,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { bool get isSemanticBoundary => container; @override - SemanticsAnnotator get semanticsAnnotator => checked != null || label != null ? _annotate : null; + SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null ? _annotate : null; void _annotate(SemanticsNode node) { if (checked != null) { @@ -2935,6 +2949,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ..hasCheckedState = true ..isChecked = checked; } + if (selected != null) + node.isSelected = selected; if (label != null) node.label = label; } diff --git a/packages/flutter/lib/src/rendering/semantics.dart b/packages/flutter/lib/src/rendering/semantics.dart index a2bc8edd852bd..cddd67779efbd 100644 --- a/packages/flutter/lib/src/rendering/semantics.dart +++ b/packages/flutter/lib/src/rendering/semantics.dart @@ -292,6 +292,10 @@ class SemanticsNode extends AbstractNode { bool get isChecked => (_flags & SemanticsFlags.isChecked.index) != 0; set isChecked(bool value) => _setFlag(SemanticsFlags.isChecked, value); + /// Whether the current node is selected (true) or not (false). + bool get isSelected => (_flags & SemanticsFlags.isSelected.index) != 0; + set isSelected(bool value) => _setFlag(SemanticsFlags.isSelected, value); + /// A textual description of this node. String get label => _label; String _label = ''; @@ -595,6 +599,8 @@ class SemanticsNode extends AbstractNode { else buffer.write('; unchecked'); } + if (isSelected) + buffer.write('; selected'); if (label.isNotEmpty) buffer.write('; "$label"'); buffer.write(')'); diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index f492c84a13902..bf462de3ff5b4 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -3635,7 +3635,8 @@ class Semantics extends SingleChildRenderObjectWidget { Widget child, this.container: false, this.checked, - this.label + this.selected, + this.label, }) : assert(container != null), super(key: key, child: child); @@ -3656,6 +3657,13 @@ class Semantics extends SingleChildRenderObjectWidget { /// state is. final bool checked; + /// If non-null indicates that this subtree represents something that can be + /// in a selected or unselected state, and what its current state is. + /// + /// The active tab in a tab bar for example is considered "selected", whereas + /// all other tabs are unselected. + final bool selected; + /// Provides a textual description of the widget. final String label; @@ -3663,7 +3671,8 @@ class Semantics extends SingleChildRenderObjectWidget { RenderSemanticsAnnotations createRenderObject(BuildContext context) => new RenderSemanticsAnnotations( container: container, checked: checked, - label: label + selected: selected, + label: label, ); @override @@ -3671,6 +3680,7 @@ class Semantics extends SingleChildRenderObjectWidget { renderObject ..container = container ..checked = checked + ..selected = selected ..label = label; } @@ -3680,6 +3690,8 @@ class Semantics extends SingleChildRenderObjectWidget { description.add('container: $container'); if (checked != null) description.add('checked: $checked'); + if (selected != null) + description.add('selected: $selected'); if (label != null) description.add('label: "$label"'); } diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 4893b5f3ee300..47c32922e09fa 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -2,12 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui' show SemanticsFlags, SemanticsAction; + import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import '../rendering/mock_canvas.dart'; import '../rendering/recording_canvas.dart'; +import '../widgets/semantics_tester.dart'; class StateMarker extends StatefulWidget { const StateMarker({ Key key, this.child }) : super(key: key); @@ -900,6 +903,62 @@ void main() { rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight) )); }); + + testWidgets('correct semantics', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + final List tabs = new List.generate(2, (int index) { + return new Tab(text: 'TAB #$index'); + }); + + final TabController controller = new TabController( + vsync: const TestVSync(), + length: tabs.length, + initialIndex: 0, + ); + + await tester.pumpWidget( + new Material( + child: new Semantics( + container: true, + child: new TabBar( + isScrollable: true, + controller: controller, + tabs: tabs, + ), + ), + ), + ); + + final TestSemantics expectedSemantics = new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + rect: TestSemantics.fullScreen, + children: [ + new TestSemantics( + id: 2, + actions: SemanticsAction.tap.index, + flags: SemanticsFlags.isSelected.index, + label: 'TAB #0', + rect: new Rect.fromLTRB(0.0, 0.0, 108.0, 46.0), + transform: new Matrix4.translationValues(0.0, 276.0, 0.0), + ), + new TestSemantics( + id: 4, + actions: SemanticsAction.tap.index, + label: 'TAB #1', + rect: new Rect.fromLTRB(0.0, 0.0, 108.0, 46.0), + transform: new Matrix4.translationValues(108.0, 276.0, 0.0), + ), + ]), + ], + ); + + expect(semantics, hasSemantics(expectedSemantics)); + + semantics.dispose(); + }); testWidgets('TabBar etc with zero tabs', (WidgetTester tester) async { final TabController controller = new TabController( From 8bf17192f655fbf1941d09133b4923d0918b667e Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 13 Jun 2017 12:49:07 -0700 Subject: [PATCH 089/110] Adding first semantics perf test (#10649) * Adding first semantics perf test * review commnts and analyzer fixes * fix analyzer warning --- .../test_driver/semantics_perf.dart | 11 +++++ .../test_driver/semantics_perf_test.dart | 43 +++++++++++++++++++ .../tasks/complex_layout_semantics_perf.dart | 38 ++++++++++++++++ dev/devicelab/manifest.yaml | 7 +++ packages/flutter_driver/lib/src/driver.dart | 10 +++++ .../flutter_driver/lib/src/extension.dart | 28 +++++++++++- packages/flutter_driver/lib/src/message.dart | 3 ++ .../flutter_driver/lib/src/semantics.dart | 43 +++++++++++++++++++ 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 dev/benchmarks/complex_layout/test_driver/semantics_perf.dart create mode 100644 dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart create mode 100644 dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart create mode 100644 packages/flutter_driver/lib/src/semantics.dart diff --git a/dev/benchmarks/complex_layout/test_driver/semantics_perf.dart b/dev/benchmarks/complex_layout/test_driver/semantics_perf.dart new file mode 100644 index 0000000000000..df4ca137b5cf4 --- /dev/null +++ b/dev/benchmarks/complex_layout/test_driver/semantics_perf.dart @@ -0,0 +1,11 @@ +// Copyright 2017 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_driver/driver_extension.dart'; +import 'package:complex_layout/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart b/dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart new file mode 100644 index 0000000000000..28b1ed026cf77 --- /dev/null +++ b/dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart @@ -0,0 +1,43 @@ +// Copyright 2017 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:convert'; +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + group('semantics performance test', () { + FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(printCommunication: true); + }); + + tearDownAll(() async { + if (driver != null) + driver.close(); + }); + + test('inital tree creation', () async { + // Let app become fully idle. + await new Future.delayed(const Duration(seconds: 1)); + + final Timeline timeline = await driver.traceAction(() async { + expect(await driver.setSemantics(true), isTrue); + }); + + final Iterable semanticsEvents = timeline.events.where((TimelineEvent event) => event.name == "Semantics"); + if (semanticsEvents.length != 1) + fail('Expected exactly one semantics event, got ${semanticsEvents.length}'); + final Duration semanticsTreeCreation = semanticsEvents.first.duration; + + final String json = JSON.encode({'initialSemanticsTreeCreation': semanticsTreeCreation.inMilliseconds}); + new File(p.join(testOutputsDirectory, 'complex_layout_semantics_perf.json')).writeAsStringSync(json); + }); + }); +} diff --git a/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart b/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart new file mode 100644 index 0000000000000..028447a75d716 --- /dev/null +++ b/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2017 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_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; +import 'package:path/path.dart' as p; + +void main() { + task(() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + + final Device device = await devices.workingDevice; + await device.unlock(); + final String deviceId = device.deviceId; + await flutter('packages', options: ['get']); + + final String complexLayoutPath = p.join(flutterDirectory.path, 'dev', 'benchmarks', 'complex_layout'); + + await inDirectory(complexLayoutPath, () async { + await flutter('drive', options: [ + '-v', + '--profile', + '--trace-startup', // Enables "endless" timeline event buffering. + '-t', + p.join(complexLayoutPath, 'test_driver', 'semantics_perf.dart'), + '-d', + deviceId, + ]); + }); + + final String dataPath = p.join(complexLayoutPath, 'build', 'complex_layout_semantics_perf.json'); + return new TaskResult.successFromFile(file(dataPath), benchmarkScoreKeys: [ + 'initialSemanticsTreeCreation', + ]); + }); +} diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 02ea757e7cf63..5a5b22096f90b 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -133,6 +133,13 @@ tasks: required_agent_capabilities: ["linux/android"] flaky: true + complex_layout_semantics_perf: + description: > + Measures duration of building the initial semantics tree. + stage: devicelab + required_agent_capabilities: ["linux/android"] + flaky: true + # iOS on-device tests channels_integration_test_ios: diff --git a/packages/flutter_driver/lib/src/driver.dart b/packages/flutter_driver/lib/src/driver.dart index 2b8addaeea7c3..edd18518bff5b 100644 --- a/packages/flutter_driver/lib/src/driver.dart +++ b/packages/flutter_driver/lib/src/driver.dart @@ -21,6 +21,7 @@ import 'gesture.dart'; import 'health.dart'; import 'message.dart'; import 'render_tree.dart'; +import 'semantics.dart'; import 'timeline.dart'; /// Timeline stream identifier. @@ -383,6 +384,15 @@ class FlutterDriver { return GetTextResult.fromJson(await _sendCommand(new GetText(finder, timeout: timeout))).text; } + /// Turns semantics on or off in the Flutter app under test. + /// + /// Returns `true` when the call actually changed the state from on to off or + /// vice versa. + Future setSemantics(bool enabled, { Duration timeout: _kShortTimeout }) async { + final SetSemanticsResult result = SetSemanticsResult.fromJson(await _sendCommand(new SetSemantics(enabled, timeout: timeout))); + return result.changedState; + } + /// Take a screenshot. The image will be returned as a PNG. Future> screenshot({ Duration timeout }) async { timeout ??= _kLongTimeout; diff --git a/packages/flutter_driver/lib/src/extension.dart b/packages/flutter_driver/lib/src/extension.dart index 851c2cb3b4397..cb5ff5fcc9f36 100644 --- a/packages/flutter_driver/lib/src/extension.dart +++ b/packages/flutter_driver/lib/src/extension.dart @@ -8,7 +8,7 @@ import 'package:meta/meta.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart' show RendererBinding; +import 'package:flutter/rendering.dart' show RendererBinding, SemanticsHandle; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -20,6 +20,7 @@ import 'gesture.dart'; import 'health.dart'; import 'message.dart'; import 'render_tree.dart'; +import 'semantics.dart'; const String _extensionMethodName = 'driver'; const String _extensionMethod = 'ext.flutter.$_extensionMethodName'; @@ -70,6 +71,7 @@ class FlutterDriverExtension { 'tap': _tap, 'get_text': _getText, 'set_frame_sync': _setFrameSync, + 'set_semantics': _setSemantics, 'scroll': _scroll, 'scrollIntoView': _scrollIntoView, 'waitFor': _waitFor, @@ -82,6 +84,7 @@ class FlutterDriverExtension { 'tap': (Map params) => new Tap.deserialize(params), 'get_text': (Map params) => new GetText.deserialize(params), 'set_frame_sync': (Map params) => new SetFrameSync.deserialize(params), + 'set_semantics': (Map params) => new SetSemantics.deserialize(params), 'scroll': (Map params) => new Scroll.deserialize(params), 'scrollIntoView': (Map params) => new ScrollIntoView.deserialize(params), 'waitFor': (Map params) => new WaitFor.deserialize(params), @@ -271,4 +274,27 @@ class FlutterDriverExtension { _frameSync = setFrameSyncCommand.enabled; return new SetFrameSyncResult(); } + + SemanticsHandle _semantics; + bool get _semanticsIsEnabled => RendererBinding.instance.pipelineOwner.semanticsOwner != null; + + Future _setSemantics(Command command) async { + final SetSemantics setSemanticsCommand = command; + final bool semanticsWasEnabled = _semanticsIsEnabled; + if (setSemanticsCommand.enabled && _semantics == null) { + _semantics = RendererBinding.instance.pipelineOwner.ensureSemantics(); + if (!semanticsWasEnabled) { + // wait for the first frame where semantics is enabled. + final Completer completer = new Completer(); + SchedulerBinding.instance.addPostFrameCallback((Duration d) { + completer.complete(); + }); + await completer.future; + } + } else if (!setSemanticsCommand.enabled && _semantics != null) { + _semantics.dispose(); + _semantics = null; + } + return new SetSemanticsResult(semanticsWasEnabled != _semanticsIsEnabled); + } } diff --git a/packages/flutter_driver/lib/src/message.dart b/packages/flutter_driver/lib/src/message.dart index ebc421a5cdec2..244444410278c 100644 --- a/packages/flutter_driver/lib/src/message.dart +++ b/packages/flutter_driver/lib/src/message.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:meta/meta.dart'; + /// An object sent from the Flutter Driver to a Flutter application to instruct /// the application to perform a task. abstract class Command { @@ -20,6 +22,7 @@ abstract class Command { String get kind; /// Serializes this command to parameter name/value pairs. + @mustCallSuper Map serialize() => { 'command': kind, 'timeout': '${timeout.inMilliseconds}', diff --git a/packages/flutter_driver/lib/src/semantics.dart b/packages/flutter_driver/lib/src/semantics.dart new file mode 100644 index 0000000000000..b9af7c15be6d0 --- /dev/null +++ b/packages/flutter_driver/lib/src/semantics.dart @@ -0,0 +1,43 @@ +// Copyright 2017 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 'message.dart'; + +/// Enables or disables semantics. +class SetSemantics extends Command { + @override + final String kind = 'set_semantics'; + + /// Whether semantics should be enabled or disabled. + final bool enabled; + + SetSemantics(this.enabled, { Duration timeout }) : super(timeout: timeout); + + /// Deserializes this command from the value generated by [serialize]. + SetSemantics.deserialize(Map params) + : this.enabled = params['enabled'].toLowerCase() == 'true', + super.deserialize(params); + + @override + Map serialize() => super.serialize()..addAll({ + 'enabled': '$enabled', + }); +} + +/// The result of a [SetSemantics] command. +class SetSemanticsResult extends Result { + SetSemanticsResult(this.changedState); + + final bool changedState; + + /// Deserializes this result from JSON. + static SetSemanticsResult fromJson(Map json) { + return new SetSemanticsResult(json['changedState']); + } + + @override + Map toJson() => { + 'changedState': changedState, + }; +} From fde985b37d16f709f73250a638004c2cd97ed569 Mon Sep 17 00:00:00 2001 From: Yegor Date: Tue, 13 Jun 2017 13:14:16 -0700 Subject: [PATCH 090/110] resurrect analyzer benchmarks (#10668) * resurrect analyzer benchmarks * move analyzer_benchmark to the more stable linux/android * report average rather than best result --- .../bin/tasks/analyzer_benchmark.dart | 12 ++ .../tasks/analyzer_cli__analysis_time.dart | 20 --- .../tasks/analyzer_server__analysis_time.dart | 20 --- dev/devicelab/lib/framework/benchmarks.dart | 62 -------- dev/devicelab/lib/framework/utils.dart | 29 ---- dev/devicelab/lib/tasks/analysis.dart | 144 ++++++++---------- dev/devicelab/manifest.yaml | 18 +-- 7 files changed, 83 insertions(+), 222 deletions(-) create mode 100644 dev/devicelab/bin/tasks/analyzer_benchmark.dart delete mode 100644 dev/devicelab/bin/tasks/analyzer_cli__analysis_time.dart delete mode 100644 dev/devicelab/bin/tasks/analyzer_server__analysis_time.dart delete mode 100644 dev/devicelab/lib/framework/benchmarks.dart diff --git a/dev/devicelab/bin/tasks/analyzer_benchmark.dart b/dev/devicelab/bin/tasks/analyzer_benchmark.dart new file mode 100644 index 0000000000000..d705eedace0ab --- /dev/null +++ b/dev/devicelab/bin/tasks/analyzer_benchmark.dart @@ -0,0 +1,12 @@ +// Copyright 2016 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 'package:flutter_devicelab/tasks/analysis.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; + +Future main() async { + await task(analyzerBenchmarkTask); +} diff --git a/dev/devicelab/bin/tasks/analyzer_cli__analysis_time.dart b/dev/devicelab/bin/tasks/analyzer_cli__analysis_time.dart deleted file mode 100644 index 50ee992e050ec..0000000000000 --- a/dev/devicelab/bin/tasks/analyzer_cli__analysis_time.dart +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2016 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 'package:flutter_devicelab/tasks/analysis.dart'; -import 'package:flutter_devicelab/framework/framework.dart'; -import 'package:flutter_devicelab/framework/utils.dart'; - -Future main() async { - final String revision = await getCurrentFlutterRepoCommit(); - final DateTime revisionTimestamp = await getFlutterRepoCommitTimestamp(revision); - final String dartSdkVersion = await getDartVersion(); - await task(createAnalyzerCliTest( - sdk: dartSdkVersion, - commit: revision, - timestamp: revisionTimestamp, - )); -} diff --git a/dev/devicelab/bin/tasks/analyzer_server__analysis_time.dart b/dev/devicelab/bin/tasks/analyzer_server__analysis_time.dart deleted file mode 100644 index 922c8ce85e0ce..0000000000000 --- a/dev/devicelab/bin/tasks/analyzer_server__analysis_time.dart +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2016 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 'package:flutter_devicelab/tasks/analysis.dart'; -import 'package:flutter_devicelab/framework/framework.dart'; -import 'package:flutter_devicelab/framework/utils.dart'; - -Future main() async { - final String revision = await getCurrentFlutterRepoCommit(); - final DateTime revisionTimestamp = await getFlutterRepoCommitTimestamp(revision); - final String dartSdkVersion = await getDartVersion(); - await task(createAnalyzerServerTest( - sdk: dartSdkVersion, - commit: revision, - timestamp: revisionTimestamp, - )); -} diff --git a/dev/devicelab/lib/framework/benchmarks.dart b/dev/devicelab/lib/framework/benchmarks.dart deleted file mode 100644 index b4bddfc665cbd..0000000000000 --- a/dev/devicelab/lib/framework/benchmarks.dart +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2016 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 'framework.dart'; - -/// A benchmark harness used to run a benchmark multiple times and report the -/// best result. -abstract class Benchmark { - Benchmark(this.name); - - final String name; - - TaskResult bestResult; - - Future init() => new Future.value(); - - Future run(); - TaskResult get lastResult; - - @override - String toString() => name; -} - -/// Runs a [benchmark] [iterations] times and reports the best result. -/// -/// Use [warmUpBenchmark] to discard cold performance results. -Future runBenchmark(Benchmark benchmark, { - int iterations: 1, - bool warmUpBenchmark: false -}) async { - await benchmark.init(); - - final List allRuns = []; - - num minValue; - - if (warmUpBenchmark) - await benchmark.run(); - - while (iterations > 0) { - iterations--; - - print(''); - - try { - final num result = await benchmark.run(); - allRuns.add(result); - - if (minValue == null || result < minValue) { - benchmark.bestResult = benchmark.lastResult; - minValue = result; - } - } catch (error) { - print('benchmark failed with error: $error'); - } - } - - return minValue; -} diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index 08fa44e7ae7cb..5b0c77e9d8ae8 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -380,35 +380,6 @@ void checkNotNull(Object o1, throw 'o10 is null'; } -/// Add benchmark values to a JSON results file. -/// -/// If the file contains information about how long the benchmark took to run -/// (a `time` field), then return that info. -// TODO(yjbanov): move this data to __metadata__ -num addBuildInfo(File jsonFile, - {num expected, String sdk, String commit, DateTime timestamp}) { - Map json; - - if (jsonFile.existsSync()) - json = JSON.decode(jsonFile.readAsStringSync()); - else - json = {}; - - if (expected != null) - json['expected'] = expected; - if (sdk != null) - json['sdk'] = sdk; - if (commit != null) - json['commit'] = commit; - if (timestamp != null) - json['timestamp'] = timestamp.millisecondsSinceEpoch; - - jsonFile.writeAsStringSync(jsonEncode(json)); - - // Return the elapsed time of the benchmark (if any). - return json['time']; -} - /// Splits [from] into lines and selects those that contain [pattern]. Iterable grep(Pattern pattern, {@required String from}) { return from.split('\n').where((String line) { diff --git a/dev/devicelab/lib/tasks/analysis.dart b/dev/devicelab/lib/tasks/analysis.dart index b02d316026f93..fb681394a24cd 100644 --- a/dev/devicelab/lib/tasks/analysis.dart +++ b/dev/devicelab/lib/tasks/analysis.dart @@ -5,112 +5,98 @@ import 'dart:async'; import 'dart:io'; -import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; -import '../framework/benchmarks.dart'; import '../framework/framework.dart'; import '../framework/utils.dart'; -TaskFunction createAnalyzerCliTest({ - @required String sdk, - @required String commit, - @required DateTime timestamp, -}) { - return new AnalyzerCliTask(sdk, commit, timestamp); -} +/// Run each benchmark this many times and compute average. +const int _kRunsPerBenchmark = 3; -TaskFunction createAnalyzerServerTest({ - @required String sdk, - @required String commit, - @required DateTime timestamp, -}) { - return new AnalyzerServerTask(sdk, commit, timestamp); -} +/// Runs a benchmark once and reports the result as a lower-is-better numeric +/// value. +typedef Future _Benchmark(); -abstract class AnalyzerTask { - Benchmark benchmark; +/// Path to the generated "mega gallery" app. +Directory get _megaGalleryDirectory => dir(path.join(Directory.systemTemp.path, 'mega_gallery')); - Future call() async { - section(benchmark.name); - await runBenchmark(benchmark, iterations: 3, warmUpBenchmark: true); - return benchmark.bestResult; - } -} +Future analyzerBenchmarkTask() async { + await inDirectory(flutterDirectory, () async { + rmTree(_megaGalleryDirectory); + mkdirs(_megaGalleryDirectory); + await dart(['dev/tools/mega_gallery.dart', '--out=${_megaGalleryDirectory.path}']); + }); -class AnalyzerCliTask extends AnalyzerTask { - AnalyzerCliTask(String sdk, String commit, DateTime timestamp) { - benchmark = new FlutterAnalyzeBenchmark(sdk, commit, timestamp); - } -} + final Map data = { + 'flutter_repo_batch': await _run(new _FlutterRepoBenchmark()), + 'flutter_repo_watch': await _run(new _FlutterRepoBenchmark(watch: true)), + 'mega_gallery_batch': await _run(new _MegaGalleryBenchmark()), + 'mega_gallery_watch': await _run(new _MegaGalleryBenchmark(watch: true)), + }; -class AnalyzerServerTask extends AnalyzerTask { - AnalyzerServerTask(String sdk, String commit, DateTime timestamp) { - benchmark = new FlutterAnalyzeAppBenchmark(sdk, commit, timestamp); - } + return new TaskResult.success(data, benchmarkScoreKeys: data.keys.toList()); } -class FlutterAnalyzeBenchmark extends Benchmark { - FlutterAnalyzeBenchmark(this.sdk, this.commit, this.timestamp) - : super('flutter analyze --flutter-repo'); - - final String sdk; - final String commit; - final DateTime timestamp; +/// Times how long it takes to analyze the Flutter repository. +class _FlutterRepoBenchmark { + _FlutterRepoBenchmark({ this.watch = false }); - File get benchmarkFile => - file(path.join(flutterDirectory.path, 'analysis_benchmark.json')); + final bool watch; - @override - TaskResult get lastResult => new TaskResult.successFromFile(benchmarkFile); - - @override - Future run() async { - rm(benchmarkFile); + Future call() async { + section('Analyze Flutter repo ${watch ? 'with watcher' : ''}'); + final Stopwatch stopwatch = new Stopwatch(); await inDirectory(flutterDirectory, () async { - await flutter('analyze', options: [ + final List options = [ '--flutter-repo', '--benchmark', - ]); + ]; + + if (watch) + options.add('--watch'); + + stopwatch.start(); + await flutter('analyze', options: options); + stopwatch.stop(); }); - return addBuildInfo(benchmarkFile, - timestamp: timestamp, expected: 25.0, sdk: sdk, commit: commit); + return stopwatch.elapsedMilliseconds / 1000; } } -class FlutterAnalyzeAppBenchmark extends Benchmark { - FlutterAnalyzeAppBenchmark(this.sdk, this.commit, this.timestamp) - : super('analysis server mega_gallery'); +/// Times how long it takes to analyze the generated "mega_gallery" app. +class _MegaGalleryBenchmark { + _MegaGalleryBenchmark({ this.watch = false }); - final String sdk; - final String commit; - final DateTime timestamp; + final bool watch; - @override - TaskResult get lastResult => new TaskResult.successFromFile(benchmarkFile); + Future call() async { + section('Analyze mega gallery ${watch ? 'with watcher' : ''}'); + final Stopwatch stopwatch = new Stopwatch(); + await inDirectory(_megaGalleryDirectory, () async { + final List options = [ + '--benchmark', + ]; - Directory get megaDir => dir( - path.join(flutterDirectory.path, 'dev/benchmarks/mega_gallery')); - File get benchmarkFile => - file(path.join(megaDir.path, 'analysis_benchmark.json')); + if (watch) + options.add('--watch'); - @override - Future init() { - return inDirectory(flutterDirectory, () async { - await dart(['dev/tools/mega_gallery.dart']); + stopwatch.start(); + await flutter('analyze', options: options); + stopwatch.stop(); }); + return stopwatch.elapsedMilliseconds / 1000; } +} - @override - Future run() async { - rm(benchmarkFile); - await inDirectory(megaDir, () async { - await flutter('analyze', options: [ - '--watch', - '--benchmark', - ]); - }); - return addBuildInfo(benchmarkFile, - timestamp: timestamp, expected: 10.0, sdk: sdk, commit: commit); +/// Runs a [benchmark] several times and reports the average result. +Future _run(_Benchmark benchmark) async { + double total = 0.0; + for (int i = 0; i < _kRunsPerBenchmark; i++) { + // Delete cached analysis results. + rmTree(dir('${Platform.environment['HOME']}/.dartServer')); + + total += await benchmark(); } + final double average = total / _kRunsPerBenchmark; + return average; } diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 5a5b22096f90b..a67b88997dc17 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -50,18 +50,6 @@ tasks: stage: devicelab required_agent_capabilities: ["has-android-device"] - analyzer_cli__analysis_time: - description: > - Measures the speed of analyzing Flutter itself in batch mode. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - - analyzer_server__analysis_time: - description: > - Measures the speed of analyzing Flutter itself in server mode. - stage: devicelab - required_agent_capabilities: ["has-android-device"] - # Android on-device tests complex_layout_scroll_perf__timeline_summary: @@ -297,3 +285,9 @@ tasks: Measures memory usage after Android app suspend and resume. stage: devicelab required_agent_capabilities: ["linux/android"] + + analyzer_benchmark: + description: > + Measures the speed of Dart analyzer. + stage: devicelab + required_agent_capabilities: ["linux/android"] From 3528cd6f2dbc8eb0d1e94f83c11b4afae1952127 Mon Sep 17 00:00:00 2001 From: Brian Slesinsky Date: Tue, 13 Jun 2017 13:26:32 -0700 Subject: [PATCH 091/110] flutter test: add --machine flag (#10520) Currently this just prints the observatory URL as a JSON event. Refactored the code to make this fit in. --- examples/hello_world/hello_world.iml | 1 + .../flutter_tools/bin/fuchsia_tester.dart | 1 - packages/flutter_tools/flutter_tools.iml | 16 +-- packages/flutter_tools/lib/executable.dart | 2 +- .../flutter_tools/lib/src/commands/test.dart | 127 ++++++++---------- .../lib/src/test/coverage_collector.dart | 10 +- .../lib/src/test/event_printer.dart | 41 ++++++ .../lib/src/test/flutter_platform.dart | 42 +++--- .../flutter_tools/lib/src/test/runner.dart | 81 +++++++++++ .../flutter_tools/lib/src/test/watcher.dart | 39 ++++++ 10 files changed, 263 insertions(+), 97 deletions(-) create mode 100644 packages/flutter_tools/lib/src/test/event_printer.dart create mode 100644 packages/flutter_tools/lib/src/test/runner.dart create mode 100644 packages/flutter_tools/lib/src/test/watcher.dart diff --git a/examples/hello_world/hello_world.iml b/examples/hello_world/hello_world.iml index 9d5dae19540c2..c82504f3177c9 100644 --- a/examples/hello_world/hello_world.iml +++ b/examples/hello_world/hello_world.iml @@ -7,6 +7,7 @@ + diff --git a/packages/flutter_tools/bin/fuchsia_tester.dart b/packages/flutter_tools/bin/fuchsia_tester.dart index fb4ef775973e4..d4bd938eece8d 100644 --- a/packages/flutter_tools/bin/fuchsia_tester.dart +++ b/packages/flutter_tools/bin/fuchsia_tester.dart @@ -88,7 +88,6 @@ Future run(List args) async { } loader.installHook( shellPath: shellPath, - debuggerMode: false, ); PackageMap.globalPackagesPath = diff --git a/packages/flutter_tools/flutter_tools.iml b/packages/flutter_tools/flutter_tools.iml index 367b72461cd70..22fe5c309c091 100644 --- a/packages/flutter_tools/flutter_tools.iml +++ b/packages/flutter_tools/flutter_tools.iml @@ -7,6 +7,12 @@ + + + + + + @@ -30,16 +36,10 @@ + - - - - - - - - + diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 9b3c208c3847d..380641270d138 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -83,7 +83,7 @@ Future main(List args) async { new RunCommand(verboseHelp: verboseHelp), new ScreenshotCommand(), new StopCommand(), - new TestCommand(), + new TestCommand(verboseHelp: verboseHelp), new TraceCommand(), new UpdatePackagesCommand(hidden: !verboseHelp), new UpgradeCommand(), diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index e3285010a7bf8..6171abcc88b97 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -4,9 +4,6 @@ import 'dart:async'; -import 'package:test/src/executable.dart' as test; // ignore: implementation_imports - -import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; @@ -14,16 +11,16 @@ import '../base/logger.dart'; import '../base/os.dart'; import '../base/platform.dart'; import '../base/process_manager.dart'; -import '../base/terminal.dart'; import '../cache.dart'; -import '../dart/package_map.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; import '../test/coverage_collector.dart'; -import '../test/flutter_platform.dart' as loader; +import '../test/event_printer.dart'; +import '../test/runner.dart'; +import '../test/watcher.dart'; class TestCommand extends FlutterCommand { - TestCommand() { + TestCommand({ bool verboseHelp: false }) { usesPubOption(); argParser.addFlag('start-paused', defaultsTo: false, @@ -53,6 +50,11 @@ class TestCommand extends FlutterCommand { defaultsTo: 'coverage/lcov.info', help: 'Where to store coverage information (if coverage is enabled).' ); + argParser.addFlag('machine', + hide: !verboseHelp, + negatable: false, + help: 'Handle machine structured JSON command input\n' + 'and provide output and progress in machine friendly format.'); commandValidator = () { if (!fs.isFileSync('pubspec.yaml')) { throwToolExit( @@ -70,37 +72,12 @@ class TestCommand extends FlutterCommand { @override String get description => 'Run Flutter unit tests for the current project.'; - Iterable _findTests(Directory directory) { - return directory.listSync(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => entity.path.endsWith('_test.dart') && - fs.isFileSync(entity.path)) - .map((FileSystemEntity entity) => fs.path.absolute(entity.path)); - } - Directory get _currentPackageTestDir { // We don't scan the entire package, only the test/ subdirectory, so that // files with names like like "hit_test.dart" don't get run. return fs.directory('test'); } - Future _runTests(List testArgs, Directory testDirectory) async { - final Directory currentDirectory = fs.currentDirectory; - try { - if (testDirectory != null) { - printTrace('switching to directory $testDirectory to run tests'); - PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(PackageMap.globalPackagesPath)); - fs.currentDirectory = testDirectory; - } - printTrace('running test package with arguments: $testArgs'); - await test.main(testArgs); - // test.main() sets dart:io's exitCode global. - printTrace('test package returned with exit code $exitCode'); - return exitCode; - } finally { - fs.currentDirectory = currentDirectory; - } - } - Future _collectCoverageData(CoverageCollector collector, { bool mergeCoverageData: false }) async { final Status status = logger.startProgress('Collecting coverage information...'); final String coverageData = await collector.finalizeCoverage( @@ -161,7 +138,7 @@ class TestCommand extends FlutterCommand { } @override - Future runCommand() async { + Future runCommand() async { if (platform.isWindows) { throwToolExit( 'The test command is currently not supported on Windows: ' @@ -169,57 +146,57 @@ class TestCommand extends FlutterCommand { ); } - final List testArgs = []; - commandValidator(); - if (!terminal.supportsColor) - testArgs.addAll(['--no-color', '-rexpanded']); + Iterable files = argResults.rest.map((String testPath) => fs.path.absolute(testPath)).toList(); - CoverageCollector collector; - if (argResults['coverage'] || argResults['merge-coverage']) { - collector = new CoverageCollector(); - testArgs.add('--concurrency=1'); + final bool startPaused = argResults['start-paused']; + if (startPaused && files.length != 1) { + throwToolExit( + 'When using --start-paused, you must specify a single test file to run.', + exitCode: 1); } - testArgs.add('--'); - - Directory testDir; - Iterable files = argResults.rest.map((String testPath) => fs.path.absolute(testPath)).toList(); - if (argResults['start-paused']) { - if (files.length != 1) - throwToolExit('When using --start-paused, you must specify a single test file to run.', exitCode: 1); - } else if (files.isEmpty) { - testDir = _currentPackageTestDir; - if (!testDir.existsSync()) - throwToolExit('Test directory "${testDir.path}" not found.'); - files = _findTests(testDir); + Directory workDir; + if (files.isEmpty) { + workDir = _currentPackageTestDir; + if (!workDir.existsSync()) + throwToolExit('Test directory "${workDir.path}" not found.'); + files = _findTests(workDir); if (files.isEmpty) { throwToolExit( - 'Test directory "${testDir.path}" does not appear to contain any test files.\n' - 'Test files must be in that directory and end with the pattern "_test.dart".' + 'Test directory "${workDir.path}" does not appear to contain any test files.\n' + 'Test files must be in that directory and end with the pattern "_test.dart".' ); } } - testArgs.addAll(files); - - final InternetAddressType serverType = argResults['ipv6'] - ? InternetAddressType.IP_V6 - : InternetAddressType.IP_V4; - - final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester); - if (!fs.isFileSync(shellPath)) - throwToolExit('Cannot find Flutter shell at $shellPath'); - loader.installHook( - shellPath: shellPath, - collector: collector, - debuggerMode: argResults['start-paused'], - serverType: serverType, - ); + + CoverageCollector collector; + if (argResults['coverage'] || argResults['merge-coverage']) { + collector = new CoverageCollector(); + } + + final bool wantEvents = argResults['machine']; + if (collector != null && wantEvents) { + throwToolExit( + "The test command doesn't support --machine and coverage together"); + } + + TestWatcher watcher; + if (collector != null) { + watcher = collector; + } else if (wantEvents) { + watcher = new EventPrinter(); + } Cache.releaseLockEarly(); - final int result = await _runTests(testArgs, testDir); + final int result = await runTests(files, + workDir: workDir, + watcher: watcher, + enableObservatory: collector != null || startPaused, + startPaused: startPaused, + ipv6: argResults['ipv6']); if (collector != null) { if (!await _collectCoverageData(collector, mergeCoverageData: argResults['merge-coverage'])) @@ -228,5 +205,13 @@ class TestCommand extends FlutterCommand { if (result != 0) throwToolExit(null); + return const FlutterCommandResult(ExitStatus.success); } } + +Iterable _findTests(Directory directory) { + return directory.listSync(recursive: true, followLinks: false) + .where((FileSystemEntity entity) => entity.path.endsWith('_test.dart') && + fs.isFileSync(entity.path)) + .map((FileSystemEntity entity) => fs.path.absolute(entity.path)); +} diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart index 3b7ded4a5014d..21b80e0befd9d 100644 --- a/packages/flutter_tools/lib/src/test/coverage_collector.dart +++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart @@ -11,10 +11,18 @@ import '../base/io.dart'; import '../dart/package_map.dart'; import '../globals.dart'; +import 'watcher.dart'; + /// A class that's used to collect coverage data during tests. -class CoverageCollector { +class CoverageCollector extends TestWatcher { Map _globalHitmap; + @override + Future onFinishedTests(ProcessEvent event) async { + printTrace('test ${event.childIndex}: collecting coverage'); + await collectCoverage(event.process, event.observatoryUri); + } + void _addHitmap(Map hitmap) { if (_globalHitmap == null) _globalHitmap = hitmap; diff --git a/packages/flutter_tools/lib/src/test/event_printer.dart b/packages/flutter_tools/lib/src/test/event_printer.dart new file mode 100644 index 0000000000000..4be6b77fe1fb5 --- /dev/null +++ b/packages/flutter_tools/lib/src/test/event_printer.dart @@ -0,0 +1,41 @@ +// Copyright 2017 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:convert' show JSON; + +import '../base/io.dart' show stdout; +import 'watcher.dart'; + +/// Prints JSON events when running a test in --machine mode. +class EventPrinter extends TestWatcher { + EventPrinter({StringSink out}) : this._out = out == null ? stdout: out; + + final StringSink _out; + + @override + void onStartedProcess(ProcessEvent event) { + _sendEvent("test.startedProcess", + {"observatoryUri": event.observatoryUri.toString()}); + } + + void _sendEvent(String name, [dynamic params]) { + final Map map = { 'event': name}; + if (params != null) { + map['params'] = params; + } + _send(map); + } + + void _send(Map command) { + final String encoded = JSON.encode(command, toEncodable: _jsonEncodeObject); + _out.writeln('\n[$encoded]'); + } + + dynamic _jsonEncodeObject(dynamic object) { + if (object is Uri) { + return object.toString(); + } + return object; + } +} diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index 58de703b7ce22..8af0147f391c3 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -19,6 +19,7 @@ import '../base/process_manager.dart'; import '../dart/package_map.dart'; import '../globals.dart'; import 'coverage_collector.dart'; +import 'watcher.dart'; /// The timeout we give the test process to connect to the test harness /// once the process has entered its main method. @@ -53,17 +54,22 @@ final Map _kHosts = [TestPlatform.vm], () => new _FlutterPlatform( shellPath: shellPath, - collector: collector, - debuggerMode: debuggerMode, + watcher: watcher, + enableObservatory: enableObservatory, + startPaused: startPaused, explicitObservatoryPort: observatoryPort, explicitDiagnosticPort: diagnosticPort, host: _kHosts[serverType], @@ -78,8 +84,9 @@ typedef Future _Finalizer(); class _FlutterPlatform extends PlatformPlugin { _FlutterPlatform({ @required this.shellPath, - this.collector, - this.debuggerMode, + this.watcher, + this.enableObservatory, + this.startPaused, this.explicitObservatoryPort, this.explicitDiagnosticPort, this.host, @@ -88,8 +95,9 @@ class _FlutterPlatform extends PlatformPlugin { } final String shellPath; - final CoverageCollector collector; - final bool debuggerMode; + final TestWatcher watcher; + final bool enableObservatory; + final bool startPaused; final int explicitObservatoryPort; final int explicitDiagnosticPort; final InternetAddress host; @@ -105,7 +113,7 @@ class _FlutterPlatform extends PlatformPlugin { @override StreamChannel loadChannel(String testPath, TestPlatform platform) { - if (explicitObservatoryPort != null || explicitDiagnosticPort != null || debuggerMode) { + if (enableObservatory || explicitDiagnosticPort != null) { if (_testCount > 0) throwToolExit('installHook() was called with an observatory port, a diagnostic port, both, or debugger mode enabled, but then more than one test suite was run.'); } @@ -190,8 +198,8 @@ class _FlutterPlatform extends PlatformPlugin { shellPath, listenerFile.path, packages: PackageMap.globalPackagesPath, - enableObservatory: collector != null || debuggerMode, - startPaused: debuggerMode, + enableObservatory: enableObservatory, + startPaused: startPaused, observatoryPort: explicitObservatoryPort, diagnosticPort: explicitDiagnosticPort, ); @@ -216,19 +224,23 @@ class _FlutterPlatform extends PlatformPlugin { // Pipe stdout and stderr from the subprocess to our printStatus console. // We also keep track of what observatory port the engine used, if any. Uri processObservatoryUri; + _pipeStandardStreamsToConsole( process, reportObservatoryUri: (Uri detectedUri) { assert(processObservatoryUri == null); assert(explicitObservatoryPort == null || explicitObservatoryPort == detectedUri.port); - if (debuggerMode) { + if (startPaused) { printStatus('The test process has been started.'); printStatus('You can now connect to it using observatory. To connect, load the following Web site in your browser:'); printStatus(' $detectedUri'); printStatus('You should first set appropriate breakpoints, then resume the test in the debugger.'); } else { - printTrace('test $ourTestCount: using observatory uri $detectedUri from pid ${process.pid} to collect coverage'); + printTrace('test $ourTestCount: using observatory uri $detectedUri from pid ${process.pid}'); + } + if (watcher != null) { + watcher.onStartedProcess(new ProcessEvent(ourTestCount, process, detectedUri)); } processObservatoryUri = detectedUri; }, @@ -341,9 +353,9 @@ class _FlutterPlatform extends PlatformPlugin { break; } - if (subprocessActive && collector != null) { - printTrace('test $ourTestCount: collecting coverage'); - await collector.collectCoverage(process, processObservatoryUri); + if (subprocessActive && watcher != null) { + await watcher.onFinishedTests( + new ProcessEvent(ourTestCount, process, processObservatoryUri)); } } catch (error, stack) { printTrace('test $ourTestCount: error caught during test; ${controllerSinkClosed ? "reporting to console" : "sending to test framework"}'); diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart new file mode 100644 index 0000000000000..cd7ad43ec8432 --- /dev/null +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -0,0 +1,81 @@ +// Copyright 2017 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'; + +// ignore: implementation_imports +import 'package:test/src/executable.dart' as test; + +import '../artifacts.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/terminal.dart'; +import '../dart/package_map.dart'; +import '../globals.dart'; +import '../test/flutter_platform.dart' as loader; +import 'watcher.dart'; + +/// Runs tests using package:test and the Flutter engine. +Future runTests( + List testFiles, { + Directory workDir, + bool enableObservatory: false, + bool startPaused: false, + bool ipv6: false, + TestWatcher watcher, + }) async { + // Compute the command-line arguments for package:test. + final List testArgs = []; + if (!terminal.supportsColor) + testArgs.addAll(['--no-color', '-rexpanded']); + + if (enableObservatory) { + testArgs.add('--concurrency=1'); + } + + testArgs.add('--'); + testArgs.addAll(testFiles); + + // Configure package:test to use the Flutter engine for child processes. + final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester); + if (!fs.isFileSync(shellPath)) + throwToolExit('Cannot find Flutter shell at $shellPath'); + + final InternetAddressType serverType = + ipv6 ? InternetAddressType.IP_V6 : InternetAddressType.IP_V4; + + loader.installHook( + shellPath: shellPath, + watcher: watcher, + enableObservatory: enableObservatory, + startPaused: startPaused, + serverType: serverType, + ); + + // Set the package path used for child processes. + // TODO(skybrian): why is this global? Move to installHook? + PackageMap.globalPackagesPath = + fs.path.normalize(fs.path.absolute(PackageMap.globalPackagesPath)); + + // Call package:test's main method in the appropriate directory. + final Directory saved = fs.currentDirectory; + try { + if (workDir != null) { + printTrace('switching to directory $workDir to run tests'); + fs.currentDirectory = workDir; + } + + printTrace('running test package with arguments: $testArgs'); + await test.main(testArgs); + + // test.main() sets dart:io's exitCode global. + // TODO(skybrian): restore previous value? + printTrace('test package returned with exit code $exitCode'); + + return exitCode; + } finally { + fs.currentDirectory = saved; + } +} diff --git a/packages/flutter_tools/lib/src/test/watcher.dart b/packages/flutter_tools/lib/src/test/watcher.dart new file mode 100644 index 0000000000000..114f456151d30 --- /dev/null +++ b/packages/flutter_tools/lib/src/test/watcher.dart @@ -0,0 +1,39 @@ +// Copyright 2017 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 '../base/io.dart' show Process; + +/// Callbacks for reporting progress while running tests. +class TestWatcher { + + /// Called after a child process starts. + /// + /// If startPaused was true, the caller needs to resume in Observatory to + /// start running the tests. + void onStartedProcess(ProcessEvent event) {} + + /// Called after the tests finish but before the process exits. + /// + /// The child process won't exit until this method completes. + /// Not called if the process died. + Future onFinishedTests(ProcessEvent event) async {} +} + +/// Describes a child process started during testing. +class ProcessEvent { + ProcessEvent(this.childIndex, this.process, this.observatoryUri); + + /// The index assigned when the child process was launched. + /// + /// Indexes are assigned consecutively starting from zero. + /// When debugging, there should only be one child process so this will + /// always be zero. + final int childIndex; + + final Process process; + + /// The observatory Uri or null if not debugging. + final Uri observatoryUri; +} From e329356f0fafc544d8688c0c071553297b9df2f2 Mon Sep 17 00:00:00 2001 From: Brian Slesinsky Date: Tue, 13 Jun 2017 14:42:09 -0700 Subject: [PATCH 092/110] Oops, fix code coverage (#10672) And add comments explaining why. --- packages/flutter_tools/lib/src/test/flutter_platform.dart | 3 ++- packages/flutter_tools/lib/src/test/runner.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index 8af0147f391c3..8872f79004942 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -113,7 +113,8 @@ class _FlutterPlatform extends PlatformPlugin { @override StreamChannel loadChannel(String testPath, TestPlatform platform) { - if (enableObservatory || explicitDiagnosticPort != null) { + // Fail if there will be a port conflict. + if (explicitObservatoryPort != null || explicitDiagnosticPort != null) { if (_testCount > 0) throwToolExit('installHook() was called with an observatory port, a diagnostic port, both, or debugger mode enabled, but then more than one test suite was run.'); } diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index cd7ad43ec8432..7bc9572693375 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -32,6 +32,7 @@ Future runTests( testArgs.addAll(['--no-color', '-rexpanded']); if (enableObservatory) { + // (In particular, for collecting code coverage.) testArgs.add('--concurrency=1'); } From 1ad346f5c5250d0f3ae680ea984c260f5b911e89 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 13 Jun 2017 17:00:08 -0700 Subject: [PATCH 093/110] Add workaround for aapt cruncher issue to complex_layout app (#10679) See https://github.com/flutter/flutter/issues/8986 --- dev/benchmarks/complex_layout/android/app/build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dev/benchmarks/complex_layout/android/app/build.gradle b/dev/benchmarks/complex_layout/android/app/build.gradle index 114a0ee1094c7..4a8ab5e563fbf 100644 --- a/dev/benchmarks/complex_layout/android/app/build.gradle +++ b/dev/benchmarks/complex_layout/android/app/build.gradle @@ -33,6 +33,14 @@ android { signingConfig signingConfigs.debug } } + + aaptOptions { + // TODO(goderbauer): remove when https://github.com/flutter/flutter/issues/8986 is resolved. + if(System.getenv("FLUTTER_CI_WIN")) { + println "AAPT cruncher disabled when running on CI, see https://github.com/flutter/flutter/issues/8986" + cruncherEnabled false + } + } } flutter { From befe01989610e4e231ca1c9095fcdb7a027ede84 Mon Sep 17 00:00:00 2001 From: Dan Rubel Date: Tue, 13 Jun 2017 20:50:53 -0400 Subject: [PATCH 094/110] flutter analyze --watch --flutter-repo check package conflicts (#10641) --- .../lib/src/commands/analyze.dart | 2 +- .../lib/src/commands/analyze_base.dart | 152 ++++++++++++++++++ .../src/commands/analyze_continuously.dart | 8 +- .../lib/src/commands/analyze_once.dart | 144 +---------------- .../src/runner/flutter_command_runner.dart | 21 +-- 5 files changed, 163 insertions(+), 164 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart index afa01c951e163..f1b3391e2a73a 100644 --- a/packages/flutter_tools/lib/src/commands/analyze.dart +++ b/packages/flutter_tools/lib/src/commands/analyze.dart @@ -57,7 +57,7 @@ class AnalyzeCommand extends FlutterCommand { @override Future runCommand() { if (argResults['watch']) { - return new AnalyzeContinuously(argResults, runner.getRepoAnalysisEntryPoints()).analyze(); + return new AnalyzeContinuously(argResults, runner.getRepoPackages()).analyze(); } else { return new AnalyzeOnce(argResults, runner.getRepoPackages(), workingDirectory: workingDirectory).analyze(); } diff --git a/packages/flutter_tools/lib/src/commands/analyze_base.dart b/packages/flutter_tools/lib/src/commands/analyze_base.dart index ec5191ccde22c..177a344124d4d 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_base.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_base.dart @@ -5,7 +5,9 @@ import 'dart:async'; import 'package:args/args.dart'; +import 'package:yaml/yaml.dart' as yaml; +import '../base/common.dart'; import '../base/file_system.dart'; import '../base/utils.dart'; import '../cache.dart'; @@ -65,3 +67,153 @@ bool inRepo(List fileList) { } return false; } + +class PackageDependency { + // This is a map from dependency targets (lib directories) to a list + // of places that ask for that target (.packages or pubspec.yaml files) + Map> values = >{}; + String canonicalSource; + void addCanonicalCase(String packagePath, String pubSpecYamlPath) { + assert(canonicalSource == null); + add(packagePath, pubSpecYamlPath); + canonicalSource = pubSpecYamlPath; + } + void add(String packagePath, String sourcePath) { + values.putIfAbsent(packagePath, () => []).add(sourcePath); + } + bool get hasConflict => values.length > 1; + bool get hasConflictAffectingFlutterRepo { + assert(fs.path.isAbsolute(Cache.flutterRoot)); + for (List targetSources in values.values) { + for (String source in targetSources) { + assert(fs.path.isAbsolute(source)); + if (fs.path.isWithin(Cache.flutterRoot, source)) + return true; + } + } + return false; + } + void describeConflict(StringBuffer result) { + assert(hasConflict); + final List targets = values.keys.toList(); + targets.sort((String a, String b) => values[b].length.compareTo(values[a].length)); + for (String target in targets) { + final int count = values[target].length; + result.writeln(' $count ${count == 1 ? 'source wants' : 'sources want'} "$target":'); + bool canonical = false; + for (String source in values[target]) { + result.writeln(' $source'); + if (source == canonicalSource) + canonical = true; + } + if (canonical) { + result.writeln(' (This is the actual package definition, so it is considered the canonical "right answer".)'); + } + } + } + String get target => values.keys.single; +} + +class PackageDependencyTracker { + /// Packages whose source is defined in the vended SDK. + static const List _vendedSdkPackages = const ['analyzer', 'front_end', 'kernel']; + + // This is a map from package names to objects that track the paths + // involved (sources and targets). + Map packages = {}; + + PackageDependency getPackageDependency(String packageName) { + return packages.putIfAbsent(packageName, () => new PackageDependency()); + } + + /// Read the .packages file in [directory] and add referenced packages to [dependencies]. + void addDependenciesFromPackagesFileIn(Directory directory) { + final String dotPackagesPath = fs.path.join(directory.path, '.packages'); + final File dotPackages = fs.file(dotPackagesPath); + if (dotPackages.existsSync()) { + // this directory has opinions about what we should be using + dotPackages + .readAsStringSync() + .split('\n') + .where((String line) => !line.startsWith(new RegExp(r'^ *#'))) + .forEach((String line) { + final int colon = line.indexOf(':'); + if (colon > 0) { + final String packageName = line.substring(0, colon); + final String packagePath = fs.path.fromUri(line.substring(colon+1)); + // Ensure that we only add `analyzer` and dependent packages defined in the vended SDK (and referred to with a local + // fs.path. directive). Analyzer package versions reached via transitive dependencies (e.g., via `test`) are ignored + // since they would produce spurious conflicts. + if (!_vendedSdkPackages.contains(packageName) || packagePath.startsWith('..')) + add(packageName, fs.path.normalize(fs.path.absolute(directory.path, packagePath)), dotPackagesPath); + } + }); + } + } + + void addCanonicalCase(String packageName, String packagePath, String pubSpecYamlPath) { + getPackageDependency(packageName).addCanonicalCase(packagePath, pubSpecYamlPath); + } + + void add(String packageName, String packagePath, String dotPackagesPath) { + getPackageDependency(packageName).add(packagePath, dotPackagesPath); + } + + void checkForConflictingDependencies(Iterable pubSpecDirectories, PackageDependencyTracker dependencies) { + for (Directory directory in pubSpecDirectories) { + final String pubSpecYamlPath = fs.path.join(directory.path, 'pubspec.yaml'); + final File pubSpecYamlFile = fs.file(pubSpecYamlPath); + if (pubSpecYamlFile.existsSync()) { + // we are analyzing the actual canonical source for this package; + // make sure we remember that, in case all the packages are actually + // pointing elsewhere somehow. + final yaml.YamlMap pubSpecYaml = yaml.loadYaml(fs.file(pubSpecYamlPath).readAsStringSync()); + final String packageName = pubSpecYaml['name']; + final String packagePath = fs.path.normalize(fs.path.absolute(fs.path.join(directory.path, 'lib'))); + dependencies.addCanonicalCase(packageName, packagePath, pubSpecYamlPath); + } + dependencies.addDependenciesFromPackagesFileIn(directory); + } + + // prepare a union of all the .packages files + if (dependencies.hasConflicts) { + final StringBuffer message = new StringBuffer(); + message.writeln(dependencies.generateConflictReport()); + message.writeln('Make sure you have run "pub upgrade" in all the directories mentioned above.'); + if (dependencies.hasConflictsAffectingFlutterRepo) { + message.writeln( + 'For packages in the flutter repository, try using ' + '"flutter update-packages --upgrade" to do all of them at once.'); + } + message.write( + 'If this does not help, to track down the conflict you can use ' + '"pub deps --style=list" and "pub upgrade --verbosity=solver" in the affected directories.'); + throwToolExit(message.toString()); + } + } + + bool get hasConflicts { + return packages.values.any((PackageDependency dependency) => dependency.hasConflict); + } + + bool get hasConflictsAffectingFlutterRepo { + return packages.values.any((PackageDependency dependency) => dependency.hasConflictAffectingFlutterRepo); + } + + String generateConflictReport() { + assert(hasConflicts); + final StringBuffer result = new StringBuffer(); + for (String package in packages.keys.where((String package) => packages[package].hasConflict)) { + result.writeln('Package "$package" has conflicts:'); + packages[package].describeConflict(result); + } + return result.toString(); + } + + Map asPackageMap() { + final Map result = {}; + for (String package in packages.keys) + result[package] = packages[package].target; + return result; + } +} diff --git a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart index 8a170bf5d5728..dac4347a16d06 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart @@ -20,9 +20,9 @@ import '../globals.dart'; import 'analyze_base.dart'; class AnalyzeContinuously extends AnalyzeBase { - AnalyzeContinuously(ArgResults argResults, this.repoAnalysisEntryPoints) : super(argResults); + AnalyzeContinuously(ArgResults argResults, this.repoPackages) : super(argResults); - final List repoAnalysisEntryPoints; + final List repoPackages; String analysisTarget; bool firstAnalysis = true; @@ -40,7 +40,9 @@ class AnalyzeContinuously extends AnalyzeBase { throwToolExit('The --dartdocs option is currently not supported when using --watch.'); if (argResults['flutter-repo']) { - directories = repoAnalysisEntryPoints.map((Directory dir) => dir.path).toList(); + final PackageDependencyTracker dependencies = new PackageDependencyTracker(); + dependencies.checkForConflictingDependencies(repoPackages, dependencies); + directories = repoPackages.map((Directory dir) => dir.path).toList(); analysisTarget = 'Flutter repository'; printTrace('Analyzing Flutter repository:'); for (String projectPath in directories) diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart index e8c111870c934..10342507576f7 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_once.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:collection'; import 'package:args/args.dart'; -import 'package:yaml/yaml.dart' as yaml; import '../base/common.dart'; import '../base/file_system.dart'; @@ -31,9 +30,6 @@ class AnalyzeOnce extends AnalyzeBase { /// The working directory for testing analysis using dartanalyzer final Directory workingDirectory; - /// Packages whose source is defined in the vended SDK. - static const List _vendedSdkPackages = const ['analyzer', 'front_end', 'kernel']; - @override Future analyze() async { final Stopwatch stopwatch = new Stopwatch()..start(); @@ -136,56 +132,7 @@ class AnalyzeOnce extends AnalyzeBase { // determine what all the various .packages files depend on final PackageDependencyTracker dependencies = new PackageDependencyTracker(); - for (Directory directory in pubSpecDirectories) { - final String pubSpecYamlPath = fs.path.join(directory.path, 'pubspec.yaml'); - final File pubSpecYamlFile = fs.file(pubSpecYamlPath); - if (pubSpecYamlFile.existsSync()) { - // we are analyzing the actual canonical source for this package; - // make sure we remember that, in case all the packages are actually - // pointing elsewhere somehow. - final yaml.YamlMap pubSpecYaml = yaml.loadYaml(fs.file(pubSpecYamlPath).readAsStringSync()); - final String packageName = pubSpecYaml['name']; - final String packagePath = fs.path.normalize(fs.path.absolute(fs.path.join(directory.path, 'lib'))); - dependencies.addCanonicalCase(packageName, packagePath, pubSpecYamlPath); - } - final String dotPackagesPath = fs.path.join(directory.path, '.packages'); - final File dotPackages = fs.file(dotPackagesPath); - if (dotPackages.existsSync()) { - // this directory has opinions about what we should be using - dotPackages - .readAsStringSync() - .split('\n') - .where((String line) => !line.startsWith(new RegExp(r'^ *#'))) - .forEach((String line) { - final int colon = line.indexOf(':'); - if (colon > 0) { - final String packageName = line.substring(0, colon); - final String packagePath = fs.path.fromUri(line.substring(colon+1)); - // Ensure that we only add `analyzer` and dependent packages defined in the vended SDK (and referred to with a local - // fs.path. directive). Analyzer package versions reached via transitive dependencies (e.g., via `test`) are ignored - // since they would produce spurious conflicts. - if (!_vendedSdkPackages.contains(packageName) || packagePath.startsWith('..')) - dependencies.add(packageName, fs.path.normalize(fs.path.absolute(directory.path, packagePath)), dotPackagesPath); - } - }); - } - } - - // prepare a union of all the .packages files - if (dependencies.hasConflicts) { - final StringBuffer message = new StringBuffer(); - message.writeln(dependencies.generateConflictReport()); - message.writeln('Make sure you have run "pub upgrade" in all the directories mentioned above.'); - if (dependencies.hasConflictsAffectingFlutterRepo) { - message.writeln( - 'For packages in the flutter repository, try using ' - '"flutter update-packages --upgrade" to do all of them at once.'); - } - message.write( - 'If this does not help, to track down the conflict you can use ' - '"pub deps --style=list" and "pub upgrade --verbosity=solver" in the affected directories.'); - throwToolExit(message.toString()); - } + dependencies.checkForConflictingDependencies(pubSpecDirectories, dependencies); final Map packages = dependencies.asPackageMap(); Cache.releaseLockEarly(); @@ -335,92 +282,3 @@ class AnalyzeOnce extends AnalyzeBase { return collected; } } - -class PackageDependency { - // This is a map from dependency targets (lib directories) to a list - // of places that ask for that target (.packages or pubspec.yaml files) - Map> values = >{}; - String canonicalSource; - void addCanonicalCase(String packagePath, String pubSpecYamlPath) { - assert(canonicalSource == null); - add(packagePath, pubSpecYamlPath); - canonicalSource = pubSpecYamlPath; - } - void add(String packagePath, String sourcePath) { - values.putIfAbsent(packagePath, () => []).add(sourcePath); - } - bool get hasConflict => values.length > 1; - bool get hasConflictAffectingFlutterRepo { - assert(fs.path.isAbsolute(Cache.flutterRoot)); - for (List targetSources in values.values) { - for (String source in targetSources) { - assert(fs.path.isAbsolute(source)); - if (fs.path.isWithin(Cache.flutterRoot, source)) - return true; - } - } - return false; - } - void describeConflict(StringBuffer result) { - assert(hasConflict); - final List targets = values.keys.toList(); - targets.sort((String a, String b) => values[b].length.compareTo(values[a].length)); - for (String target in targets) { - final int count = values[target].length; - result.writeln(' $count ${count == 1 ? 'source wants' : 'sources want'} "$target":'); - bool canonical = false; - for (String source in values[target]) { - result.writeln(' $source'); - if (source == canonicalSource) - canonical = true; - } - if (canonical) { - result.writeln(' (This is the actual package definition, so it is considered the canonical "right answer".)'); - } - } - } - String get target => values.keys.single; -} - -class PackageDependencyTracker { - // This is a map from package names to objects that track the paths - // involved (sources and targets). - Map packages = {}; - - PackageDependency getPackageDependency(String packageName) { - return packages.putIfAbsent(packageName, () => new PackageDependency()); - } - - void addCanonicalCase(String packageName, String packagePath, String pubSpecYamlPath) { - getPackageDependency(packageName).addCanonicalCase(packagePath, pubSpecYamlPath); - } - - void add(String packageName, String packagePath, String dotPackagesPath) { - getPackageDependency(packageName).add(packagePath, dotPackagesPath); - } - - bool get hasConflicts { - return packages.values.any((PackageDependency dependency) => dependency.hasConflict); - } - - bool get hasConflictsAffectingFlutterRepo { - return packages.values.any((PackageDependency dependency) => dependency.hasConflictAffectingFlutterRepo); - } - - String generateConflictReport() { - assert(hasConflicts); - final StringBuffer result = new StringBuffer(); - for (String package in packages.keys.where((String package) => packages[package].hasConflict)) { - result.writeln('Package "$package" has conflicts:'); - packages[package].describeConflict(result); - } - return result.toString(); - } - - Map asPackageMap() { - final Map result = {}; - for (String package in packages.keys) - result[package] = packages[package].target; - return result; - } -} diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index b187a2525ff4e..408db71b15e48 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -346,7 +346,10 @@ class FlutterCommandRunner extends CommandRunner { /// Get all pub packages in the Flutter repo. List getRepoPackages() { - return _gatherProjectPaths(fs.path.absolute(Cache.flutterRoot)) + final String root = fs.path.absolute(Cache.flutterRoot); + // not bin, and not the root + return ['dev', 'examples', 'packages'] + .expand((String path) => _gatherProjectPaths(fs.path.join(root, path))) .map((String dir) => fs.directory(dir)) .toList(); } @@ -366,22 +369,6 @@ class FlutterCommandRunner extends CommandRunner { .toList(); } - /// Get the entry-points we want to analyze in the Flutter repo. - List getRepoAnalysisEntryPoints() { - final String rootPath = fs.path.absolute(Cache.flutterRoot); - final List result = [ - // not bin, and not the root - fs.directory(fs.path.join(rootPath, 'dev')), - fs.directory(fs.path.join(rootPath, 'examples')), - ]; - // And since analyzer refuses to look at paths that end in "packages/": - result.addAll( - _gatherProjectPaths(fs.path.join(rootPath, 'packages')) - .map((String path) => fs.directory(path)) - ); - return result; - } - void _checkFlutterCopy() { // If the current directory is contained by a flutter repo, check that it's // the same flutter that is currently running. From 9f344b695ddb61b061c42a205a5679a61102e8c8 Mon Sep 17 00:00:00 2001 From: gspencergoog Date: Tue, 13 Jun 2017 19:17:04 -0700 Subject: [PATCH 095/110] Adds prefix and suffix support to TextField, per Material Design spec. (#10675) * Prefix and Suffix support for TextFields * Adding Tests * Removing spurious newline. * Fixing a small problem with the test * Review Changes --- .../demo/material/text_form_field_demo.dart | 11 + .../lib/src/material/input_decorator.dart | 94 ++++++- .../test/material/text_field_test.dart | 262 +++++++++++++++++- 3 files changed, 348 insertions(+), 19 deletions(-) diff --git a/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart b/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart index d1f3f3100b822..7abfade4cafdb 100644 --- a/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart @@ -129,6 +129,7 @@ class TextFormFieldDemoState extends State { icon: const Icon(Icons.phone), hintText: 'Where can we reach you?', labelText: 'Phone Number *', + prefixText: '+1' ), keyboardType: TextInputType.phone, onSaved: (String value) { person.phoneNumber = value; }, @@ -147,6 +148,16 @@ class TextFormFieldDemoState extends State { ), maxLines: 3, ), + new TextFormField( + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: 'Salary', + prefixText: '\$', + suffixText: 'USD', + suffixStyle: const TextStyle(color: Colors.green) + ), + maxLines: 1, + ), new Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 7e5ed82e065a9..668d897a54810 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -37,6 +37,10 @@ class InputDecoration { this.errorStyle, this.isDense: false, this.hideDivider: false, + this.prefixText, + this.prefixStyle, + this.suffixText, + this.suffixStyle, }) : isCollapsed = false; /// Creates a decoration that is the same size as the input field. @@ -55,7 +59,11 @@ class InputDecoration { errorStyle = null, isDense = false, isCollapsed = true, - hideDivider = true; + hideDivider = true, + prefixText = null, + prefixStyle = null, + suffixText = null, + suffixStyle = null; /// An icon to show before the input field. /// @@ -108,7 +116,7 @@ class InputDecoration { /// If non-null the divider, that appears below the input field is red. final String errorText; - /// The style to use for the [errorText. + /// The style to use for the [errorText]. /// /// If null, defaults of a value derived from the base [TextStyle] for the /// input field and the current [Theme]. @@ -133,6 +141,28 @@ class InputDecoration { /// Defaults to false. final bool hideDivider; + /// Optional text prefix to place on the line before the input. + /// + /// Uses the [prefixStyle]. Uses [hintStyle] if [prefixStyle] isn't + /// specified. Prefix is not returned as part of the input. + final String prefixText; + + /// The style to use for the [prefixText]. + /// + /// If null, defaults to the [hintStyle]. + final TextStyle prefixStyle; + + /// Optional text suffix to place on the line after the input. + /// + /// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't + /// specified. Suffix is not returned as part of the input. + final String suffixText; + + /// The style to use for the [suffixText]. + /// + /// If null, defaults to the [hintStyle]. + final TextStyle suffixStyle; + /// Creates a copy of this input decoration but with the given fields replaced /// with the new values. /// @@ -147,6 +177,10 @@ class InputDecoration { TextStyle errorStyle, bool isDense, bool hideDivider, + String prefixText, + TextStyle prefixStyle, + String suffixText, + TextStyle suffixStyle, }) { return new InputDecoration( icon: icon ?? this.icon, @@ -158,6 +192,10 @@ class InputDecoration { errorStyle: errorStyle ?? this.errorStyle, isDense: isDense ?? this.isDense, hideDivider: hideDivider ?? this.hideDivider, + prefixText: prefixText ?? this.prefixText, + prefixStyle: prefixStyle ?? this.prefixStyle, + suffixText: suffixText ?? this.suffixText, + suffixStyle: suffixStyle ?? this.suffixStyle, ); } @@ -177,7 +215,11 @@ class InputDecoration { && typedOther.errorStyle == errorStyle && typedOther.isDense == isDense && typedOther.isCollapsed == isCollapsed - && typedOther.hideDivider == hideDivider; + && typedOther.hideDivider == hideDivider + && typedOther.prefixText == prefixText + && typedOther.prefixStyle == prefixStyle + && typedOther.suffixText == suffixText + && typedOther.suffixStyle == suffixStyle; } @override @@ -193,6 +235,10 @@ class InputDecoration { isDense, isCollapsed, hideDivider, + prefixText, + prefixStyle, + suffixText, + suffixStyle, ); } @@ -213,6 +259,14 @@ class InputDecoration { description.add('isCollapsed: $isCollapsed'); if (hideDivider) description.add('hideDivider: $hideDivider'); + if (prefixText != null) + description.add('prefixText: $prefixText'); + if (prefixStyle != null) + description.add('prefixStyle: $prefixStyle'); + if (suffixText != null) + description.add('suffixText: $suffixText'); + if (suffixStyle != null) + description.add('suffixStyle: $suffixStyle'); return 'InputDecoration(${description.join(', ')})'; } } @@ -293,7 +347,7 @@ class InputDecorator extends StatelessWidget { return themeData.hintColor; } - Widget _buildContent(Color borderColor, double topPadding, bool isDense) { + Widget _buildContent(Color borderColor, double topPadding, bool isDense, Widget inputChild) { final double bottomPadding = isDense ? 8.0 : 1.0; const double bottomBorder = 2.0; final double bottomHeight = isDense ? 14.0 : 18.0; @@ -305,7 +359,7 @@ class InputDecorator extends StatelessWidget { return new Container( margin: margin + const EdgeInsets.only(bottom: bottomBorder), padding: padding, - child: child, + child: inputChild, ); } @@ -322,7 +376,7 @@ class InputDecorator extends StatelessWidget { ), ), ), - child: child, + child: inputChild, ); } @@ -348,7 +402,7 @@ class InputDecorator extends StatelessWidget { final List stackChildren = []; - // If we're not focused, there's not value, and labelText was provided, + // If we're not focused, there's no value, and labelText was provided, // then the label appears where the hint would. And we will not show // the hintText. final bool hasInlineLabel = !isFocused && labelText != null && isEmpty; @@ -402,11 +456,33 @@ class InputDecorator extends StatelessWidget { ); } + Widget inputChild; + if (!hasInlineLabel && (!isEmpty || hintText == null) && + (decoration?.prefixText != null || decoration?.suffixText != null)) { + final List rowContents = []; + if (decoration.prefixText != null) { + rowContents.add( + new Text(decoration.prefixText, + style: decoration.prefixStyle ?? hintStyle) + ); + } + rowContents.add(new Expanded(child: child)); + if (decoration.suffixText != null) { + rowContents.add( + new Text(decoration.suffixText, + style: decoration.suffixStyle ?? hintStyle) + ); + } + inputChild = new Row(children: rowContents); + } else { + inputChild = child; + } + if (isCollapsed) { - stackChildren.add(child); + stackChildren.add(inputChild); } else { final Color borderColor = errorText == null ? activeColor : themeData.errorColor; - stackChildren.add(_buildContent(borderColor, topPadding, isDense)); + stackChildren.add(_buildContent(borderColor, topPadding, isDense, inputChild)); } if (!isDense && errorText != null) { diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 2fbdd0249c71b..7591c5cd70d4e 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -801,6 +801,248 @@ void main() { expect(hintText.style, hintStyle); }); + testWidgets('TextField with specified prefixStyle', (WidgetTester tester) async { + final TextStyle prefixStyle = new TextStyle( + color: Colors.pink[500], + fontSize: 10.0, + ); + + Widget builder() { + return new Center( + child: new Material( + child: new TextField( + decoration: new InputDecoration( + prefixText: 'Prefix:', + prefixStyle: prefixStyle, + ), + ), + ), + ); + } + + await tester.pumpWidget(builder()); + + final Text prefixText = tester.widget(find.text('Prefix:')); + expect(prefixText.style, prefixStyle); + }); + + testWidgets('TextField with specified suffixStyle', (WidgetTester tester) async { + final TextStyle suffixStyle = new TextStyle( + color: Colors.pink[500], + fontSize: 10.0, + ); + + Widget builder() { + return new Center( + child: new Material( + child: new TextField( + decoration: new InputDecoration( + suffixText: '.com', + suffixStyle: suffixStyle, + ), + ), + ), + ); + } + + await tester.pumpWidget(builder()); + + final Text suffixText = tester.widget(find.text('.com')); + expect(suffixText.style, suffixStyle); + }); + + testWidgets('TextField prefix and suffix appear correctly with no hint or label', + (WidgetTester tester) async { + final Key secondKey = new UniqueKey(); + + Widget innerBuilder() { + return new Center( + child: new Material( + child: new Column( + children: [ + const TextField( + decoration: const InputDecoration( + labelText: 'First', + ), + ), + new TextField( + key: secondKey, + decoration: const InputDecoration( + prefixText: 'Prefix', + suffixText: 'Suffix', + ), + ), + ], + ), + ), + ); + } + Widget builder() => overlay(innerBuilder()); + + await tester.pumpWidget(builder()); + + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + + // Focus the Input. The prefix should still display. + await tester.tap(find.byKey(secondKey)); + await tester.pump(); + + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + + // Enter some text, and the prefix should still display. + await tester.enterText(find.byKey(secondKey), "Hi"); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + }); + + testWidgets('TextField prefix and suffix appear correctly with hint text', + (WidgetTester tester) async { + final TextStyle hintStyle = new TextStyle( + color: Colors.pink[500], + fontSize: 10.0, + ); + final Key secondKey = new UniqueKey(); + + Widget innerBuilder() { + return new Center( + child: new Material( + child: new Column( + children: [ + const TextField( + decoration: const InputDecoration( + labelText: 'First', + ), + ), + new TextField( + key: secondKey, + decoration: new InputDecoration( + hintText: 'Hint', + hintStyle: hintStyle, + prefixText: 'Prefix', + suffixText: 'Suffix', + ), + ), + ], + ), + ), + ); + } + Widget builder() => overlay(innerBuilder()); + + await tester.pumpWidget(builder()); + + // Neither the prefix or the suffix should initially be visible, only the hint. + expect(find.text('Prefix'), findsNothing); + expect(find.text('Suffix'), findsNothing); + expect(find.text('Hint'), findsOneWidget); + + await tester.tap(find.byKey(secondKey)); + await tester.pump(); + + // Focus the Input. The hint should display, but not the prefix and suffix. + expect(find.text('Prefix'), findsNothing); + expect(find.text('Suffix'), findsNothing); + expect(find.text('Hint'), findsOneWidget); + + // Enter some text, and the hint should disappear and the prefix and suffix + // should appear. + await tester.enterText(find.byKey(secondKey), "Hi"); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + + // It's onstage, but animated to zero opacity. + expect(find.text('Hint'), findsOneWidget); + final Element target = tester.element(find.text('Hint')); + final Opacity opacity = target.ancestorWidgetOfExactType(Opacity); + expect(opacity, isNotNull); + expect(opacity.opacity, equals(0.0)); + + // Check and make sure that the right styles were applied. + final Text prefixText = tester.widget(find.text('Prefix')); + expect(prefixText.style, hintStyle); + final Text suffixText = tester.widget(find.text('Suffix')); + expect(suffixText.style, hintStyle); + }); + + testWidgets('TextField prefix and suffix appear correctly with label text', + (WidgetTester tester) async { + final TextStyle prefixStyle = new TextStyle( + color: Colors.pink[500], + fontSize: 10.0, + ); + final TextStyle suffixStyle = new TextStyle( + color: Colors.green[500], + fontSize: 12.0, + ); + final Key secondKey = new UniqueKey(); + + Widget innerBuilder() { + return new Center( + child: new Material( + child: new Column( + children: [ + const TextField( + decoration: const InputDecoration( + labelText: 'First', + ), + ), + new TextField( + key: secondKey, + decoration: new InputDecoration( + labelText: 'Label', + prefixText: 'Prefix', + prefixStyle: prefixStyle, + suffixText: 'Suffix', + suffixStyle: suffixStyle, + ), + ), + ], + ), + ), + ); + } + Widget builder() => overlay(innerBuilder()); + + await tester.pumpWidget(builder()); + + // Not focused. The prefix should not display, but the label should. + expect(find.text('Prefix'), findsNothing); + expect(find.text('Suffix'), findsNothing); + expect(find.text('Label'), findsOneWidget); + + await tester.tap(find.byKey(secondKey)); + await tester.pump(); + + // Focus the input. The label should display, and also the prefix. + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + expect(find.text('Label'), findsOneWidget); + + // Enter some text, and the label should stay and the prefix should + // remain. + await tester.enterText(find.byKey(secondKey), "Hi"); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + expect(find.text('Label'), findsOneWidget); + + // Check and make sure that the right styles were applied. + final Text prefixText = tester.widget(find.text('Prefix')); + expect(prefixText.style, prefixStyle); + final Text suffixText = tester.widget(find.text('Suffix')); + expect(suffixText.style, suffixStyle); + }); + testWidgets('TextField label text animates', (WidgetTester tester) async { final Key secondKey = new UniqueKey(); @@ -1006,7 +1248,7 @@ void main() { }); testWidgets( - 'Cannot enter new lines onto single line TextField', + 'Cannot enter new lines onto single line TextField', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); @@ -1021,13 +1263,13 @@ void main() { ); testWidgets( - 'Injected formatters are chained', + 'Injected formatters are chained', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); await tester.pumpWidget(new Material( child: new TextField( - controller: textController, + controller: textController, decoration: null, inputFormatters: [ new BlacklistingTextInputFormatter( @@ -1045,13 +1287,13 @@ void main() { ); testWidgets( - 'Chained formatters are in sequence', + 'Chained formatters are in sequence', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); await tester.pumpWidget(new Material( child: new TextField( - controller: textController, + controller: textController, decoration: null, maxLines: 2, inputFormatters: [ @@ -1067,15 +1309,15 @@ void main() { await tester.enterText(find.byType(TextField), 'a1b2c3'); // The first formatter turns it into // 12\n112\n212\n3 - // The second formatter turns it into + // The second formatter turns it into // \n1\n2\n3 - // Multiline is allowed since maxLine != 1. + // Multiline is allowed since maxLine != 1. expect(textController.text, '\n1\n2\n3'); } ); testWidgets( - 'Pasted values are formatted', + 'Pasted values are formatted', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); @@ -1083,7 +1325,7 @@ void main() { return overlay(new Center( child: new Material( child: new TextField( - controller: textController, + controller: textController, decoration: null, inputFormatters: [ WhitelistingTextInputFormatter.digitsOnly, @@ -1104,7 +1346,7 @@ void main() { await tester.tapAt(textOffsetToPosition(tester, '123'.indexOf('2'))); await tester.pumpWidget(builder()); final RenderEditable renderEditable = findRenderEditable(tester); - final List endpoints = + final List endpoints = renderEditable.getEndpointsForSelection(textController.selection); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); await tester.pumpWidget(builder()); From 1a1bbacf0d16c1780377ea77580ec3b05c387f88 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Tue, 13 Jun 2017 23:22:58 -0700 Subject: [PATCH 096/110] Bump version in preparation for new tag (#10689) https://github.com/flutter/flutter/issues/10688 --- VERSION | 2 +- packages/flutter/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 3b82e709899db..54861fbca8a17 100644 --- a/VERSION +++ b/VERSION @@ -6,4 +6,4 @@ # incompatible way, this version number might not change. Instead, the version # number for package:flutter will update to reflect that change. -0.0.8-dev +0.0.8 diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 132b05ddba1d1..01b83eae9251a 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter -version: 0.0.28-dev +version: 0.0.28 author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 59d1130b2953b..ff8d1cbbd4cf1 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_driver -version: 0.0.6-dev +version: 0.0.6 description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 101eec854b887..3884327336673 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_test -version: 0.0.6-dev +version: 0.0.6 dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so From 7158646ba7992424171823c18b6cb0cdf35c3206 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 14 Jun 2017 08:08:24 -0700 Subject: [PATCH 097/110] [accessibility] Announce index of tab in tab bar (#10664) * [accessibility] Announce index if tab in tab bar * added TODO --- packages/flutter/lib/src/material/tabs.dart | 21 ++++++++++++------- packages/flutter/test/material/tabs_test.dart | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 25bc23206f90a..eb7b9e7a94649 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -729,14 +729,21 @@ class _TabBarState extends State { // Add the tap handler to each tab. If the tab bar is scrollable // then give all of the tabs equal flexibility so that their widths // reflect the intrinsic width of their labels. - for (int index = 0; index < widget.tabs.length; index++) { + final int tabCount = widget.tabs.length; + for (int index = 0; index < tabCount; index++) { wrappedTabs[index] = new MergeSemantics( - child: new Semantics( - selected: index == _currentIndex, - child: new InkWell( - onTap: () { _handleTap(index); }, - child: wrappedTabs[index], - ), + child: new Stack( + children: [ + new InkWell( + onTap: () { _handleTap(index); }, + child: wrappedTabs[index], + ), + new Semantics( + selected: index == _currentIndex, + // TODO(goderbauer): I10N-ify + label: 'Tab ${index + 1} of $tabCount', + ), + ], ), ); if (!widget.isScrollable) diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 47c32922e09fa..7e9c0221a3ef9 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -940,14 +940,14 @@ void main() { id: 2, actions: SemanticsAction.tap.index, flags: SemanticsFlags.isSelected.index, - label: 'TAB #0', + label: 'TAB #0\nTab 1 of 2', rect: new Rect.fromLTRB(0.0, 0.0, 108.0, 46.0), transform: new Matrix4.translationValues(0.0, 276.0, 0.0), ), new TestSemantics( - id: 4, + id: 5, actions: SemanticsAction.tap.index, - label: 'TAB #1', + label: 'TAB #1\nTab 2 of 2', rect: new Rect.fromLTRB(0.0, 0.0, 108.0, 46.0), transform: new Matrix4.translationValues(108.0, 276.0, 0.0), ), From e2d4f9242e5053f72b20feeb54d21d6ddb3715c8 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 14 Jun 2017 08:09:05 -0700 Subject: [PATCH 098/110] Benchmark for semantic overhead during transitions (#10678) * Benchmark for semantic overhead during transitions * review comments --- ...llery__transition_perf_with_semantics.dart | 29 +++++++++++++++++++ dev/devicelab/lib/tasks/gallery.dart | 14 +++++++-- dev/devicelab/manifest.yaml | 8 +++++ .../test_driver/transitions_perf_test.dart | 6 +++- .../transitions_perf_with_semantics.dart | 9 ++++++ .../transitions_perf_with_semantics_test.dart | 9 ++++++ 6 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 dev/devicelab/bin/tasks/flutter_gallery__transition_perf_with_semantics.dart create mode 100644 examples/flutter_gallery/test_driver/transitions_perf_with_semantics.dart create mode 100644 examples/flutter_gallery/test_driver/transitions_perf_with_semantics_test.dart diff --git a/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_with_semantics.dart b/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_with_semantics.dart new file mode 100644 index 0000000000000..27c61713cd5bb --- /dev/null +++ b/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_with_semantics.dart @@ -0,0 +1,29 @@ +// Copyright 2017 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 'package:flutter_devicelab/tasks/gallery.dart'; +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(() async { + final TaskResult withoutSemantics = await createGalleryTransitionTest()(); + final TaskResult withSemantics = await createGalleryTransitionTest(semanticsEnabled: true)(); + + final List benchmarkScoreKeys = []; + final Map data = {}; + for (String key in withSemantics.benchmarkScoreKeys) { + final String deltaKey = 'delta_$key'; + data[deltaKey] = withSemantics.data[key] - withoutSemantics.data[key]; + data['semantics_$key'] = withSemantics.data[key]; + data[key] = withoutSemantics.data[key]; + benchmarkScoreKeys.add(deltaKey); + } + + return new TaskResult.success(data, benchmarkScoreKeys: benchmarkScoreKeys); + }); +} diff --git a/dev/devicelab/lib/tasks/gallery.dart b/dev/devicelab/lib/tasks/gallery.dart index 6a36e00e6ab9d..b0730033bd35e 100644 --- a/dev/devicelab/lib/tasks/gallery.dart +++ b/dev/devicelab/lib/tasks/gallery.dart @@ -12,12 +12,16 @@ import '../framework/framework.dart'; import '../framework/ios.dart'; import '../framework/utils.dart'; -TaskFunction createGalleryTransitionTest() { - return new GalleryTransitionTest(); +TaskFunction createGalleryTransitionTest({ bool semanticsEnabled: false }) { + return new GalleryTransitionTest(semanticsEnabled: semanticsEnabled); } class GalleryTransitionTest { + GalleryTransitionTest({ this.semanticsEnabled: false }); + + final bool semanticsEnabled; + Future call() async { final Device device = await devices.workingDevice; await device.unlock(); @@ -33,11 +37,15 @@ class GalleryTransitionTest { await flutter('build', options: ['ios', '--profile']); } + final String testDriver = semanticsEnabled + ? 'transitions_perf_with_semantics.dart' + : 'transitions_perf.dart'; + await flutter('drive', options: [ '--profile', '--trace-startup', '-t', - 'test_driver/transitions_perf.dart', + 'test_driver/$testDriver', '-d', deviceId, ]); diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index a67b88997dc17..a8fab3f43997b 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -274,6 +274,14 @@ tasks: stage: devicelab required_agent_capabilities: ["linux/android"] + flutter_gallery__transition_perf_with_semantics: + description: > + Measures the delta in performance of screen transitions without and + with semantics enabled. + stage: devicelab + required_agent_capabilities: ["linux/android"] + flaky: true + flutter_gallery__memory_nav: description: > Measures memory usage after repeated navigation in Gallery. diff --git a/examples/flutter_gallery/test_driver/transitions_perf_test.dart b/examples/flutter_gallery/test_driver/transitions_perf_test.dart index 827ee70055782..614a2b31fc494 100644 --- a/examples/flutter_gallery/test_driver/transitions_perf_test.dart +++ b/examples/flutter_gallery/test_driver/transitions_perf_test.dart @@ -174,11 +174,15 @@ Future runDemos(Iterable demos, FlutterDriver driver) async { } } -void main() { +void main(List args) { group('flutter gallery transitions', () { FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); + if (args.contains('--with_semantics')) { + print('Enabeling semantics...'); + await driver.setSemantics(true); + } }); tearDownAll(() async { diff --git a/examples/flutter_gallery/test_driver/transitions_perf_with_semantics.dart b/examples/flutter_gallery/test_driver/transitions_perf_with_semantics.dart new file mode 100644 index 0000000000000..02879b4871129 --- /dev/null +++ b/examples/flutter_gallery/test_driver/transitions_perf_with_semantics.dart @@ -0,0 +1,9 @@ +// Copyright 2017 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 'transitions_perf.dart' as transitions_perf; + +void main() { + transitions_perf.main(); +} diff --git a/examples/flutter_gallery/test_driver/transitions_perf_with_semantics_test.dart b/examples/flutter_gallery/test_driver/transitions_perf_with_semantics_test.dart new file mode 100644 index 0000000000000..afa8235eddd3d --- /dev/null +++ b/examples/flutter_gallery/test_driver/transitions_perf_with_semantics_test.dart @@ -0,0 +1,9 @@ +// Copyright 2017 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 'transitions_perf_test.dart' as transitions_perf_test; + +void main() { + transitions_perf_test.main(['--with_semantics']); +} From e5213b8e0a7b9a114ab43dfc41d9c83990046625 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Wed, 14 Jun 2017 08:10:11 -0700 Subject: [PATCH 099/110] Bump versions to `-dev` to complete the release of alpha (#10692) https://github.com/flutter/flutter/issues/10688 --- VERSION | 2 +- packages/flutter/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 54861fbca8a17..1baabf057b2b0 100644 --- a/VERSION +++ b/VERSION @@ -6,4 +6,4 @@ # incompatible way, this version number might not change. Instead, the version # number for package:flutter will update to reflect that change. -0.0.8 +0.0.9-dev diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 01b83eae9251a..b046ad3489157 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter -version: 0.0.28 +version: 0.0.29-dev author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index ff8d1cbbd4cf1..cfd69c4753fd6 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_driver -version: 0.0.6 +version: 0.0.7-dev description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 3884327336673..a8b16a9a3a688 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_test -version: 0.0.6 +version: 0.0.7-dev dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so From ede575a92ebd2103fdc38d649755ea8b49b5e6e2 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 14 Jun 2017 09:05:53 -0700 Subject: [PATCH 100/110] Refactor sample catalog screenshot production (#10676) --- .../lib/tasks/sample_catalog_generator.dart | 16 ++++++---- .../lib/tasks/save_catalog_screenshots.dart | 29 +++++++++++-------- dev/devicelab/manifest.yaml | 2 +- dev/devicelab/pubspec.yaml | 1 + examples/catalog/bin/sample_page.dart | 2 +- .../catalog/bin/screenshot_test.dart.template | 1 - examples/catalog/pubspec.yaml | 1 - 7 files changed, 30 insertions(+), 22 deletions(-) rename examples/catalog/bin/save_screenshots.dart => dev/devicelab/lib/tasks/save_catalog_screenshots.dart (84%) diff --git a/dev/devicelab/lib/tasks/sample_catalog_generator.dart b/dev/devicelab/lib/tasks/sample_catalog_generator.dart index 8b005d11794a7..c3a73d698cdfa 100644 --- a/dev/devicelab/lib/tasks/sample_catalog_generator.dart +++ b/dev/devicelab/lib/tasks/sample_catalog_generator.dart @@ -9,6 +9,8 @@ import '../framework/adb.dart'; import '../framework/framework.dart'; import '../framework/ios.dart'; import '../framework/utils.dart'; +import 'save_catalog_screenshots.dart' show saveCatalogScreenshots; + Future samplePageCatalogGenerator(String authorizationToken) async { final Device device = await devices.workingDevice; @@ -19,7 +21,8 @@ Future samplePageCatalogGenerator(String authorizationToken) async { await inDirectory(catalogDirectory, () async { await flutter('packages', options: ['get']); - if (deviceOperatingSystem == DeviceOperatingSystem.ios) + final bool isIosDevice = deviceOperatingSystem == DeviceOperatingSystem.ios; + if (isIosDevice) await prepareProvisioningCertificates(catalogDirectory.path); await dart(['bin/sample_page.dart']); @@ -31,11 +34,12 @@ Future samplePageCatalogGenerator(String authorizationToken) async { deviceId, ]); - await dart([ - 'bin/save_screenshots.dart', - await getCurrentFlutterRepoCommit(), - authorizationToken, - ]); + await saveCatalogScreenshots( + directory: dir('${flutterDirectory.path}/examples/catalog/.generated'), + commit: await getCurrentFlutterRepoCommit(), + token: authorizationToken, + prefix: isIosDevice ? 'ios_' : '', + ); }); return new TaskResult.success(null); diff --git a/examples/catalog/bin/save_screenshots.dart b/dev/devicelab/lib/tasks/save_catalog_screenshots.dart similarity index 84% rename from examples/catalog/bin/save_screenshots.dart rename to dev/devicelab/lib/tasks/save_catalog_screenshots.dart index fd78f02c0784e..661266699f22c 100644 --- a/examples/catalog/bin/save_screenshots.dart +++ b/dev/devicelab/lib/tasks/save_catalog_screenshots.dart @@ -1,3 +1,7 @@ +// Copyright 2017 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:convert'; import 'dart:io'; @@ -56,6 +60,7 @@ class Upload { final HttpClientResponse response = await request.close().timeout(timeLimit); if (response.statusCode == HttpStatus.OK) { + logMessage('Saved $name'); await response.drain(); } else { // TODO(hansmuller): only retry on 5xx and 429 responses @@ -96,40 +101,40 @@ Future saveScreenshots(List fromPaths, List largeNames, Li for (int index = 0; index < uploads.length; index += 1) uploads[index] = new Upload(fromPaths[index], largeNames[index], smallNames[index]); - final HttpClient client = new HttpClient(); while(uploads.any(Upload.isNotComplete)) { + final HttpClient client = new HttpClient(); uploads = uploads.where(Upload.isNotComplete).toList(); await Future.wait(uploads.map((Upload upload) => upload.run(client))); + client.close(force: true); } - client.close(); } // If path is lib/foo.png then screenshotName is foo. String screenshotName(String path) => basenameWithoutExtension(path); -Future main(List args) async { - if (args.length != 2) - throw new UploadError('Usage: dart bin/save_screenshots.dart commit authorization'); - - final Directory outputDirectory = new Directory('.generated'); +Future saveCatalogScreenshots({ + Directory directory, // Where the *.png screenshots are. + String commit, // The commit hash to be used as a cloud storage "directory". + String token, // Cloud storage authorization token. + String prefix, // Prefix for all file names. + }) async { final List screenshots = []; - outputDirectory.listSync().forEach((FileSystemEntity entity) { + directory.listSync().forEach((FileSystemEntity entity) { if (entity is File && entity.path.endsWith('.png')) { final File file = entity; screenshots.add(file.path); } }); - final String commit = args[0]; final List largeNames = []; // Cloud storage names for the full res screenshots. final List smallNames = []; // Likewise for the scaled down screenshots. for (String path in screenshots) { final String name = screenshotName(path); - largeNames.add('$commit/$name.png'); - smallNames.add('$commit/${name}_small.png'); + largeNames.add('$commit/$prefix$name.png'); + smallNames.add('$commit/$prefix${name}_small.png'); } - authorizationToken = args[1]; + authorizationToken = token; await saveScreenshots(screenshots, largeNames, smallNames); } diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index a8fab3f43997b..c455fc220f8bb 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -118,7 +118,7 @@ tasks: description: > Builds sample catalog markdown pages and Android screenshots stage: devicelab - required_agent_capabilities: ["linux/android"] + required_agent_capabilities: ["has-android-device"] flaky: true complex_layout_semantics_perf: diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 141f8eeee70f2..d2a2397605d72 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: args: ^0.13.4 + image: ^1.1.27 meta: ^1.0.5 path: ^1.4.0 process: 2.0.3 diff --git a/examples/catalog/bin/sample_page.dart b/examples/catalog/bin/sample_page.dart index c9f1ac14cd9bd..b790330737921 100644 --- a/examples/catalog/bin/sample_page.dart +++ b/examples/catalog/bin/sample_page.dart @@ -180,7 +180,7 @@ void generate() { screenshotDriverTemplate, { 'paths': samples.map((SampleGenerator sample) { - return "'${outputFile('\${prefix}' + sample.sourceName + '.png').path}'"; + return "'${outputFile(sample.sourceName + '.png').path}'"; }).toList().join(',\n'), }, ); diff --git a/examples/catalog/bin/screenshot_test.dart.template b/examples/catalog/bin/screenshot_test.dart.template index 4457b0617e4b3..2b283773d3d0e 100644 --- a/examples/catalog/bin/screenshot_test.dart.template +++ b/examples/catalog/bin/screenshot_test.dart.template @@ -8,7 +8,6 @@ import 'package:test/test.dart'; void main() { group('sample screenshots', () { - final String prefix = Platform.isMacOS ? 'ios_' : ""; FlutterDriver driver; setUpAll(() async { diff --git a/examples/catalog/pubspec.yaml b/examples/catalog/pubspec.yaml index 3c2aec5e80b64..bca57f4a43d3e 100644 --- a/examples/catalog/pubspec.yaml +++ b/examples/catalog/pubspec.yaml @@ -3,7 +3,6 @@ description: A collection of Flutter sample apps dependencies: flutter: sdk: flutter - image: path: ^1.4.0 dev_dependencies: From 7ada46677bb8d5a8f9db3fc967f7be57247dfef4 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 14 Jun 2017 11:25:03 -0700 Subject: [PATCH 101/110] TabPageSelector colors and indicatorSize (#10665) --- packages/flutter/lib/src/material/tabs.dart | 71 +++++++++++---- .../test/material/page_selector_test.dart | 90 ++++++++++++++----- 2 files changed, 119 insertions(+), 42 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index eb7b9e7a94649..796ca9ebc4f3d 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -961,12 +961,19 @@ class _TabBarViewState extends State { } } -/// Displays a single 12x12 circle with the specified border and background colors. +/// Displays a single circle with the specified border and background colors. /// /// Used by [TabPageSelector] to indicate the selected page. class TabPageSelectorIndicator extends StatelessWidget { /// Creates an indicator used by [TabPageSelector]. - const TabPageSelectorIndicator({ Key key, this.backgroundColor, this.borderColor }) : super(key: key); + /// + /// The [backgroundColor], [borderColor], and [size] parameters cannot be null. + const TabPageSelectorIndicator({ + Key key, + @required this.backgroundColor, + @required this.borderColor, + @required this.size, + }) : assert(backgroundColor != null), assert(borderColor != null), assert(size != null), super(key: key); /// The indicator circle's background color. final Color backgroundColor; @@ -974,11 +981,14 @@ class TabPageSelectorIndicator extends StatelessWidget { /// The indicator circle's border color. final Color borderColor; + /// The indicator circle's diameter. + final double size; + @override Widget build(BuildContext context) { return new Container( - width: 12.0, - height: 12.0, + width: size, + height: size, margin: const EdgeInsets.all(4.0), decoration: new BoxDecoration( color: backgroundColor, @@ -996,7 +1006,13 @@ class TabPageSelectorIndicator extends StatelessWidget { /// ancestor. class TabPageSelector extends StatelessWidget { /// Creates a compact widget that indicates which tab has been selected. - const TabPageSelector({ Key key, this.controller }) : super(key: key); + const TabPageSelector({ + Key key, + this.controller, + this.indicatorSize: 12.0, + this.color, + this.selectedColor, + }) : assert(indicatorSize != null && indicatorSize > 0.0), super(key: key); /// This widget's selection and animation state. /// @@ -1004,47 +1020,64 @@ class TabPageSelector extends StatelessWidget { /// will be used. final TabController controller; + /// The indicator circle's diameter (the default value is 12.0). + final double indicatorSize; + + /// The indicator cicle's fill color for unselected pages. + /// + /// If this parameter is null then the indicator is filled with [Colors.transparent]. + final Color color; + + /// The indicator cicle's fill color for selected pages and border color + /// for all indicator circles. + /// + /// If this parameter is null then the indicator is filled with the theme's + /// accent color, [ThemeData.accentColor]. + final Color selectedColor; + Widget _buildTabIndicator( int tabIndex, TabController tabController, - ColorTween selectedColor, - ColorTween previousColor, + ColorTween selectedColorTween, + ColorTween previousColorTween, ) { Color background; if (tabController.indexIsChanging) { // The selection's animation is animating from previousValue to value. final double t = 1.0 - _indexChangeProgress(tabController); if (tabController.index == tabIndex) - background = selectedColor.lerp(t); + background = selectedColorTween.lerp(t); else if (tabController.previousIndex == tabIndex) - background = previousColor.lerp(t); + background = previousColorTween.lerp(t); else - background = selectedColor.begin; + background = selectedColorTween.begin; } else { // The selection's offset reflects how far the TabBarView has /// been dragged to the left (-1.0 to 0.0) or the right (0.0 to 1.0). final double offset = tabController.offset; if (tabController.index == tabIndex) { - background = selectedColor.lerp(1.0 - offset.abs()); + background = selectedColorTween.lerp(1.0 - offset.abs()); } else if (tabController.index == tabIndex - 1 && offset > 0.0) { - background = selectedColor.lerp(offset); + background = selectedColorTween.lerp(offset); } else if (tabController.index == tabIndex + 1 && offset < 0.0) { - background = selectedColor.lerp(-offset); + background = selectedColorTween.lerp(-offset); } else { - background = selectedColor.begin; + background = selectedColorTween.begin; } } return new TabPageSelectorIndicator( backgroundColor: background, - borderColor: selectedColor.end, + borderColor: selectedColorTween.end, + size: indicatorSize, ); } @override Widget build(BuildContext context) { - final Color color = Theme.of(context).accentColor; - final ColorTween selectedColor = new ColorTween(begin: Colors.transparent, end: color); - final ColorTween previousColor = new ColorTween(begin: color, end: Colors.transparent); + final Color fixColor = color ?? Colors.transparent; + final Color fixSelectedColor = selectedColor ?? Theme.of(context).accentColor; + final ColorTween selectedColorTween = new ColorTween(begin: fixColor, end: fixSelectedColor); + final ColorTween previousColorTween = new ColorTween(begin: fixSelectedColor, end: fixColor); final TabController tabController = controller ?? DefaultTabController.of(context); assert(() { if (tabController == null) { @@ -1070,7 +1103,7 @@ class TabPageSelector extends StatelessWidget { child: new Row( mainAxisSize: MainAxisSize.min, children: new List.generate(tabController.length, (int tabIndex) { - return _buildTabIndicator(tabIndex, tabController, selectedColor, previousColor); + return _buildTabIndicator(tabIndex, tabController, selectedColorTween, previousColorTween); }).toList(), ), ); diff --git a/packages/flutter/test/material/page_selector_test.dart b/packages/flutter/test/material/page_selector_test.dart index 814b7a89a284e..ca1a65e7c44cc 100644 --- a/packages/flutter/test/material/page_selector_test.dart +++ b/packages/flutter/test/material/page_selector_test.dart @@ -5,12 +5,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; -const Color selectedColor = const Color(0xFF00FF00); -const Color unselectedColor = Colors.transparent; +const Color kSelectedColor = const Color(0xFF00FF00); +const Color kUnselectedColor = Colors.transparent; -Widget buildFrame(TabController tabController) { +Widget buildFrame(TabController tabController, { Color color, Color selectedColor, double indicatorSize: 12.0 }) { return new Theme( - data: new ThemeData(accentColor: selectedColor), + data: new ThemeData(accentColor: kSelectedColor), child: new SizedBox.expand( child: new Center( child: new SizedBox( @@ -18,7 +18,12 @@ Widget buildFrame(TabController tabController) { height: 400.0, child: new Column( children: [ - new TabPageSelector(controller: tabController), + new TabPageSelector( + controller: tabController, + color: color, + selectedColor: selectedColor, + indicatorSize: indicatorSize, + ), new Flexible( child: new TabBarView( controller: tabController, @@ -56,17 +61,17 @@ void main() { await tester.pumpWidget(buildFrame(tabController)); expect(tabController.index, 0); - expect(indicatorColors(tester), const [selectedColor, unselectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kSelectedColor, kUnselectedColor, kUnselectedColor]); tabController.index = 1; await tester.pump(); expect(tabController.index, 1); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); tabController.index = 2; await tester.pump(); expect(tabController.index, 2); - expect(indicatorColors(tester), const [unselectedColor, unselectedColor, selectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kUnselectedColor, kSelectedColor]); }); testWidgets('PageSelector responds correctly to TabController.animateTo()', (WidgetTester tester) async { @@ -77,7 +82,7 @@ void main() { await tester.pumpWidget(buildFrame(tabController)); expect(tabController.index, 0); - expect(indicatorColors(tester), const [selectedColor, unselectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kSelectedColor, kUnselectedColor, kUnselectedColor]); tabController.animateTo(1, duration: const Duration(milliseconds: 200)); await tester.pump(); @@ -87,14 +92,14 @@ void main() { await tester.pump(const Duration(milliseconds: 10)); List colors = indicatorColors(tester); expect(colors[0].alpha, greaterThan(colors[1].alpha)); - expect(colors[2], unselectedColor); + expect(colors[2], kUnselectedColor); await tester.pump(const Duration(milliseconds: 175)); colors = indicatorColors(tester); expect(colors[0].alpha, lessThan(colors[1].alpha)); - expect(colors[2], unselectedColor); + expect(colors[2], kUnselectedColor); await tester.pumpAndSettle(); expect(tabController.index, 1); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); tabController.animateTo(2, duration: const Duration(milliseconds: 200)); await tester.pump(); @@ -102,14 +107,14 @@ void main() { await tester.pump(const Duration(milliseconds: 10)); colors = indicatorColors(tester); expect(colors[1].alpha, greaterThan(colors[2].alpha)); - expect(colors[0], unselectedColor); + expect(colors[0], kUnselectedColor); await tester.pump(const Duration(milliseconds: 175)); colors = indicatorColors(tester); expect(colors[1].alpha, lessThan(colors[2].alpha)); - expect(colors[0], unselectedColor); + expect(colors[0], kUnselectedColor); await tester.pumpAndSettle(); expect(tabController.index, 2); - expect(indicatorColors(tester), const [unselectedColor, unselectedColor, selectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kUnselectedColor, kSelectedColor]); }); testWidgets('PageSelector responds correctly to TabBarView drags', (WidgetTester tester) async { @@ -121,7 +126,7 @@ void main() { await tester.pumpWidget(buildFrame(tabController)); expect(tabController.index, 1); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0)); @@ -131,13 +136,13 @@ void main() { await tester.pumpAndSettle(); List colors = indicatorColors(tester); expect(colors[1].alpha, greaterThan(colors[2].alpha)); - expect(colors[0], unselectedColor); + expect(colors[0], kUnselectedColor); // Drag back to where we started. await gesture.moveBy(const Offset(100.0, 0.0)); await tester.pumpAndSettle(); colors = indicatorColors(tester); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); // Drag to the left moving the selection towards indicator 0. Indicator 0's // opacity should increase and Indicator 1's opacity should decrease. @@ -145,30 +150,69 @@ void main() { await tester.pumpAndSettle(); colors = indicatorColors(tester); expect(colors[1].alpha, greaterThan(colors[0].alpha)); - expect(colors[2], unselectedColor); + expect(colors[2], kUnselectedColor); // Drag back to where we started. await gesture.moveBy(const Offset(-100.0, 0.0)); await tester.pumpAndSettle(); colors = indicatorColors(tester); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); // Completing the gesture doesn't change anything await gesture.up(); await tester.pumpAndSettle(); colors = indicatorColors(tester); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); // Fling to the left, selects indicator 2 await tester.fling(find.byType(TabBarView), const Offset(-100.0, 0.0), 1000.0); await tester.pumpAndSettle(); - expect(indicatorColors(tester), const [unselectedColor, unselectedColor, selectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kUnselectedColor, kSelectedColor]); // Fling to the right, selects indicator 1 await tester.fling(find.byType(TabBarView), const Offset(100.0, 0.0), 1000.0); await tester.pumpAndSettle(); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); + + }); + + testWidgets('PageSelector indicatorColors', (WidgetTester tester) async { + const Color kRed = const Color(0xFFFF0000); + const Color kBlue = const Color(0xFF0000FF); + + final TabController tabController = new TabController( + vsync: const TestVSync(), + initialIndex: 1, + length: 3, + ); + await tester.pumpWidget(buildFrame(tabController, color: kRed, selectedColor: kBlue)); + + expect(tabController.index, 1); + expect(indicatorColors(tester), const [kRed, kBlue, kRed]); + + tabController.index = 0; + await tester.pumpAndSettle(); + expect(indicatorColors(tester), const [kBlue, kRed, kRed]); + }); + + testWidgets('PageSelector indicatorSize', (WidgetTester tester) async { + final TabController tabController = new TabController( + vsync: const TestVSync(), + initialIndex: 1, + length: 3, + ); + await tester.pumpWidget(buildFrame(tabController, indicatorSize: 16.0)); + + final Iterable indicatorElements = find.descendant( + of: find.byType(TabPageSelector), + matching: find.byType(TabPageSelectorIndicator), + ).evaluate(); + + // Indicators get an 8 pixel margin, 16 + 8 = 24. + for (Element indicatorElement in indicatorElements) + expect(indicatorElement.size, const Size(24.0, 24.0)); + expect(tester.getSize(find.byType(TabPageSelector)).height, 24.0); }); } From 480d6286355a656b00ad4df8c4f822ef813bf437 Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 14 Jun 2017 16:26:52 -0700 Subject: [PATCH 102/110] mark Linux tasks as stable (#10410) --- dev/devicelab/manifest.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index c455fc220f8bb..5eb0fd94a4fae 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -238,21 +238,18 @@ tasks: Measures the performance of Dart VM hot patching feature on a Linux host. stage: devicelab required_agent_capabilities: ["linux/android"] - flaky: true dartdocs: description: > Tracks how many members are still lacking documentation. stage: devicelab required_agent_capabilities: ["linux/android"] - flaky: true technical_debt__cost: description: > Estimates our technical debt (TODOs, analyzer ignores, etc). stage: devicelab required_agent_capabilities: ["linux/android"] - flaky: true flutter_gallery__build: description: > From d8cb16418b11d47a3c05d7c49b564089d064942a Mon Sep 17 00:00:00 2001 From: Seth Ladd Date: Wed, 14 Jun 2017 16:29:18 -0700 Subject: [PATCH 103/110] upload master and alpha docs to different hosts (#10707) * upload master and alpha docs to different hosts * include robots.txt when master --- dev/bots/docs.sh | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh index 508a092d31a75..4d9de3f85bae6 100755 --- a/dev/bots/docs.sh +++ b/dev/bots/docs.sh @@ -19,13 +19,24 @@ FLUTTER_ROOT=$PWD bin/cache/dart-sdk/bin/dart dev/tools/javadoc.dart # Ensure google webmaster tools can verify our site. cp dev/docs/google2ed1af765c529f57.html dev/docs/doc -# Upload new API docs when on Travis and branch is master. -if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_BRANCH" = "master" ]; then - cd dev/docs - firebase deploy --project docs-flutter-io - exit_code=$? - if [[ $exit_code -ne 0 ]]; then +# Upload new API docs when on Travis +if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + if [ "$TRAVIS_BRANCH" == "master" -o "$TRAVIS_BRANCH" == "alpha" ]; then + cd dev/docs + + if [ "$TRAVIS_BRANCH" == "master" ]; then + echo -e "User-agent: *\nDisallow: /" > doc/robots.txt + firebase deploy --project master-docs-flutter-io + fi + + if [ "$TRAVIS_BRANCH" == "alpha" ]; then + firebase deploy --project docs-flutter-io + fi + + exit_code=$? + if [[ $exit_code -ne 0 ]]; then >&2 echo "Error deploying docs via firebase ($exit_code)" exit $exit_code + fi fi fi From d3f960824778f191530fed39e808477be524ce2f Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 14 Jun 2017 17:10:56 -0700 Subject: [PATCH 104/110] roll engine to 18fdfb86bb3876fcbb4e1d25e5b2aad0c5cd669f (#10711) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 8171842911ff5..c63693caabebd 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -4f5d6fab1187c1f5749a52ab5b4277d1bd51ee79 +18fdfb86bb3876fcbb4e1d25e5b2aad0c5cd669f From 6f77b4a9a76e57c4e6d23e61ba59a0cdd80d22b7 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 14 Jun 2017 17:27:08 -0700 Subject: [PATCH 105/110] Prepare Alpha 0.0.9 (#10712) --- VERSION | 2 +- packages/flutter/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 1baabf057b2b0..c2e0e0c7439d4 100644 --- a/VERSION +++ b/VERSION @@ -6,4 +6,4 @@ # incompatible way, this version number might not change. Instead, the version # number for package:flutter will update to reflect that change. -0.0.9-dev +0.0.9 diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index b046ad3489157..55f0e33acb1d8 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter -version: 0.0.29-dev +version: 0.0.29 author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index cfd69c4753fd6..90fae2b494816 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_driver -version: 0.0.7-dev +version: 0.0.7 description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index a8b16a9a3a688..5c195673f3561 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_test -version: 0.0.7-dev +version: 0.0.7 dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so From 44126cd9b74078b4346b7dbb54bf5a756d814c07 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 14 Jun 2017 21:19:10 -0700 Subject: [PATCH 106/110] Increment versions with -dev prefix (#10715) --- VERSION | 2 +- packages/flutter/pubspec.yaml | 2 +- packages/flutter_driver/pubspec.yaml | 2 +- packages/flutter_test/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index c2e0e0c7439d4..321598098eff4 100644 --- a/VERSION +++ b/VERSION @@ -6,4 +6,4 @@ # incompatible way, this version number might not change. Instead, the version # number for package:flutter will update to reflect that change. -0.0.9 +0.0.10-dev diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 55f0e33acb1d8..095456c136a82 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter -version: 0.0.29 +version: 0.0.30-dev author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 90fae2b494816..3f293665f6024 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_driver -version: 0.0.7 +version: 0.0.8-dev description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 5c195673f3561..42d1db7a8306d 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_test -version: 0.0.7 +version: 0.0.8-dev dependencies: # The flutter tools depend on very specific internal implementation # details of the 'test' package, which change between versions, so From 36c3a962c56fec14e572599e603a4c9e6e28d5bb Mon Sep 17 00:00:00 2001 From: xster Date: Wed, 14 Jun 2017 23:33:14 -0700 Subject: [PATCH 107/110] Create a CupertinoPageRoute (#10686) * started copying stuff into cupertino page route * extracted from material page route. Ready for testing * works with button and gesture * tests and docs * review notes * review notes --- packages/flutter/lib/cupertino.dart | 1 + packages/flutter/lib/src/cupertino/page.dart | 280 +++++++++++++----- packages/flutter/lib/src/material/page.dart | 76 ++--- packages/flutter/lib/src/widgets/pages.dart | 11 +- .../cupertino/activity_indicator_test.dart | 1 - .../test/cupertino/bottom_tab_bar_test.dart | 1 - .../flutter/test/cupertino/button_test.dart | 1 - .../flutter/test/cupertino/nav_bar_test.dart | 1 - .../flutter/test/cupertino/page_test.dart | 144 +++++++++ .../flutter/test/cupertino/scaffold_test.dart | 1 - .../flutter/test/cupertino/switch_test.dart | 1 - packages/flutter/test/material/page_test.dart | 79 ++++- 12 files changed, 470 insertions(+), 127 deletions(-) create mode 100644 packages/flutter/test/cupertino/page_test.dart diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index 064ad2b089a6c..6ec803a7f4f64 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -18,3 +18,4 @@ export 'src/cupertino/scaffold.dart'; export 'src/cupertino/slider.dart'; export 'src/cupertino/switch.dart'; export 'src/cupertino/thumb_painter.dart'; +export 'widgets.dart'; diff --git a/packages/flutter/lib/src/cupertino/page.dart b/packages/flutter/lib/src/cupertino/page.dart index 2e5ab53a6ac25..b2aa609bb0db3 100644 --- a/packages/flutter/lib/src/cupertino/page.dart +++ b/packages/flutter/lib/src/cupertino/page.dart @@ -46,95 +46,150 @@ final DecorationTween _kGradientShadowTween = new DecorationTween( ), ); -/// A custom [Decoration] used to paint an extra shadow on the left edge of the -/// box it's decorating. It's like a [BoxDecoration] with only a gradient except -/// it paints to the left of the box instead of behind the box. -class _CupertinoEdgeShadowDecoration extends Decoration { - const _CupertinoEdgeShadowDecoration({ this.edgeGradient }); - - /// A Decoration with no decorating properties. - static const _CupertinoEdgeShadowDecoration none = - const _CupertinoEdgeShadowDecoration(); +/// A modal route that replaces the entire screen with an iOS transition. +/// +/// The page slides in from the right and exits in reverse. +/// The page also shifts to the left in parallax when another page enters to cover it. +/// +/// The page slides in from the bottom and exits in reverse with no parallax effect +/// for fullscreen dialogs. +/// +/// By default, when a modal route is replaced by another, the previous route +/// remains in memory. To free all the resources when this is not necessary, set +/// [maintainState] to false. +/// +/// See also: +/// +/// * [MaterialPageRoute] for an adaptive [PageRoute] that uses a platform appropriate transition. +class CupertinoPageRoute extends PageRoute { + /// Creates a page route for use in an iOS designed app. + CupertinoPageRoute({ + @required this.builder, + RouteSettings settings: const RouteSettings(), + this.maintainState: true, + bool fullscreenDialog: false, + }) : assert(builder != null), + assert(opaque), + super(settings: settings, fullscreenDialog: fullscreenDialog); + + /// Builds the primary contents of the route. + final WidgetBuilder builder; - /// A gradient to draw to the left of the box being decorated. - /// FractionalOffsets are relative to the original box translated one box - /// width to the left. - final LinearGradient edgeGradient; + @override + final bool maintainState; - /// Linearly interpolate between two edge shadow decorations decorations. - /// - /// See also [Decoration.lerp]. - static _CupertinoEdgeShadowDecoration lerp( - _CupertinoEdgeShadowDecoration a, - _CupertinoEdgeShadowDecoration b, - double t - ) { - if (a == null && b == null) - return null; - return new _CupertinoEdgeShadowDecoration( - edgeGradient: LinearGradient.lerp(a?.edgeGradient, b?.edgeGradient, t), - ); - } + @override + Duration get transitionDuration => const Duration(milliseconds: 350); @override - _CupertinoEdgeShadowDecoration lerpFrom(Decoration a, double t) { - if (a is! _CupertinoEdgeShadowDecoration) - return _CupertinoEdgeShadowDecoration.lerp(null, this, t); - return _CupertinoEdgeShadowDecoration.lerp(a, this, t); - } + Color get barrierColor => null; @override - _CupertinoEdgeShadowDecoration lerpTo(Decoration b, double t) { - if (b is! _CupertinoEdgeShadowDecoration) - return _CupertinoEdgeShadowDecoration.lerp(this, null, t); - return _CupertinoEdgeShadowDecoration.lerp(this, b, t); + bool canTransitionFrom(TransitionRoute nextRoute) { + return nextRoute is CupertinoPageRoute; } @override - _CupertinoEdgeShadowPainter createBoxPainter([VoidCallback onChanged]) { - return new _CupertinoEdgeShadowPainter(this, onChanged); + bool canTransitionTo(TransitionRoute nextRoute) { + // Don't perform outgoing animation if the next route is a fullscreen dialog. + return nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog; } @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other.runtimeType != _CupertinoEdgeShadowDecoration) - return false; - final _CupertinoEdgeShadowDecoration typedOther = other; - return edgeGradient == typedOther.edgeGradient; + void dispose() { + _backGestureController?.dispose(); + // If the route is never installed (i.e. pushed into a Navigator) such as the + // case when [MaterialPageRoute] delegates transition building to [CupertinoPageRoute], + // don't dispose super. + if (overlayEntries.isNotEmpty) + super.dispose(); } + CupertinoBackGestureController _backGestureController; + + /// Support for dismissing this route with a horizontal swipe. + /// + /// Swiping will be disabled if the page is a fullscreen dialog or if + /// dismissals can be overriden because a [WillPopCallback] was + /// defined for the route. + /// + /// See also: + /// + /// * [hasScopedWillPopCallback], which is true if a `willPop` callback + /// is defined for this route. @override - int get hashCode { - return edgeGradient.hashCode; + NavigationGestureController startPopGesture() { + return startPopGestureForRoute(this); } -} -/// A [BoxPainter] used to draw the page transition shadow using gradients. -class _CupertinoEdgeShadowPainter extends BoxPainter { - _CupertinoEdgeShadowPainter( - this._decoration, - VoidCallback onChange - ) : assert(_decoration != null), - super(onChange); + /// Create a CupertinoBackGestureController using a specific PageRoute. + /// + /// Used when [MaterialPageRoute] delegates the back gesture to [CupertinoPageRoute] + /// since the [CupertinoPageRoute] is not actually inserted into the Navigator. + NavigationGestureController startPopGestureForRoute(PageRoute hostRoute) { + // If attempts to dismiss this route might be vetoed such as in a page + // with forms, then do not allow the user to dismiss the route with a swipe. + if (hostRoute.hasScopedWillPopCallback) + return null; + // Fullscreen dialogs aren't dismissable by back swipe. + if (fullscreenDialog) + return null; + if (hostRoute.controller.status != AnimationStatus.completed) + return null; + assert(_backGestureController == null); + _backGestureController = new CupertinoBackGestureController( + navigator: hostRoute.navigator, + controller: hostRoute.controller, + ); - final _CupertinoEdgeShadowDecoration _decoration; + Function handleBackGestureEnded; + handleBackGestureEnded = (AnimationStatus status) { + if (status == AnimationStatus.completed) { + _backGestureController?.dispose(); + _backGestureController = null; + hostRoute.controller.removeStatusListener(handleBackGestureEnded); + } + }; + + hostRoute.controller.addStatusListener(handleBackGestureEnded); + return _backGestureController; + } @override - void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { - final LinearGradient gradient = _decoration.edgeGradient; - if (gradient == null) - return; - // The drawable space for the gradient is a rect with the same size as - // its parent box one box width to the left of the box. - final Rect rect = - (offset & configuration.size).translate(-configuration.size.width, 0.0); - final Paint paint = new Paint() - ..shader = gradient.createShader(rect); + Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { + final Widget result = builder(context); + assert(() { + if (result == null) { + throw new FlutterError( + 'The builder for route "${settings.name}" returned null.\n' + 'Route builders must never return null.' + ); + } + return true; + }); + return result; + } - canvas.drawRect(rect, paint); + @override + Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + if (fullscreenDialog) + return new CupertinoFullscreenDialogTransition( + animation: animation, + child: child, + ); + else + return new CupertinoPageTransition( + primaryRouteAnimation: animation, + secondaryRouteAnimation: secondaryAnimation, + child: child, + // In the middle of a back gesture drag, let the transition be linear to match finger + // motions. + linearTransition: _backGestureController != null, + ); } + + @override + String get debugLabel => '${super.debugLabel}(${settings.name})'; } /// Provides an iOS-style page transition animation. @@ -303,3 +358,94 @@ class CupertinoBackGestureController extends NavigationGestureController { navigator.pop(); } } + +/// A custom [Decoration] used to paint an extra shadow on the left edge of the +/// box it's decorating. It's like a [BoxDecoration] with only a gradient except +/// it paints to the left of the box instead of behind the box. +class _CupertinoEdgeShadowDecoration extends Decoration { + const _CupertinoEdgeShadowDecoration({ this.edgeGradient }); + + /// A Decoration with no decorating properties. + static const _CupertinoEdgeShadowDecoration none = + const _CupertinoEdgeShadowDecoration(); + + /// A gradient to draw to the left of the box being decorated. + /// FractionalOffsets are relative to the original box translated one box + /// width to the left. + final LinearGradient edgeGradient; + + /// Linearly interpolate between two edge shadow decorations decorations. + /// + /// See also [Decoration.lerp]. + static _CupertinoEdgeShadowDecoration lerp( + _CupertinoEdgeShadowDecoration a, + _CupertinoEdgeShadowDecoration b, + double t + ) { + if (a == null && b == null) + return null; + return new _CupertinoEdgeShadowDecoration( + edgeGradient: LinearGradient.lerp(a?.edgeGradient, b?.edgeGradient, t), + ); + } + + @override + _CupertinoEdgeShadowDecoration lerpFrom(Decoration a, double t) { + if (a is! _CupertinoEdgeShadowDecoration) + return _CupertinoEdgeShadowDecoration.lerp(null, this, t); + return _CupertinoEdgeShadowDecoration.lerp(a, this, t); + } + + @override + _CupertinoEdgeShadowDecoration lerpTo(Decoration b, double t) { + if (b is! _CupertinoEdgeShadowDecoration) + return _CupertinoEdgeShadowDecoration.lerp(this, null, t); + return _CupertinoEdgeShadowDecoration.lerp(this, b, t); + } + + @override + _CupertinoEdgeShadowPainter createBoxPainter([VoidCallback onChanged]) { + return new _CupertinoEdgeShadowPainter(this, onChanged); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + if (other.runtimeType != _CupertinoEdgeShadowDecoration) + return false; + final _CupertinoEdgeShadowDecoration typedOther = other; + return edgeGradient == typedOther.edgeGradient; + } + + @override + int get hashCode { + return edgeGradient.hashCode; + } +} + +/// A [BoxPainter] used to draw the page transition shadow using gradients. +class _CupertinoEdgeShadowPainter extends BoxPainter { + _CupertinoEdgeShadowPainter( + this._decoration, + VoidCallback onChange + ) : assert(_decoration != null), + super(onChange); + + final _CupertinoEdgeShadowDecoration _decoration; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final LinearGradient gradient = _decoration.edgeGradient; + if (gradient == null) + return; + // The drawable space for the gradient is a rect with the same size as + // its parent box one box width to the left of the box. + final Rect rect = + (offset & configuration.size).translate(-configuration.size.width, 0.0); + final Paint paint = new Paint() + ..shader = gradient.createShader(rect); + + canvas.drawRect(rect, paint); + } +} diff --git a/packages/flutter/lib/src/material/page.dart b/packages/flutter/lib/src/material/page.dart index 6ff54e23c9e13..4d9a371a66a1b 100644 --- a/packages/flutter/lib/src/material/page.dart +++ b/packages/flutter/lib/src/material/page.dart @@ -54,25 +54,34 @@ class _MountainViewPageTransition extends StatelessWidget { /// /// Specify whether the incoming page is a fullscreen modal dialog. On iOS, those /// pages animate bottom->up rather than right->left. +/// +/// See also: +/// +/// * [CupertinoPageRoute], that this [PageRoute] delegates transition animations to for iOS. class MaterialPageRoute extends PageRoute { /// Creates a page route for use in a material design app. MaterialPageRoute({ @required this.builder, RouteSettings settings: const RouteSettings(), this.maintainState: true, - this.fullscreenDialog: false, + bool fullscreenDialog: false, }) : assert(builder != null), assert(opaque), - super(settings: settings); + super(settings: settings, fullscreenDialog: fullscreenDialog); /// Builds the primary contents of the route. final WidgetBuilder builder; - /// Whether this route is a full-screen dialog. - /// - /// Prevents [startPopGesture] from poping the route using an edge swipe on - /// iOS. - final bool fullscreenDialog; + /// A delegate PageRoute to which iOS themed page operations are delegated to. + /// It's lazily created on first use. + CupertinoPageRoute _internalCupertinoPageRoute; + CupertinoPageRoute get _cupertinoPageRoute { + _internalCupertinoPageRoute ??= new CupertinoPageRoute( + builder: builder, // Not used. + fullscreenDialog: fullscreenDialog, + ); + return _internalCupertinoPageRoute; + } @override final bool maintainState; @@ -85,23 +94,22 @@ class MaterialPageRoute extends PageRoute { @override bool canTransitionFrom(TransitionRoute nextRoute) { - return nextRoute is MaterialPageRoute; + return nextRoute is MaterialPageRoute || nextRoute is CupertinoPageRoute; } @override bool canTransitionTo(TransitionRoute nextRoute) { // Don't perform outgoing animation if the next route is a fullscreen dialog. - return nextRoute is MaterialPageRoute && !nextRoute.fullscreenDialog; + return (nextRoute is MaterialPageRoute && !nextRoute.fullscreenDialog) + || (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog); } @override void dispose() { - _backGestureController?.dispose(); + _internalCupertinoPageRoute?.dispose(); super.dispose(); } - CupertinoBackGestureController _backGestureController; - /// Support for dismissing this route with a horizontal swipe is enabled /// for [TargetPlatform.iOS]. If attempts to dismiss this route might be /// vetoed because a [WillPopCallback] was defined for the route then the @@ -109,35 +117,14 @@ class MaterialPageRoute extends PageRoute { /// /// See also: /// + /// * [CupertinoPageRoute] that backs the gesture for iOS. /// * [hasScopedWillPopCallback], which is true if a `willPop` callback /// is defined for this route. @override NavigationGestureController startPopGesture() { - // If attempts to dismiss this route might be vetoed, then do not - // allow the user to dismiss the route with a swipe. - if (hasScopedWillPopCallback) - return null; - // Fullscreen dialogs aren't dismissable by back swipe. - if (fullscreenDialog) - return null; - if (controller.status != AnimationStatus.completed) - return null; - assert(_backGestureController == null); - _backGestureController = new CupertinoBackGestureController( - navigator: navigator, - controller: controller, - ); - - controller.addStatusListener(_handleBackGestureEnded); - return _backGestureController; - } - - void _handleBackGestureEnded(AnimationStatus status) { - if (status == AnimationStatus.completed) { - _backGestureController?.dispose(); - _backGestureController = null; - controller.removeStatusListener(_handleBackGestureEnded); - } + return Theme.of(navigator.context).platform == TargetPlatform.iOS + ? _cupertinoPageRoute.startPopGestureForRoute(this) + : null; } @override @@ -158,20 +145,7 @@ class MaterialPageRoute extends PageRoute { @override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { if (Theme.of(context).platform == TargetPlatform.iOS) { - if (fullscreenDialog) - return new CupertinoFullscreenDialogTransition( - animation: animation, - child: child, - ); - else - return new CupertinoPageTransition( - primaryRouteAnimation: animation, - secondaryRouteAnimation: secondaryAnimation, - child: child, - // In the middle of a back gesture drag, let the transition be linear to match finger - // motions. - linearTransition: _backGestureController != null, - ); + return _cupertinoPageRoute.buildTransitions(context, animation, secondaryAnimation, child); } else { return new _MountainViewPageTransition( routeAnimation: animation, diff --git a/packages/flutter/lib/src/widgets/pages.dart b/packages/flutter/lib/src/widgets/pages.dart index 558451b556cb6..a37e8b1c0308d 100644 --- a/packages/flutter/lib/src/widgets/pages.dart +++ b/packages/flutter/lib/src/widgets/pages.dart @@ -13,9 +13,18 @@ import 'routes.dart'; abstract class PageRoute extends ModalRoute { /// Creates a modal route that replaces the entire screen. PageRoute({ - RouteSettings settings: const RouteSettings() + RouteSettings settings: const RouteSettings(), + this.fullscreenDialog: false, }) : super(settings: settings); + /// Whether this page route is a full-screen dialog. + /// + /// In Material and Cupertino, being fullscreen has the effects of making + /// the app bars have a close button instead of a back button. On + /// iOS, dialogs transitions animate differently and are also not closeable + /// with the back swipe gesture. + final bool fullscreenDialog; + @override bool get opaque => true; diff --git a/packages/flutter/test/cupertino/activity_indicator_test.dart b/packages/flutter/test/cupertino/activity_indicator_test.dart index 5f84af36a28ec..642f90e08f6de 100644 --- a/packages/flutter/test/cupertino/activity_indicator_test.dart +++ b/packages/flutter/test/cupertino/activity_indicator_test.dart @@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/scheduler.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter/test/cupertino/bottom_tab_bar_test.dart b/packages/flutter/test/cupertino/bottom_tab_bar_test.dart index ad1bdf15a3a3b..9005414c86df0 100644 --- a/packages/flutter/test/cupertino/bottom_tab_bar_test.dart +++ b/packages/flutter/test/cupertino/bottom_tab_bar_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../services/mocks_for_image_cache.dart'; diff --git a/packages/flutter/test/cupertino/button_test.dart b/packages/flutter/test/cupertino/button_test.dart index 5e4c02604a2c1..75f12590eba6a 100644 --- a/packages/flutter/test/cupertino/button_test.dart +++ b/packages/flutter/test/cupertino/button_test.dart @@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/scheduler.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; const TextStyle testStyle = const TextStyle( diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart index d143ac59a19e7..ff65491610319 100644 --- a/packages/flutter/test/cupertino/nav_bar_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_test.dart @@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter/test/cupertino/page_test.dart b/packages/flutter/test/cupertino/page_test.dart new file mode 100644 index 0000000000000..56ea3da1b395e --- /dev/null +++ b/packages/flutter/test/cupertino/page_test.dart @@ -0,0 +1,144 @@ +// Copyright 2017 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_test/flutter_test.dart'; + +void main() { + testWidgets('test iOS page transition', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + final String pageNumber = settings.name == '/' ? "1" : "2"; + return new Center(child: new Text('Page $pageNumber')); + } + ); + }, + ), + ); + + final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1')); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 150)); + + Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2')); + + // Page 1 is moving to the left. + expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true); + // Page 1 isn't moving vertically. + expect(widget1TransientTopLeft.dy == widget1InitialTopLeft.dy, true); + // iOS transition is horizontal only. + expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true); + // Page 2 is coming in from the right. + expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true); + + await tester.pumpAndSettle(); + + // Page 2 covers page 1. + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + tester.state(find.byType(Navigator)).pop(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + widget2TopLeft = tester.getTopLeft(find.text('Page 2')); + + // Page 1 is coming back from the left. + expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true); + // Page 1 isn't moving vertically. + expect(widget1TransientTopLeft.dy == widget1InitialTopLeft.dy, true); + // iOS transition is horizontal only. + expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true); + // Page 2 is leaving towards the right. + expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true); + + await tester.pumpAndSettle(); + + expect(find.text('Page 1'), isOnstage); + expect(find.text('Page 2'), findsNothing); + + widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + + // Page 1 is back where it started. + expect(widget1InitialTopLeft == widget1TransientTopLeft, true); + }); + + testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + return const Center(child: const Text('Page 1')); + } + ); + }, + ), + ); + + final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1')); + + tester.state(find.byType(Navigator)).push(new CupertinoPageRoute( + builder: (BuildContext context) { + return const Center(child: const Text('Page 2')); + }, + fullscreenDialog: true, + )); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2')); + + // Page 1 doesn't move. + expect(widget1TransientTopLeft == widget1InitialTopLeft, true); + // Fullscreen dialogs transitions vertically only. + expect(widget1InitialTopLeft.dx == widget2TopLeft.dx, true); + // Page 2 is coming in from the bottom. + expect(widget2TopLeft.dy > widget1InitialTopLeft.dy, true); + + await tester.pumpAndSettle(); + + // Page 2 covers page 1. + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + tester.state(find.byType(Navigator)).pop(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + widget2TopLeft = tester.getTopLeft(find.text('Page 2')); + + // Page 1 doesn't move. + expect(widget1TransientTopLeft == widget1InitialTopLeft, true); + // Fullscreen dialogs transitions vertically only. + expect(widget1InitialTopLeft.dx == widget2TopLeft.dx, true); + // Page 2 is leaving towards the bottom. + expect(widget2TopLeft.dy > widget1InitialTopLeft.dy, true); + + await tester.pumpAndSettle(); + + expect(find.text('Page 1'), isOnstage); + expect(find.text('Page 2'), findsNothing); + + widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + + // Page 1 is back where it started. + expect(widget1InitialTopLeft == widget1TransientTopLeft, true); + }); +} \ No newline at end of file diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart index 40514df5ffbae..34482ff4b3740 100644 --- a/packages/flutter/test/cupertino/scaffold_test.dart +++ b/packages/flutter/test/cupertino/scaffold_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/rendering_tester.dart'; diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index 61e6a720e59bf..0e16ab485955a 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter/test/material/page_test.dart b/packages/flutter/test/material/page_test.dart index 4545b42ac68df..239f0c5ced4bb 100644 --- a/packages/flutter/test/material/page_test.dart +++ b/packages/flutter/test/material/page_test.dart @@ -38,7 +38,7 @@ void main() { // Animation begins from the top of the page. expect(widget2TopLeft.dy < widget2Size.height, true); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 300)); // Page 2 covers page 1. expect(find.text('Page 1'), findsNothing); @@ -53,7 +53,7 @@ void main() { // Page 2 starts to move down. expect(widget1TopLeft.dy < widget2TopLeft.dy, true); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 300)); expect(find.text('Page 1'), isOnstage); expect(find.text('Page 2'), findsNothing); @@ -302,4 +302,79 @@ void main() { // Page 2 didn't move expect(tester.getTopLeft(find.text('Page 2')), Offset.zero); }); + + testWidgets('test adaptable transitions switch during execution', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.android), + home: const Material(child: const Text('Page 1')), + routes: { + '/next': (BuildContext context) { + return const Material(child: const Text('Page 2')); + }, + }, + ) + ); + + final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1')); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2')); + final Size widget2Size = tester.getSize(find.text('Page 2')); + + // Android transition is vertical only. + expect(widget1InitialTopLeft.dx == widget2TopLeft.dx, true); + // Page 1 is above page 2 mid-transition. + expect(widget1InitialTopLeft.dy < widget2TopLeft.dy, true); + // Animation begins from the top of the page. + expect(widget2TopLeft.dy < widget2Size.height, true); + + await tester.pump(const Duration(milliseconds: 300)); + + // Page 2 covers page 1. + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + // Re-pump the same app but with iOS instead of Android. + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.iOS), + home: const Material(child: const Text('Page 1')), + routes: { + '/next': (BuildContext context) { + return const Material(child: const Text('Page 2')); + }, + }, + ) + ); + + tester.state(find.byType(Navigator)).pop(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + widget2TopLeft = tester.getTopLeft(find.text('Page 2')); + + // Page 1 is coming back from the left. + expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true); + // Page 1 isn't moving vertically. + expect(widget1TransientTopLeft.dy == widget1InitialTopLeft.dy, true); + // iOS transition is horizontal only. + expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true); + // Page 2 is leaving towards the right. + expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true); + + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.text('Page 1'), isOnstage); + expect(find.text('Page 2'), findsNothing); + + widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1')); + + // Page 1 is back where it started. + expect(widget1InitialTopLeft == widget1TransientTopLeft, true); + }); } From 4739159eff1a60261a6d1a0226daa27818f21ca8 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Thu, 15 Jun 2017 10:15:56 -0700 Subject: [PATCH 108/110] Allow transitions_perf_test.dart to be run using package:test (#10725) package:test does not allow main() methods to have required arguments - changing to an optional positional arguments list fixes this. --- examples/flutter_gallery/test_driver/transitions_perf_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flutter_gallery/test_driver/transitions_perf_test.dart b/examples/flutter_gallery/test_driver/transitions_perf_test.dart index 614a2b31fc494..e2017d45b3b54 100644 --- a/examples/flutter_gallery/test_driver/transitions_perf_test.dart +++ b/examples/flutter_gallery/test_driver/transitions_perf_test.dart @@ -174,7 +174,7 @@ Future runDemos(Iterable demos, FlutterDriver driver) async { } } -void main(List args) { +void main([List args = const []]) { group('flutter gallery transitions', () { FlutterDriver driver; setUpAll(() async { From 747e25e891e06795ceaab6eccf039767c0ee1567 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Thu, 15 Jun 2017 10:29:42 -0700 Subject: [PATCH 109/110] Fix devfs directory scanning to not attempt to scan non-existent directories (#10727) --- packages/flutter_tools/lib/src/devfs.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 8efcc8c40b14a..2657e9254201e 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -622,11 +622,15 @@ class DevFS { final String packagePath = fs.path.fromUri(packageUri); final Directory packageDirectory = fs.directory(packageUri); Uri directoryUriOnDevice = fs.path.toUri(fs.path.join('packages', packageName) + fs.path.separator); - bool packageExists; + bool packageExists = packageDirectory.existsSync(); + + if (!packageExists) { + // If the package directory doesn't exist at all, we ignore it. + continue; + } if (fs.path.isWithin(rootDirectory.path, packagePath)) { // We already scanned everything under the root directory. - packageExists = packageDirectory.existsSync(); directoryUriOnDevice = fs.path.toUri( fs.path.relative(packagePath, from: rootDirectory.path) + fs.path.separator ); From 1bc54e065707e6c91df470bce42deda2ab95cc45 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 15 Jun 2017 11:32:39 -0700 Subject: [PATCH 110/110] Eliminate dead iOS device mock code (#10732) This code is unused in any test. In upcoming changes that migrate to Xcode instruments based device listing, we'll mock out the instruments output separately. --- packages/flutter_tools/lib/src/ios/devices.dart | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index a1cb175e7d855..396266fb231d4 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -96,20 +96,20 @@ class IOSDevice extends Device { @override bool get supportsStartPaused => false; - static List getAttachedDevices([IOSDevice mockIOS]) { + static List getAttachedDevices() { if (!doctor.iosWorkflow.hasIDeviceId) return []; final List devices = []; - for (String id in _getAttachedDeviceIDs(mockIOS)) { - final String name = IOSDevice._getDeviceInfo(id, 'DeviceName', mockIOS); + for (String id in _getAttachedDeviceIDs()) { + final String name = IOSDevice._getDeviceInfo(id, 'DeviceName'); devices.add(new IOSDevice(id, name: name)); } return devices; } - static Iterable _getAttachedDeviceIDs([IOSDevice mockIOS]) { - final String listerPath = (mockIOS != null) ? mockIOS.listerPath : _checkForCommand('idevice_id'); + static Iterable _getAttachedDeviceIDs() { + final String listerPath = _checkForCommand('idevice_id'); try { final String output = runSync([listerPath, '-l']); return output.trim().split('\n').where((String s) => s != null && s.isNotEmpty); @@ -118,10 +118,8 @@ class IOSDevice extends Device { } } - static String _getDeviceInfo(String deviceID, String infoKey, [IOSDevice mockIOS]) { - final String informerPath = (mockIOS != null) - ? mockIOS.informerPath - : _checkForCommand('ideviceinfo'); + static String _getDeviceInfo(String deviceID, String infoKey) { + final String informerPath = _checkForCommand('ideviceinfo'); return runSync([informerPath, '-k', infoKey, '-u', deviceID]).trim(); }