Commit ca2c54ba authored by Mehmet Fidanboylu's avatar Mehmet Fidanboylu Committed by GitHub

Support changing the dismissable threshold (#8007)

* Support changing the dismissable threshold for any direction for dismissable widget.

* Fixing review comments.
parent d1d1c50c
...@@ -79,7 +79,8 @@ class Dismissable extends StatefulWidget { ...@@ -79,7 +79,8 @@ class Dismissable extends StatefulWidget {
this.onResize, this.onResize,
this.onDismissed, this.onDismissed,
this.direction: DismissDirection.horizontal, this.direction: DismissDirection.horizontal,
this.resizeDuration: const Duration(milliseconds: 300) this.resizeDuration: const Duration(milliseconds: 300),
this.dismissThresholds: const <DismissDirection, double>{},
}) : super(key: key) { }) : super(key: key) {
assert(key != null); assert(key != null);
assert(secondaryBackground != null ? background != null : true); assert(secondaryBackground != null ? background != null : true);
...@@ -113,6 +114,14 @@ class Dismissable extends StatefulWidget { ...@@ -113,6 +114,14 @@ class Dismissable extends StatefulWidget {
/// immediately after the the widget is dismissed. /// immediately after the the widget is dismissed.
final Duration resizeDuration; final Duration resizeDuration;
/// The offset threshold the item has to be dragged in order to be considered dismissed.
///
/// Represented as a fraction, e.g. if it is 0.4, then the item has to be dragged at least
/// 40% towards one direction to be considered dismissed. Clients can define different
/// thresholds for each dismiss direction. This allows for use cases where item can be
/// dismissed to end but not to start.
final Map<DismissDirection, double> dismissThresholds;
@override @override
_DismissableState createState() => new _DismissableState(); _DismissableState createState() => new _DismissableState();
} }
...@@ -195,6 +204,10 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin ...@@ -195,6 +204,10 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
return _dragExtent > 0 ? DismissDirection.down : DismissDirection.up; return _dragExtent > 0 ? DismissDirection.down : DismissDirection.up;
} }
double get _dismissThreshold {
return config.dismissThresholds[_dismissDirection] ?? _kDismissThreshold;
}
bool get _isActive { bool get _isActive {
return _dragUnderway || _moveController.isAnimating; return _dragUnderway || _moveController.isAnimating;
} }
...@@ -262,6 +275,9 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin ...@@ -262,6 +275,9 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
} }
bool _isFlingGesture(Velocity velocity) { bool _isFlingGesture(Velocity velocity) {
// Cannot fling an item if it cannot be dismissed by drag.
if (_dismissThreshold >= 1.0)
return false;
final double vx = velocity.pixelsPerSecond.dx; final double vx = velocity.pixelsPerSecond.dx;
final double vy = velocity.pixelsPerSecond.dy; final double vy = velocity.pixelsPerSecond.dy;
if (_directionIsXAxis) { if (_directionIsXAxis) {
...@@ -299,7 +315,7 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin ...@@ -299,7 +315,7 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
final double flingVelocity = _directionIsXAxis ? details.velocity.pixelsPerSecond.dx : details.velocity.pixelsPerSecond.dy; final double flingVelocity = _directionIsXAxis ? details.velocity.pixelsPerSecond.dx : details.velocity.pixelsPerSecond.dy;
_dragExtent = flingVelocity.sign; _dragExtent = flingVelocity.sign;
_moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale); _moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
} else if (_moveController.value > _kDismissThreshold) { } else if (_moveController.value > _dismissThreshold) {
_moveController.forward(); _moveController.forward();
} else { } else {
_moveController.reverse(); _moveController.reverse();
......
...@@ -23,30 +23,37 @@ void handleOnDismissed(DismissDirection direction, int item) { ...@@ -23,30 +23,37 @@ void handleOnDismissed(DismissDirection direction, int item) {
dismissedItems.add(item); dismissedItems.add(item);
} }
Widget buildDismissableItem(int item) { Widget buildTest({ double startToEndThreshold }) {
Widget buildDismissableItem(int item) {
return new Dismissable( return new Dismissable(
key: new ValueKey<int>(item), key: new ValueKey<int>(item),
direction: dismissDirection, direction: dismissDirection,
onDismissed: (DismissDirection direction) { handleOnDismissed(direction, item); }, onDismissed: (DismissDirection direction) {
onResize: () { handleOnResize(item); }, handleOnDismissed(direction, item);
},
onResize: () {
handleOnResize(item);
},
background: background, background: background,
dismissThresholds: startToEndThreshold == null
? <DismissDirection, double>{}
: <DismissDirection, double>{DismissDirection.startToEnd: startToEndThreshold},
child: new Container( child: new Container(
width: itemExtent, width: itemExtent,
height: itemExtent, height: itemExtent,
child: new Text(item.toString()) child: new Text(item.toString())
) )
); );
} }
Widget widgetBuilder() {
return new Container( return new Container(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: new ScrollableList( child: new ScrollableList(
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
itemExtent: itemExtent, itemExtent: itemExtent,
children: <int>[0, 1, 2, 3, 4].where( children: <int>[0, 1, 2, 3, 4]
(int i) => !dismissedItems.contains(i) .where((int i) => !dismissedItems.contains(i))
).map(buildDismissableItem) .map(buildDismissableItem)
) )
); );
} }
...@@ -58,7 +65,7 @@ Future<Null> dismissElement(WidgetTester tester, Finder finder, { DismissDirecti ...@@ -58,7 +65,7 @@ Future<Null> dismissElement(WidgetTester tester, Finder finder, { DismissDirecti
Point downLocation; Point downLocation;
Point upLocation; Point upLocation;
switch(gestureDirection) { switch (gestureDirection) {
case DismissDirection.endToStart: case DismissDirection.endToStart:
// getTopRight() returns a point that's just beyond itemWidget's right // getTopRight() returns a point that's just beyond itemWidget's right
// edge and outside the Dismissable event listener's bounds. // edge and outside the Dismissable event listener's bounds.
...@@ -99,11 +106,11 @@ Future<Null> dismissItem(WidgetTester tester, int item, { DismissDirection gestu ...@@ -99,11 +106,11 @@ Future<Null> dismissItem(WidgetTester tester, int item, { DismissDirection gestu
await dismissElement(tester, itemFinder, gestureDirection: gestureDirection); await dismissElement(tester, itemFinder, gestureDirection: gestureDirection);
await tester.pumpWidget(widgetBuilder()); // start the slide await tester.pumpWidget(buildTest()); // start the slide
await tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the slide and start shrinking... await tester.pumpWidget(buildTest(), const Duration(seconds: 1)); // finish the slide and start shrinking...
await tester.pumpWidget(widgetBuilder()); // first frame of shrinking animation await tester.pumpWidget(buildTest()); // first frame of shrinking animation
await tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the shrinking and call the callback... await tester.pumpWidget(buildTest(), const Duration(seconds: 1)); // finish the shrinking and call the callback...
await tester.pumpWidget(widgetBuilder()); // rebuild after the callback removes the entry await tester.pumpWidget(buildTest()); // rebuild after the callback removes the entry
} }
class Test1215DismissableWidget extends StatelessWidget { class Test1215DismissableWidget extends StatelessWidget {
...@@ -133,7 +140,7 @@ void main() { ...@@ -133,7 +140,7 @@ void main() {
scrollDirection = Axis.vertical; scrollDirection = Axis.vertical;
dismissDirection = DismissDirection.horizontal; dismissDirection = DismissDirection.horizontal;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd); await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
...@@ -151,7 +158,7 @@ void main() { ...@@ -151,7 +158,7 @@ void main() {
scrollDirection = Axis.horizontal; scrollDirection = Axis.horizontal;
dismissDirection = DismissDirection.vertical; dismissDirection = DismissDirection.vertical;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.up); await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
...@@ -169,7 +176,7 @@ void main() { ...@@ -169,7 +176,7 @@ void main() {
scrollDirection = Axis.vertical; scrollDirection = Axis.vertical;
dismissDirection = DismissDirection.endToStart; dismissDirection = DismissDirection.endToStart;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd); await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
...@@ -187,7 +194,7 @@ void main() { ...@@ -187,7 +194,7 @@ void main() {
scrollDirection = Axis.vertical; scrollDirection = Axis.vertical;
dismissDirection = DismissDirection.startToEnd; dismissDirection = DismissDirection.startToEnd;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart); await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
...@@ -203,7 +210,7 @@ void main() { ...@@ -203,7 +210,7 @@ void main() {
scrollDirection = Axis.horizontal; scrollDirection = Axis.horizontal;
dismissDirection = DismissDirection.up; dismissDirection = DismissDirection.up;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.down); await dismissItem(tester, 0, gestureDirection: DismissDirection.down);
...@@ -219,7 +226,7 @@ void main() { ...@@ -219,7 +226,7 @@ void main() {
scrollDirection = Axis.horizontal; scrollDirection = Axis.horizontal;
dismissDirection = DismissDirection.down; dismissDirection = DismissDirection.down;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.up); await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
...@@ -231,6 +238,22 @@ void main() { ...@@ -231,6 +238,22 @@ void main() {
expect(dismissedItems, equals(<int>[0])); expect(dismissedItems, equals(<int>[0]));
}); });
testWidgets('drag-left has no effect on dismissable with a high dismiss threshold', (WidgetTester tester) async {
scrollDirection = Axis.vertical;
dismissDirection = DismissDirection.horizontal;
await tester.pumpWidget(buildTest(startToEndThreshold: 1.0));
expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
expect(find.text('0'), findsOneWidget);
expect(dismissedItems, isEmpty);
await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
expect(find.text('0'), findsNothing);
expect(dismissedItems, equals(<int>[0]));
});
// This is a regression test for an fn2 bug where dragging a card caused an // This is a regression test for an fn2 bug where dragging a card caused an
// assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL // assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
// was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404 // was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
...@@ -241,22 +264,22 @@ void main() { ...@@ -241,22 +264,22 @@ void main() {
scrollDirection = Axis.horizontal; scrollDirection = Axis.horizontal;
dismissDirection = DismissDirection.down; dismissDirection = DismissDirection.down;
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
Point location = tester.getTopLeft(find.text('0')); Point location = tester.getTopLeft(find.text('0'));
Offset offset = const Offset(0.0, 5.0); Offset offset = const Offset(0.0, 5.0);
TestGesture gesture = await tester.startGesture(location, pointer: 5); TestGesture gesture = await tester.startGesture(location, pointer: 5);
await gesture.moveBy(offset); await gesture.moveBy(offset);
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
await gesture.moveBy(offset); await gesture.moveBy(offset);
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
await gesture.moveBy(offset); await gesture.moveBy(offset);
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
await gesture.moveBy(offset); await gesture.moveBy(offset);
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
await gesture.up(); await gesture.up();
}); });
// This one is for a case where dssmissing a widget above a previously // This one is for a case where dismissing a widget above a previously
// dismissed widget threw an exception, which was documented at the // dismissed widget threw an exception, which was documented at the
// now-obsolete URL https://github.com/flutter/engine/issues/1215 (the URL // 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 // died in the migration to the new repo). Don't copy this test; it doesn't
...@@ -294,7 +317,7 @@ void main() { ...@@ -294,7 +317,7 @@ void main() {
dismissDirection = DismissDirection.horizontal; dismissDirection = DismissDirection.horizontal;
background = new Text('background'); background = new Text('background');
await tester.pumpWidget(widgetBuilder()); await tester.pumpWidget(buildTest());
expect(dismissedItems, isEmpty); expect(dismissedItems, isEmpty);
Finder itemFinder = find.text('0'); Finder itemFinder = find.text('0');
......
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