Move Swipeable to separate package
This commit is contained in:
parent
84a4a2c0c4
commit
23936fa7f1
|
@ -1,481 +0,0 @@
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
const double _kMinFlingVelocity = 700.0;
|
|
||||||
const double _kMinFlingVelocityDelta = 400.0;
|
|
||||||
const double _kFlingVelocityScale = 1.0 / 300.0;
|
|
||||||
const double _kDismissThreshold = 0.4;
|
|
||||||
|
|
||||||
/// Signature used by [Swipeable] to indicate that it has been swiped in
|
|
||||||
/// the given `direction`.
|
|
||||||
///
|
|
||||||
/// Used by [Swipeable.onSwiped].
|
|
||||||
typedef SwipeDirectionCallback = void Function(SwipeDirection direction);
|
|
||||||
|
|
||||||
/// Signature used by [Swipeable] to give the application an opportunity to
|
|
||||||
/// confirm or veto a swipe gesture.
|
|
||||||
///
|
|
||||||
/// Used by [Swipeable.confirmSwipe].
|
|
||||||
typedef ConfirmSwipeCallback = Future<bool> Function(SwipeDirection direction);
|
|
||||||
|
|
||||||
/// The direction in which a [Swipeable] can be swiped.
|
|
||||||
enum SwipeDirection {
|
|
||||||
/// The [Swipeable] can be swiped by dragging either left or right.
|
|
||||||
horizontal,
|
|
||||||
|
|
||||||
/// The [Swipeable] can be swiped by dragging in the reverse of the
|
|
||||||
/// reading direction (e.g., from right to left in left-to-right languages).
|
|
||||||
endToStart,
|
|
||||||
|
|
||||||
/// The [Swipeable] can be swiped by dragging in the reading direction
|
|
||||||
/// (e.g., from left to right in left-to-right languages).
|
|
||||||
startToEnd,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Swipeable extends StatefulWidget {
|
|
||||||
/// Creates a widget that calls a function when swiped.
|
|
||||||
///
|
|
||||||
/// The [key] argument must not be null because [Swipeable]s are commonly
|
|
||||||
/// used in lists and removed from the list when dismissed. Without keys, the
|
|
||||||
/// default behavior is to sync widgets based on their index in the list,
|
|
||||||
/// which means the item after the dismissed item would be synced with the
|
|
||||||
/// state of the dismissed item. Using keys causes the widgets to sync
|
|
||||||
/// according to their keys and avoids this pitfall.
|
|
||||||
const Swipeable({
|
|
||||||
@required Key key,
|
|
||||||
@required this.child,
|
|
||||||
this.background,
|
|
||||||
this.secondaryBackground,
|
|
||||||
this.confirmSwipe,
|
|
||||||
this.onSwiped,
|
|
||||||
this.direction = SwipeDirection.horizontal,
|
|
||||||
this.dismissThresholds = const <SwipeDirection, double>{},
|
|
||||||
this.maxOffset = 0.4,
|
|
||||||
this.movementDuration = const Duration(milliseconds: 200),
|
|
||||||
this.crossAxisEndOffset = 0.0,
|
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
|
||||||
this.allowedPointerKinds = const {
|
|
||||||
PointerDeviceKind.invertedStylus,
|
|
||||||
PointerDeviceKind.stylus,
|
|
||||||
PointerDeviceKind.touch
|
|
||||||
},
|
|
||||||
}) : assert(key != null),
|
|
||||||
assert(secondaryBackground == null || background != null),
|
|
||||||
assert(dragStartBehavior != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// The widget below this widget in the tree.
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.child}
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// A widget that is stacked behind the child. If secondaryBackground is also
|
|
||||||
/// specified then this widget only appears when the child has been dragged
|
|
||||||
/// to the right.
|
|
||||||
final Widget background;
|
|
||||||
|
|
||||||
/// A widget that is stacked behind the child and is exposed when the child
|
|
||||||
/// has been dragged to the left. It may only be specified when background
|
|
||||||
/// has also been specified.
|
|
||||||
final Widget secondaryBackground;
|
|
||||||
|
|
||||||
/// Gives the app an opportunity to confirm or veto a pending dismissal.
|
|
||||||
///
|
|
||||||
/// If the returned Future<bool> completes true, then this widget will be
|
|
||||||
/// dismissed, otherwise it will be moved back to its original location.
|
|
||||||
///
|
|
||||||
/// If the returned Future<bool> completes to false or null the [onSwiped]
|
|
||||||
/// callback will not run.
|
|
||||||
final ConfirmSwipeCallback confirmSwipe;
|
|
||||||
|
|
||||||
/// Called when the widget has been dismissed, after finishing resizing.
|
|
||||||
final SwipeDirectionCallback onSwiped;
|
|
||||||
|
|
||||||
/// The direction in which the widget can be dismissed.
|
|
||||||
final SwipeDirection direction;
|
|
||||||
|
|
||||||
/// 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 (the default), 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.
|
|
||||||
///
|
|
||||||
/// Flinging is treated as being equivalent to dragging almost to 1.0, so
|
|
||||||
/// flinging can dismiss an item past any threshold less than 1.0.
|
|
||||||
///
|
|
||||||
/// Setting a threshold of 1.0 (or greater) prevents a drag in the given
|
|
||||||
/// [SwipeDirection] even if it would be allowed by the [direction]
|
|
||||||
/// property.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [direction], which controls the directions in which the items can
|
|
||||||
/// be dismissed.
|
|
||||||
final Map<SwipeDirection, double> dismissThresholds;
|
|
||||||
|
|
||||||
/// The maximum horizontal offset the item can move to/
|
|
||||||
///
|
|
||||||
/// Represented as a fraction, e.g. if it is 0.4 (the default), then the
|
|
||||||
/// item can be moved at maximum 40% of item's width.
|
|
||||||
final double maxOffset;
|
|
||||||
|
|
||||||
/// Defines the duration for card to dismiss or to come back to original position if not dismissed.
|
|
||||||
final Duration movementDuration;
|
|
||||||
|
|
||||||
/// Defines the end offset across the main axis after the card is dismissed.
|
|
||||||
///
|
|
||||||
/// If non-zero value is given then widget moves in cross direction depending on whether
|
|
||||||
/// it is positive or negative.
|
|
||||||
final double crossAxisEndOffset;
|
|
||||||
|
|
||||||
/// Defines pointer types which are allowed to trigger swipe gesture.
|
|
||||||
///
|
|
||||||
/// Defaults to {PointerDeviceKind.touch, PointerDeviceKind.invertedStylus, PointerDeviceKind.stylus}
|
|
||||||
final Set<PointerDeviceKind> allowedPointerKinds;
|
|
||||||
|
|
||||||
/// Determines the way that drag start behavior is handled.
|
|
||||||
///
|
|
||||||
/// If set to [DragStartBehavior.start], the drag gesture used to dismiss a
|
|
||||||
/// dismissible will begin upon the detection of a drag gesture. If set to
|
|
||||||
/// [DragStartBehavior.down] it will begin when a down event is first detected.
|
|
||||||
///
|
|
||||||
/// In general, setting this to [DragStartBehavior.start] will make drag
|
|
||||||
/// animation smoother and setting it to [DragStartBehavior.down] will make
|
|
||||||
/// drag behavior feel slightly more reactive.
|
|
||||||
///
|
|
||||||
/// By default, the drag start behavior is [DragStartBehavior.start].
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
|
|
||||||
final DragStartBehavior dragStartBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_SwipeableState createState() => _SwipeableState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SwipeableClipper extends CustomClipper<Rect> {
|
|
||||||
_SwipeableClipper({
|
|
||||||
@required this.moveAnimation,
|
|
||||||
}) : assert(moveAnimation != null),
|
|
||||||
super(reclip: moveAnimation);
|
|
||||||
|
|
||||||
final Animation<Offset> moveAnimation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Rect getClip(Size size) {
|
|
||||||
final offset = moveAnimation.value.dx * size.width;
|
|
||||||
if (offset < 0) {
|
|
||||||
return Rect.fromLTRB(size.width + offset, 0.0, size.width, size.height);
|
|
||||||
}
|
|
||||||
return Rect.fromLTRB(0.0, 0.0, offset, size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Rect getApproximateClipRect(Size size) => getClip(size);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldReclip(_SwipeableClipper oldClipper) {
|
|
||||||
return oldClipper.moveAnimation.value != moveAnimation.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum _FlingGestureKind { none, forward, reverse }
|
|
||||||
|
|
||||||
class _SwipeableState extends State<Swipeable>
|
|
||||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_moveController =
|
|
||||||
AnimationController(duration: widget.movementDuration, vsync: this)
|
|
||||||
..addStatusListener(_handleDismissStatusChanged);
|
|
||||||
_updateMoveAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimationController _moveController;
|
|
||||||
Animation<Offset> _moveAnimation;
|
|
||||||
|
|
||||||
double _dragExtent = 0.0;
|
|
||||||
bool _dragUnderway = false;
|
|
||||||
Size _sizePriorToCollapse;
|
|
||||||
|
|
||||||
bool _isTouch = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => _moveController?.isAnimating == true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_moveController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeDirection _extentToDirection(double extent) {
|
|
||||||
if (extent == 0.0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
switch (Directionality.of(context)) {
|
|
||||||
case TextDirection.rtl:
|
|
||||||
return extent < 0
|
|
||||||
? SwipeDirection.startToEnd
|
|
||||||
: SwipeDirection.endToStart;
|
|
||||||
case TextDirection.ltr:
|
|
||||||
return extent > 0
|
|
||||||
? SwipeDirection.startToEnd
|
|
||||||
: SwipeDirection.endToStart;
|
|
||||||
}
|
|
||||||
assert(false);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeDirection get _SwipeDirection => _extentToDirection(_dragExtent);
|
|
||||||
|
|
||||||
bool get _isActive {
|
|
||||||
return _dragUnderway || _moveController.isAnimating;
|
|
||||||
}
|
|
||||||
|
|
||||||
double get _overallDragAxisExtent {
|
|
||||||
final size = context.size;
|
|
||||||
return size.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePointerDown(PointerDownEvent event) {
|
|
||||||
setState(() {
|
|
||||||
_isTouch = widget.allowedPointerKinds.contains(event.kind);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragStart(DragStartDetails details) {
|
|
||||||
_dragUnderway = true;
|
|
||||||
if (_moveController.isAnimating) {
|
|
||||||
_dragExtent =
|
|
||||||
_moveController.value * _overallDragAxisExtent * _dragExtent.sign;
|
|
||||||
_moveController.stop();
|
|
||||||
} else {
|
|
||||||
_dragExtent = 0.0;
|
|
||||||
_moveController.value = 0.0;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_updateMoveAnimation();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragUpdate(DragUpdateDetails details) {
|
|
||||||
if (!_isActive || _moveController.isAnimating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final delta = details.primaryDelta;
|
|
||||||
final oldDragExtent = _dragExtent;
|
|
||||||
switch (widget.direction) {
|
|
||||||
case SwipeDirection.horizontal:
|
|
||||||
_dragExtent += delta;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SwipeDirection.endToStart:
|
|
||||||
switch (Directionality.of(context)) {
|
|
||||||
case TextDirection.rtl:
|
|
||||||
if (_dragExtent + delta > 0) {
|
|
||||||
_dragExtent += delta;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TextDirection.ltr:
|
|
||||||
if (_dragExtent + delta < 0) {
|
|
||||||
_dragExtent += delta;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SwipeDirection.startToEnd:
|
|
||||||
switch (Directionality.of(context)) {
|
|
||||||
case TextDirection.rtl:
|
|
||||||
if (_dragExtent + delta < 0) {
|
|
||||||
_dragExtent += delta;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TextDirection.ltr:
|
|
||||||
if (_dragExtent + delta > 0) {
|
|
||||||
_dragExtent += delta;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (oldDragExtent.sign != _dragExtent.sign) {
|
|
||||||
setState(() {
|
|
||||||
_updateMoveAnimation();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!_moveController.isAnimating) {
|
|
||||||
_moveController.value = _dragExtent.abs() / _overallDragAxisExtent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateMoveAnimation() {
|
|
||||||
final end = _dragExtent.sign;
|
|
||||||
_moveAnimation = _moveController.drive(
|
|
||||||
Tween<Offset>(
|
|
||||||
begin: Offset.zero,
|
|
||||||
end: Offset(widget.maxOffset * end, widget.crossAxisEndOffset),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_FlingGestureKind _describeFlingGesture(Velocity velocity) {
|
|
||||||
assert(widget.direction != null);
|
|
||||||
if (_dragExtent == 0.0) {
|
|
||||||
// If it was a fling, then it was a fling that was let loose at the exact
|
|
||||||
// middle of the range (i.e. when there's no displacement). In that case,
|
|
||||||
// we assume that the user meant to fling it back to the center, as
|
|
||||||
// opposed to having wanted to drag it out one way, then fling it past the
|
|
||||||
// center and into and out the other side.
|
|
||||||
return _FlingGestureKind.none;
|
|
||||||
}
|
|
||||||
final vx = velocity.pixelsPerSecond.dx;
|
|
||||||
final vy = velocity.pixelsPerSecond.dy;
|
|
||||||
SwipeDirection flingDirection;
|
|
||||||
// Verify that the fling is in the generally right direction and fast enough.
|
|
||||||
if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta ||
|
|
||||||
vx.abs() < _kMinFlingVelocity) {
|
|
||||||
return _FlingGestureKind.none;
|
|
||||||
}
|
|
||||||
assert(vx != 0.0);
|
|
||||||
flingDirection = _extentToDirection(vx);
|
|
||||||
|
|
||||||
assert(_SwipeDirection != null);
|
|
||||||
if (flingDirection == _SwipeDirection) {
|
|
||||||
return _FlingGestureKind.forward;
|
|
||||||
}
|
|
||||||
return _FlingGestureKind.reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleDragEnd(DragEndDetails details) async {
|
|
||||||
if (!_isActive || _moveController.isAnimating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_dragUnderway = false;
|
|
||||||
if (_moveController.isCompleted &&
|
|
||||||
await _confirmStartResizeAnimation() == true) {
|
|
||||||
_startResizeAnimation();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final flingVelocity = details.velocity.pixelsPerSecond.dx;
|
|
||||||
switch (_describeFlingGesture(details.velocity)) {
|
|
||||||
case _FlingGestureKind.forward:
|
|
||||||
assert(_dragExtent != 0.0);
|
|
||||||
assert(!_moveController.isDismissed);
|
|
||||||
if ((widget.dismissThresholds[_SwipeDirection] ?? _kDismissThreshold) >=
|
|
||||||
1.0) {
|
|
||||||
await _moveController.reverse();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_dragExtent = flingVelocity.sign;
|
|
||||||
await _moveController.fling(
|
|
||||||
velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
|
||||||
break;
|
|
||||||
case _FlingGestureKind.reverse:
|
|
||||||
assert(_dragExtent != 0.0);
|
|
||||||
assert(!_moveController.isDismissed);
|
|
||||||
_dragExtent = flingVelocity.sign;
|
|
||||||
await _moveController.fling(
|
|
||||||
velocity: -flingVelocity.abs() * _kFlingVelocityScale);
|
|
||||||
break;
|
|
||||||
case _FlingGestureKind.none:
|
|
||||||
if (!_moveController.isDismissed) {
|
|
||||||
// we already know it's not completed, we check that above
|
|
||||||
if (_moveController.value >
|
|
||||||
(widget.dismissThresholds[_SwipeDirection] ??
|
|
||||||
_kDismissThreshold)) {
|
|
||||||
await _moveController.forward();
|
|
||||||
} else {
|
|
||||||
await _moveController.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleDismissStatusChanged(AnimationStatus status) async {
|
|
||||||
if (status == AnimationStatus.completed && !_dragUnderway) {
|
|
||||||
if (await _confirmStartResizeAnimation() == true) {
|
|
||||||
_startResizeAnimation();
|
|
||||||
} else {
|
|
||||||
await _moveController.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateKeepAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _confirmStartResizeAnimation() async {
|
|
||||||
if (widget.confirmSwipe != null) {
|
|
||||||
final direction = _SwipeDirection;
|
|
||||||
assert(direction != null);
|
|
||||||
return widget.confirmSwipe(direction);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startResizeAnimation() {
|
|
||||||
assert(_moveController != null);
|
|
||||||
assert(_moveController.isCompleted);
|
|
||||||
assert(_sizePriorToCollapse == null);
|
|
||||||
_moveController.reverse();
|
|
||||||
if (widget.onSwiped != null) {
|
|
||||||
final direction = _SwipeDirection;
|
|
||||||
assert(direction != null);
|
|
||||||
widget.onSwiped(direction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
|
||||||
|
|
||||||
assert(debugCheckHasDirectionality(context));
|
|
||||||
|
|
||||||
var background = widget.background;
|
|
||||||
if (widget.secondaryBackground != null) {
|
|
||||||
final direction = _SwipeDirection;
|
|
||||||
if (direction == SwipeDirection.endToStart) {
|
|
||||||
background = widget.secondaryBackground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget content = SlideTransition(
|
|
||||||
position: _moveAnimation,
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (background != null) {
|
|
||||||
content = Stack(children: <Widget>[
|
|
||||||
if (!_moveAnimation.isDismissed)
|
|
||||||
Positioned.fill(
|
|
||||||
child: ClipRect(
|
|
||||||
clipper: _SwipeableClipper(
|
|
||||||
moveAnimation: _moveAnimation,
|
|
||||||
),
|
|
||||||
child: background,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
content,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// We are not swiping but we may be being dragging in widget.direction.
|
|
||||||
return Listener(
|
|
||||||
onPointerDown: _handlePointerDown,
|
|
||||||
child: GestureDetector(
|
|
||||||
onHorizontalDragStart: _isTouch ? _handleDragStart : null,
|
|
||||||
onHorizontalDragUpdate: _isTouch ? _handleDragUpdate : null,
|
|
||||||
onHorizontalDragEnd: _isTouch ? _handleDragEnd : null,
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: content,
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,6 +28,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:pedantic/pedantic.dart';
|
import 'package:pedantic/pedantic.dart';
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
|
import 'package:swipe_to_action/swipe_to_action.dart';
|
||||||
|
|
||||||
import '../components/dialogs/send_file_dialog.dart';
|
import '../components/dialogs/send_file_dialog.dart';
|
||||||
import '../components/input_bar.dart';
|
import '../components/input_bar.dart';
|
||||||
|
@ -687,7 +688,7 @@ class _ChatState extends State<_Chat> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
direction: SwipeDirection.startToEnd,
|
direction: SwipeDirection.startToEnd,
|
||||||
onSwiped: (direction) {
|
onSwipe: (direction) {
|
||||||
replyAction(
|
replyAction(
|
||||||
replyTo: filteredEvents[i - 1]);
|
replyTo: filteredEvents[i - 1]);
|
||||||
},
|
},
|
||||||
|
|
|
@ -870,6 +870,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0-nullsafety.1"
|
version: "1.1.0-nullsafety.1"
|
||||||
|
swipe_to_action:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: swipe_to_action
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0"
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -69,6 +69,7 @@ dependencies:
|
||||||
ref: master
|
ref: master
|
||||||
flutter_blurhash: ^0.5.0
|
flutter_blurhash: ^0.5.0
|
||||||
scroll_to_index: ^1.0.6
|
scroll_to_index: ^1.0.6
|
||||||
|
swipe_to_action: ^0.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue