diff --git a/.gitignore b/.gitignore index 3f533dea..c20e283b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,13 @@ doc/api/ .idea/ .DS_Store .vscode/ + +example/android/\.project + +example/android/\.settings/org\.eclipse\.buildship\.core\.prefs + +example/android/app/\.classpath + +example/android/app/\.project + +example/android/app/\.settings/org\.eclipse\.buildship\.core\.prefs diff --git a/README.md b/README.md index 27b1360f..dc78348e 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ By default, a `Slidable` closes when the nearest `Scrollable` widget starts to s To prevent this, you can pass in `false` to the `closeOnScroll` constructor argument. #### How can I dismiss my Slidable? + In order to make your `Slidable` dismissible, you have to set the `slideToDismissDelegate` argument of the `Slidable` constructor. You can set any class that inherits `SlideToDismissDelegate`. For now there is only one built-in: `SlideToDismissDrawerDelegate`. @@ -173,6 +174,7 @@ slideToDismissDelegate: new SlideToDismissDrawerDelegate( ``` #### How can I prevent to dismiss one side but not the other? + If you only want one side to be dismissible, you can set the associated threshold to 1.0 or more. For example, if you don't want the first primary action to be dismissed, you will set the following thresholds on the `slideToDismissDelegate`: @@ -182,6 +184,39 @@ dismissThresholds: { }, ``` +#### How to let the user cancel a dismissal? + +You can let the user confirm the dismissal by setting the `onWillDismiss` callback on the `slideToDismissDelegate`. + +Example: + +```dart +slideToDismissDelegate: new SlideToDismissDrawerDelegate( + onWillDismiss: (actionType) { + return showDialog( + context: context, + builder: (context) { + return new AlertDialog( + title: new Text('Delete'), + content: new Text('Item will be deleted'), + actions: [ + new FlatButton( + child: new Text('Cancel'), + onPressed: () => Navigator.of(context).pop(false), + ), + new FlatButton( + child: new Text('Ok'), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + }, + ... + ), +``` + ## Changelog Please see the [Changelog](https://github.com/letsar/flutter_slidable/blob/master/CHANGELOG.md) page to know what's recently changed. diff --git a/example/lib/main.dart b/example/lib/main.dart index 1ebbd466..9c6599b5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -55,7 +55,8 @@ class _MyHomePageState extends State { itemBuilder: (context, index) { final Axis slidableDirection = direction == Axis.horizontal ? Axis.vertical : Axis.horizontal; - if (index < 8) { + var item = items[index]; + if (item.index < 8) { return _getSlidableWithLists(context, index, slidableDirection); } else { return _getSlidableWithDelegates(context, index, slidableDirection); @@ -111,10 +112,23 @@ class _MyHomePageState extends State { Widget _getSlidableWithLists( BuildContext context, int index, Axis direction) { final _HomeItem item = items[index]; + //final int t = index; return new Slidable( key: new Key(item.title), direction: direction, - delegate: _getDelegate(index), + slideToDismissDelegate: new SlideToDismissDrawerDelegate( + onDismissed: (actionType) { + _showSnackBar( + context, + actionType == SlideActionType.primary + ? 'Dismiss Archive' + : 'Dimiss Delete'); + setState(() { + items.removeAt(index); + }); + }, + ), + delegate: _getDelegate(item.index), actionExtentRatio: 0.25, child: direction == Axis.horizontal ? _buildVerticalListItem(context, index) @@ -154,10 +168,34 @@ class _MyHomePageState extends State { Widget _getSlidableWithDelegates( BuildContext context, int index, Axis direction) { final _HomeItem item = items[index]; + return new Slidable.builder( key: new Key(item.title), direction: direction, slideToDismissDelegate: new SlideToDismissDrawerDelegate( + onWillDismiss: (item.index != 10) + ? null + : (actionType) { + return showDialog( + context: context, + builder: (context) { + return new AlertDialog( + title: new Text('Delete'), + content: new Text('Item will be deleted'), + actions: [ + new FlatButton( + child: new Text('Cancel'), + onPressed: () => Navigator.of(context).pop(false), + ), + new FlatButton( + child: new Text('Ok'), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + }, onDismissed: (actionType) { _showSnackBar( context, @@ -169,7 +207,7 @@ class _MyHomePageState extends State { }); }, ), - delegate: _getDelegate(index), + delegate: _getDelegate(item.index), actionExtentRatio: 0.25, child: direction == Axis.horizontal ? _buildVerticalListItem(context, index) diff --git a/lib/src/widgets/slidable.dart b/lib/src/widgets/slidable.dart index a56b23ed..6ddb5902 100644 --- a/lib/src/widgets/slidable.dart +++ b/lib/src/widgets/slidable.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -33,11 +35,17 @@ enum SlideActionType { } /// Signature used by [SlideToDismissDelegate] to indicate that it has been -/// dismissed for the given `actionType`. +/// dismissed for the given [actionType]. /// /// Used by [SlideToDismissDelegate.onDismissed]. typedef void DismissSlideActionCallback(SlideActionType actionType); +/// Signature for determining whether the widget will be dismissed for the +/// given [actionType]. +/// +/// Used by [SlideToDismissDelegate.onWillDismiss]. +typedef FutureOr SlideActionWillBeDismissed(SlideActionType actionType); + /// Signature for the builder callback used to create slide actions. typedef Widget SlideActionBuilder(BuildContext context, int index, Animation animation, SlidableRenderingMode step); @@ -61,6 +69,7 @@ abstract class SlideToDismissDelegate { this.onDismissed, this.resizeDuration: _kResizeDuration, this.crossAxisEndOffset: 0.0, + this.onWillDismiss, }) : assert(dismissThresholds != null); /// The offset threshold the item has to be dragged in order to be considered @@ -81,6 +90,12 @@ abstract class SlideToDismissDelegate { /// Called when the widget has been dismissed, after finishing resizing. final DismissSlideActionCallback onDismissed; + /// Called before the widget is dismissed. If the call returns false, the + /// item will not be dismissed. + /// + /// If null, the widget will always be dismissed. + final SlideActionWillBeDismissed onWillDismiss; + /// Called when the widget changes size (i.e., when contracting before being dismissed). final VoidCallback onResize; @@ -120,12 +135,14 @@ class SlideToDismissDrawerDelegate extends SlideToDismissDelegate { DismissSlideActionCallback onDismissed, Duration resizeDuration: _kResizeDuration, double crossAxisEndOffset: 0.0, + SlideActionWillBeDismissed onWillDismiss, }) : super( dismissThresholds: dismissThresholds, onResize: onResize, onDismissed: onDismissed, resizeDuration: resizeDuration, crossAxisEndOffset: crossAxisEndOffset, + onWillDismiss: onWillDismiss, ); Widget buildActionsWhileDismissing( @@ -949,12 +966,17 @@ class SlidableState extends State updateKeepAlive(); } - void _handleDismissStatusChanged(AnimationStatus status) { + void _handleDismissStatusChanged(AnimationStatus status) async { if (dismissible) { if (status == AnimationStatus.completed && _overallMoveController.value == _overallMoveController.upperBound && !_dragUnderway) { - _startResizeAnimation(); + if (widget.slideToDismissDelegate.onWillDismiss == null || + await widget.slideToDismissDelegate.onWillDismiss(actionType)) { + _startResizeAnimation(); + } else { + open(); + } } updateKeepAlive(); }