Commit 4f672dc0 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1020 from Hixie/transform

RenderFractionalTranslation and Dismissable
parents a9ddbb4e 9bad312a
......@@ -510,36 +510,55 @@ void paintImage({
/// FractionalOffset(1.0, 0.0) represents the top right of the Size,
/// FractionalOffset(0.0, 1.0) represents the bottom left of the Size,
class FractionalOffset {
const FractionalOffset(this.x, this.y);
final double x;
final double y;
const FractionalOffset(this.dx, this.dy);
final double dx;
final double dy;
static const FractionalOffset zero = const FractionalOffset(0.0, 0.0);
FractionalOffset operator -() {
return new FractionalOffset(-dx, -dy);
}
FractionalOffset operator -(FractionalOffset other) {
return new FractionalOffset(x - other.x, y - other.y);
return new FractionalOffset(dx - other.dx, dy - other.dy);
}
FractionalOffset operator +(FractionalOffset other) {
return new FractionalOffset(x + other.x, y + other.y);
return new FractionalOffset(dx + other.dx, dy + other.dy);
}
FractionalOffset operator *(double other) {
return new FractionalOffset(x * other, y * other);
return new FractionalOffset(dx * other, dy * other);
}
FractionalOffset operator /(double other) {
return new FractionalOffset(dx / other, dy / other);
}
FractionalOffset operator ~/(double other) {
return new FractionalOffset((dx ~/ other).toDouble(), (dy ~/ other).toDouble());
}
FractionalOffset operator %(double other) {
return new FractionalOffset(dx % other, dy % other);
}
Offset alongOffset(Offset other) {
return new Offset(dx * other.dx, dy * other.dy);
}
Offset alongSize(Size other) {
return new Offset(dx * other.width, dy * other.height);
}
bool operator ==(dynamic other) {
if (other is! FractionalOffset)
return false;
final FractionalOffset typedOther = other;
return x == typedOther.x &&
y == typedOther.y;
return dx == typedOther.dx &&
dy == typedOther.dy;
}
int get hashCode => hashValues(x, y);
int get hashCode => hashValues(dx, dy);
static FractionalOffset lerp(FractionalOffset a, FractionalOffset b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return new FractionalOffset(b.x * t, b.y * t);
return new FractionalOffset(b.dx * t, b.dy * t);
if (b == null)
return new FractionalOffset(b.x * (1.0 - t), b.y * (1.0 - t));
return new FractionalOffset(ui.lerpDouble(a.x, b.x, t), ui.lerpDouble(a.y, b.y, t));
return new FractionalOffset(b.dx * (1.0 - t), b.dy * (1.0 - t));
return new FractionalOffset(ui.lerpDouble(a.dx, b.dx, t), ui.lerpDouble(a.dy, b.dy, t));
}
String toString() => '$runtimeType($x, $y)';
String toString() => '$runtimeType($dx, $dy)';
}
/// A background image for a box.
......@@ -919,8 +938,8 @@ class _BoxDecorationPainter extends BoxPainter {
rect: rect,
image: image,
colorFilter: backgroundImage.colorFilter,
alignX: backgroundImage.alignment?.x,
alignY: backgroundImage.alignment?.y,
alignX: backgroundImage.alignment?.dx,
alignY: backgroundImage.alignment?.dy,
fit: backgroundImage.fit,
repeat: backgroundImage.repeat
);
......
......@@ -216,8 +216,8 @@ class RenderImage extends RenderBox {
image: _image,
colorFilter: _colorFilter,
fit: _fit,
alignX: _alignment?.x,
alignY: _alignment?.y,
alignX: _alignment?.dx,
alignY: _alignment?.dy,
centerSlice: _centerSlice,
repeat: _repeat
);
......
......@@ -851,10 +851,11 @@ class RenderTransform extends RenderProxyBox {
Matrix4 transform,
Offset origin,
FractionalOffset alignment,
this.transformHitTests: true,
RenderBox child
}) : super(child) {
assert(transform != null);
assert(alignment == null || (alignment.x != null && alignment.y != null));
assert(alignment == null || (alignment.dx != null && alignment.dy != null));
this.transform = transform;
this.alignment = alignment;
this.origin = origin;
......@@ -881,13 +882,21 @@ class RenderTransform extends RenderProxyBox {
FractionalOffset get alignment => _alignment;
FractionalOffset _alignment;
void set alignment (FractionalOffset newAlignment) {
assert(newAlignment == null || (newAlignment.x != null && newAlignment.y != null));
assert(newAlignment == null || (newAlignment.dx != null && newAlignment.dy != null));
if (_alignment == newAlignment)
return;
_alignment = newAlignment;
markNeedsPaint();
}
/// When set to true, hit tests are performed based on the position of the
/// child as it is painted. When set to false, hit tests are performed
/// ignoring the transformation.
///
/// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
/// always honor the transformation, regardless of the value of this property.
bool transformHitTests;
// Note the lack of a getter for transform because Matrix4 is not immutable
Matrix4 _transform;
......@@ -942,25 +951,29 @@ class RenderTransform extends RenderProxyBox {
Matrix4 result = new Matrix4.identity();
if (_origin != null)
result.translate(_origin.dx, _origin.dy);
if (_alignment != null)
result.translate(_alignment.x * size.width, _alignment.y * size.height);
Offset translation;
if (_alignment != null) {
translation = _alignment.alongSize(size);
result.translate(translation.dx, translation.dy);
}
result.multiply(_transform);
if (_alignment != null)
result.translate(-_alignment.x * size.width, -_alignment.y * size.height);
result.translate(-translation.dx, -translation.dy);
if (_origin != null)
result.translate(-_origin.dx, -_origin.dy);
return result;
}
bool hitTest(HitTestResult result, { Point position }) {
Matrix4 inverse = new Matrix4.zero();
// TODO(abarth): Check the determinant for degeneracy.
inverse.copyInverse(_effectiveTransform);
Vector3 position3 = new Vector3(position.x, position.y, 0.0);
Vector3 transformed3 = inverse.transform3(position3);
Point transformed = new Point(transformed3.x, transformed3.y);
return super.hitTest(result, position: transformed);
if (transformHitTests) {
Matrix4 inverse = new Matrix4.zero();
// TODO(abarth): Check the determinant for degeneracy.
inverse.copyInverse(_effectiveTransform);
Vector3 position3 = new Vector3(position.x, position.y, 0.0);
Vector3 transformed3 = inverse.transform3(position3);
position = new Point(transformed3.x, transformed3.y);
}
return super.hitTest(result, position: position);
}
void paint(PaintingContext context, Offset offset) {
......@@ -985,6 +998,65 @@ class RenderTransform extends RenderProxyBox {
settings.addAll(debugDescribeTransform(_transform));
settings.add('origin: $origin');
settings.add('alignment: $alignment');
settings.add('transformHitTests: $transformHitTests');
}
}
/// Applies a translation transformation before painting its child. The
/// translation is expressed as a [FractionalOffset] relative to the
/// RenderFractionalTranslation box's size. Hit tests will only be detected
/// inside the bounds of the RenderFractionalTranslation, even if the contents
/// are offset such that they overflow.
class RenderFractionalTranslation extends RenderProxyBox {
RenderFractionalTranslation({
FractionalOffset translation,
this.transformHitTests: true,
RenderBox child
}) : _translation = translation, super(child) {
assert(translation == null || (translation.dx != null && translation.dy != null));
}
/// The translation to apply to the child, as a multiple of the size.
FractionalOffset get translation => _translation;
FractionalOffset _translation;
void set translation (FractionalOffset newTranslation) {
assert(newTranslation == null || (newTranslation.dx != null && newTranslation.dy != null));
if (_translation == newTranslation)
return;
_translation = newTranslation;
markNeedsPaint();
}
/// When set to true, hit tests are performed based on the position of the
/// child as it is painted. When set to false, hit tests are performed
/// ignoring the transformation.
///
/// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
/// always honor the transformation, regardless of the value of this property.
bool transformHitTests;
bool hitTest(HitTestResult result, { Point position }) {
assert(!needsLayout);
if (transformHitTests)
position = new Point(position.x - translation.dx * size.width, position.y - translation.dy * size.height);
return super.hitTest(result, position: position);
}
void paint(PaintingContext context, Offset offset) {
assert(!needsLayout);
if (child != null)
super.paint(context, offset + translation.alongSize(size));
}
void applyPaintTransform(RenderBox child, Matrix4 transform) {
transform.translate(translation.dx * size.width, translation.dy * size.height);
super.applyPaintTransform(child, transform);
}
void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings);
settings.add('translation: $translation');
settings.add('transformHitTests: $transformHitTests');
}
}
......
......@@ -179,7 +179,7 @@ class RenderPositionedBox extends RenderShiftedBox {
_widthFactor = widthFactor,
_heightFactor = heightFactor,
super(child) {
assert(alignment != null && alignment.x != null && alignment.y != null);
assert(alignment != null && alignment.dx != null && alignment.dy != null);
assert(widthFactor == null || widthFactor >= 0.0);
assert(heightFactor == null || heightFactor >= 0.0);
}
......@@ -196,7 +196,7 @@ class RenderPositionedBox extends RenderShiftedBox {
FractionalOffset get alignment => _alignment;
FractionalOffset _alignment;
void set alignment (FractionalOffset newAlignment) {
assert(newAlignment != null && newAlignment.x != null && newAlignment.y != null);
assert(newAlignment != null && newAlignment.dx != null && newAlignment.dy != null);
if (_alignment == newAlignment)
return;
_alignment = newAlignment;
......@@ -237,9 +237,8 @@ class RenderPositionedBox extends RenderShiftedBox {
child.layout(constraints.loosen(), parentUsesSize: true);
size = constraints.constrain(new Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.INFINITY,
shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.INFINITY));
final Offset delta = size - child.size;
final BoxParentData childParentData = child.parentData;
childParentData.position = delta.scale(_alignment.x, _alignment.y).toPoint();
childParentData.position = _alignment.alongOffset(size - child.size).toPoint();
} else {
size = constraints.constrain(new Size(shrinkWrapWidth ? 0.0 : double.INFINITY,
shrinkWrapHeight ? 0.0 : double.INFINITY));
......
......@@ -328,9 +328,7 @@ abstract class RenderStackBase extends RenderBox
final StackParentData childParentData = child.parentData;
if (!childParentData.isPositioned) {
double x = (size.width - child.size.width) * alignment.x;
double y = (size.height - child.size.height) * alignment.y;
childParentData.position = new Point(x, y);
childParentData.position = alignment.alongOffset(size - child.size).toPoint();
} else {
BoxConstraints childConstraints = const BoxConstraints();
......
......@@ -270,7 +270,7 @@ class ClipOval extends OneChildRenderObjectWidget {
/// Applies a transformation before painting its child.
class Transform extends OneChildRenderObjectWidget {
Transform({ Key key, this.transform, this.origin, this.alignment, Widget child })
Transform({ Key key, this.transform, this.origin, this.alignment, this.transformHitTests: true, Widget child })
: super(key: key, child: child) {
assert(transform != null);
}
......@@ -291,12 +291,43 @@ class Transform extends OneChildRenderObjectWidget {
/// If it is specificed at the same time as an offset, both are applied.
final FractionalOffset alignment;
RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin, alignment: alignment);
/// Whether to apply the translation when performing hit tests.
final bool transformHitTests;
RenderTransform createRenderObject() => new RenderTransform(
transform: transform,
origin: origin,
alignment: alignment,
transformHitTests: transformHitTests
);
void updateRenderObject(RenderTransform renderObject, Transform oldWidget) {
renderObject.transform = transform;
renderObject.origin = origin;
renderObject.alignment = alignment;
renderObject.transformHitTests = transformHitTests;
}
}
/// Applies a translation expressed as a fraction of the box's size before
/// painting its child.
class FractionalTranslation extends OneChildRenderObjectWidget {
FractionalTranslation({ Key key, this.translation, this.transformHitTests: true, Widget child })
: super(key: key, child: child) {
assert(translation != null);
}
/// The offset by which to translate the child, as a multiple of its size.
final FractionalOffset translation;
/// Whether to apply the translation when performing hit tests.
final bool transformHitTests;
RenderFractionalTranslation createRenderObject() => new RenderFractionalTranslation(translation: translation, transformHitTests: transformHitTests);
void updateRenderObject(RenderFractionalTranslation renderObject, FractionalTranslation oldWidget) {
renderObject.translation = translation;
renderObject.transformHitTests = transformHitTests;
}
}
......@@ -335,7 +366,7 @@ class Align extends OneChildRenderObjectWidget {
this.heightFactor,
Widget child
}) : super(key: key, child: child) {
assert(alignment != null && alignment.x != null && alignment.y != null);
assert(alignment != null && alignment.dx != null && alignment.dy != null);
assert(widthFactor == null || widthFactor >= 0.0);
assert(heightFactor == null || heightFactor >= 0.0);
}
......
......@@ -11,9 +11,9 @@ import 'transitions.dart';
import 'framework.dart';
import 'gesture_detector.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
const Duration _kCardDismissResize = const Duration(milliseconds: 300);
const Curve _kCardDismissResizeCurve = const Interval(0.4, 1.0, curve: Curves.ease);
const Duration _kCardDismissDuration = const Duration(milliseconds: 200);
const Duration _kCardResizeDuration = const Duration(milliseconds: 300);
const Curve _kCardResizeTimeCurve = const Interval(0.4, 1.0, curve: Curves.ease);
const double _kMinFlingVelocity = 700.0;
const double _kMinFlingVelocityDelta = 400.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
......@@ -60,7 +60,7 @@ class Dismissable extends StatefulComponent {
/// Called when the widget changes size (i.e., when contracting after being dismissed).
final VoidCallback onResized;
/// Called when the widget has been dismissed.
/// Called when the widget has been dismissed, after finishing resizing.
final VoidCallback onDismissed;
/// The direction in which the widget can be dismissed.
......@@ -72,14 +72,14 @@ class Dismissable extends StatefulComponent {
class _DismissableState extends State<Dismissable> {
void initState() {
super.initState();
_fadePerformance = new Performance(duration: _kCardDismissFadeout);
_fadePerformance.addStatusListener((PerformanceStatus status) {
_dismissPerformance = new Performance(duration: _kCardDismissDuration);
_dismissPerformance.addStatusListener((PerformanceStatus status) {
if (status == PerformanceStatus.completed)
_handleFadeCompleted();
_handleDismissCompleted();
});
}
Performance _fadePerformance;
Performance _dismissPerformance;
Performance _resizePerformance;
Size _size;
......@@ -87,7 +87,7 @@ class _DismissableState extends State<Dismissable> {
bool _dragUnderway = false;
void dispose() {
_fadePerformance?.stop();
_dismissPerformance?.stop();
_resizePerformance?.stop();
super.dispose();
}
......@@ -99,13 +99,13 @@ class _DismissableState extends State<Dismissable> {
config.direction == DismissDirection.down;
}
void _handleFadeCompleted() {
void _handleDismissCompleted() {
if (!_dragUnderway)
_startResizePerformance();
}
bool get _isActive {
return _size != null && (_dragUnderway || _fadePerformance.isAnimating);
return _size != null && (_dragUnderway || _dismissPerformance.isAnimating);
}
void _maybeCallOnResized() {
......@@ -120,13 +120,12 @@ class _DismissableState extends State<Dismissable> {
void _startResizePerformance() {
assert(_size != null);
assert(_fadePerformance != null);
assert(_fadePerformance.isCompleted);
assert(_dismissPerformance != null);
assert(_dismissPerformance.isCompleted);
assert(_resizePerformance == null);
setState(() {
_resizePerformance = new Performance()
..duration = _kCardDismissResize
..duration = _kCardResizeDuration
..addListener(_handleResizeProgressChanged);
_resizePerformance.play();
});
......@@ -140,21 +139,21 @@ class _DismissableState extends State<Dismissable> {
}
void _handleDragStart(_) {
if (_fadePerformance.isAnimating)
if (_dismissPerformance.isAnimating)
return;
setState(() {
_dragUnderway = true;
_dragExtent = 0.0;
_fadePerformance.progress = 0.0;
_dismissPerformance.progress = 0.0;
});
}
void _handleDragUpdate(double delta) {
if (!_isActive || _fadePerformance.isAnimating)
if (!_isActive || _dismissPerformance.isAnimating)
return;
double oldDragExtent = _dragExtent;
switch(config.direction) {
switch (config.direction) {
case DismissDirection.horizontal:
case DismissDirection.vertical:
_dragExtent += delta;
......@@ -181,8 +180,8 @@ class _DismissableState extends State<Dismissable> {
// the performances.
});
}
if (!_fadePerformance.isAnimating)
_fadePerformance.progress = _dragExtent.abs() / (_size.width * _kDismissCardThreshold);
if (!_dismissPerformance.isAnimating)
_dismissPerformance.progress = _dragExtent.abs() / _size.width;
}
bool _isFlingGesture(ui.Offset velocity) {
......@@ -215,19 +214,20 @@ class _DismissableState extends State<Dismissable> {
}
void _handleDragEnd(ui.Offset velocity) {
if (!_isActive || _fadePerformance.isAnimating)
if (!_isActive || _dismissPerformance.isAnimating)
return;
setState(() {
_dragUnderway = false;
if (_fadePerformance.isCompleted) {
if (_dismissPerformance.isCompleted) {
_startResizePerformance();
} else if (_isFlingGesture(velocity)) {
double flingVelocity = _directionIsYAxis ? velocity.dy : velocity.dx;
_dragExtent = flingVelocity.sign;
_fadePerformance.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
_dismissPerformance.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
} else if (_dismissPerformance.progress > _kDismissCardThreshold) {
_dismissPerformance.forward();
} else {
_fadePerformance.reverse();
_dismissPerformance.reverse();
}
});
}
......@@ -238,12 +238,12 @@ class _DismissableState extends State<Dismissable> {
});
}
Point get _activeCardDragEndPoint {
FractionalOffset get _activeCardDragEndPoint {
if (!_isActive)
return Point.origin;
assert(_size != null);
double extent = _directionIsYAxis ? _size.height : _size.width;
return new Point(_dragExtent.sign * extent * _kDismissCardThreshold, 0.0);
return FractionalOffset.zero;
if (_directionIsYAxis)
return new FractionalOffset(0.0, _dragExtent.sign);
return new FractionalOffset(_dragExtent.sign, 0.0);
}
Widget build(BuildContext context) {
......@@ -254,7 +254,7 @@ class _DismissableState extends State<Dismissable> {
AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
_directionIsYAxis ? _size.width : _size.height,
end: 0.0,
curve: _kCardDismissResizeCurve
curve: _kCardResizeTimeCurve
);
return new SquashTransition(
......@@ -274,14 +274,13 @@ class _DismissableState extends State<Dismissable> {
behavior: HitTestBehavior.opaque,
child: new SizeObserver(
onSizeChanged: _handleSizeChanged,
child: new FadeTransition(
performance: _fadePerformance.view,
opacity: new AnimatedValue<double>(1.0, end: 0.0),
child: new SlideTransition(
performance: _fadePerformance.view,
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
child: config.child
)
child: new SlideTransition(
performance: _dismissPerformance.view,
position: new AnimatedValue<FractionalOffset>(
FractionalOffset.zero,
end: _activeCardDragEndPoint
),
child: config.child
)
)
);
......
......@@ -82,18 +82,18 @@ class SlideTransition extends TransitionWithChild {
Key key,
this.position,
PerformanceView performance,
this.transformHitTests: true,
Widget child
}) : super(key: key,
performance: performance,
child: child);
final AnimatedValue<Point> position;
final AnimatedValue<FractionalOffset> position;
bool transformHitTests;
Widget buildWithChild(BuildContext context, Widget child) {
performance.updateVariable(position);
Matrix4 transform = new Matrix4.identity()
..translate(position.value.x, position.value.y);
return new Transform(transform: transform, child: child);
return new FractionalTranslation(translation: position.value, transformHitTests: transformHitTests, child: child);
}
}
......
......@@ -12,11 +12,11 @@ ScrollDirection scrollDirection = ScrollDirection.vertical;
DismissDirection dismissDirection = DismissDirection.horizontal;
List<int> dismissedItems = <int>[];
void handleOnResized(item) {
void handleOnResized(int item) {
expect(dismissedItems.contains(item), isFalse);
}
void handleOnDismissed(item) {
void handleOnDismissed(int item) {
expect(dismissedItems.contains(item), isFalse);
dismissedItems.add(item);
}
......@@ -39,7 +39,7 @@ Widget widgetBuilder() {
return new Container(
padding: const EdgeDims.all(10.0),
child: new ScrollableList<int>(
items: [0, 1, 2, 3, 4].where((int i) => !dismissedItems.contains(i)).toList(),
items: <int>[0, 1, 2, 3, 4].where((int i) => !dismissedItems.contains(i)).toList(),
itemBuilder: buildDismissableItem,
scrollDirection: scrollDirection,
itemExtent: itemExtent
......@@ -56,25 +56,25 @@ void dismissElement(WidgetTester tester, Element itemElement, { DismissDirection
Point upLocation;
switch(gestureDirection) {
case DismissDirection.left:
// Note: getTopRight() returns a point that's just beyond
// itemWidget's right edge and outside the Dismissable event
// listener's bounds.
// getTopRight() returns a point that's just beyond itemWidget's right
// edge and outside the Dismissable event listener's bounds.
downLocation = tester.getTopRight(itemElement) + const Offset(-0.1, 0.0);
upLocation = tester.getTopLeft(itemElement);
break;
case DismissDirection.right:
downLocation = tester.getTopLeft(itemElement);
// we do the same thing here to keep the test symmetric
downLocation = tester.getTopLeft(itemElement) + const Offset(0.1, 0.0);
upLocation = tester.getTopRight(itemElement);
break;
case DismissDirection.up:
// Note: getBottomLeft() returns a point that's just below
// itemWidget's bottom edge and outside the Dismissable event
// listener's bounds.
// getBottomLeft() returns a point that's just below itemWidget's bottom
// edge and outside the Dismissable event listener's bounds.
downLocation = tester.getBottomLeft(itemElement) + const Offset(0.0, -0.1);
upLocation = tester.getTopLeft(itemElement);
break;
case DismissDirection.down:
downLocation = tester.getTopLeft(itemElement);
// again with doing the same here for symmetry
downLocation = tester.getTopLeft(itemElement) + const Offset(0.1, 0.0);
upLocation = tester.getBottomLeft(itemElement);
break;
default:
......@@ -96,9 +96,11 @@ void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirect
dismissElement(tester, itemElement, gestureDirection: gestureDirection);
tester.pumpWidget(widgetBuilder()); // start the resize animation
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the resize animation
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // dismiss
tester.pumpWidget(widgetBuilder()); // start the slide
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the slide and start shrinking...
tester.pumpWidget(widgetBuilder()); // first frame of shrinking animation
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the shrinking and call the callback...
tester.pumpWidget(widgetBuilder()); // rebuild after the callback removes the entry
}
class Test1215DismissableComponent extends StatelessComponent {
......@@ -229,8 +231,12 @@ void main() {
});
});
// This is a regression test for
// https://github.com/domokit/sky_engine/issues/1068
// This is a regression test for an fn2 bug where dragging a card caused an
// assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
// was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
// now since we migrated to the new repo. The bug was fixed by
// https://github.com/flutter/engine/pull/1134 at the time, and later made
// irrelevant by fn3, but just in case...
test('Verify that drag-move events do not assert', () {
testWidgets((WidgetTester tester) {
scrollDirection = ScrollDirection.horizontal;
......@@ -255,8 +261,12 @@ void main() {
});
});
// This one is for
// https://github.com/flutter/engine/issues/1215
// This one is for a case where dssmissing a component above a previously
// dismissed component threw an exception, which was documented at the
// now-obsolete URL https://github.com/flutter/engine/issues/1215 (the URL
// died in the migration to the new repo). Don't copy this test; it doesn't
// actually remove the dismissed widget, which is a violation of the
// Dismissable contract. This is not an example of good practice.
test('dismissing bottom then top (smoketest)', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new Center(
......@@ -272,11 +282,13 @@ void main() {
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
dismissElement(tester, tester.findText('2'), gestureDirection: DismissDirection.right);
tester.pump(new Duration(seconds: 1));
tester.pump(); // start the slide away
tester.pump(new Duration(seconds: 1)); // finish the slide away
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNull);
dismissElement(tester, tester.findText('1'), gestureDirection: DismissDirection.right);
tester.pump(new Duration(seconds: 1));
tester.pump(); // start the slide away
tester.pump(new Duration(seconds: 1)); // finish the slide away (at which point the child is no longer included in the tree)
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNull);
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment