diff --git a/lib/flutter_card_swiper.dart b/lib/flutter_card_swiper.dart index 9db9166..8071972 100644 --- a/lib/flutter_card_swiper.dart +++ b/lib/flutter_card_swiper.dart @@ -3,6 +3,7 @@ /// animations supporting Android, iOS, Web & Desktop. library flutter_card_swiper; +export 'package:flutter_card_swiper/src/card_swipe_direction.dart'; export 'package:flutter_card_swiper/src/card_swiper.dart'; export 'package:flutter_card_swiper/src/card_swiper_controller.dart'; export 'package:flutter_card_swiper/src/enums.dart'; diff --git a/lib/src/card_animation.dart b/lib/src/card_animation.dart index 6ccba0a..59e31d2 100644 --- a/lib/src/card_animation.dart +++ b/lib/src/card_animation.dart @@ -10,16 +10,22 @@ class CardAnimation { required this.maxAngle, required this.initialScale, required this.initialOffset, - this.isHorizontalSwipingEnabled = true, - this.isVerticalSwipingEnabled = true, + @Deprecated('Use [cardSwipeDirection] instead]') + this.isHorizontalSwipingEnabled = true, + @Deprecated('Use [cardSwipeDirection] instead]') + this.isVerticalSwipingEnabled = true, + this.cardSwipeDirection = const AllowedSwipeDirection.all(), }) : scale = initialScale; final double maxAngle; final double initialScale; final Offset initialOffset; final AnimationController animationController; + @Deprecated('Use [cardSwipeDirection] instead]') final bool isHorizontalSwipingEnabled; + @Deprecated('Use [cardSwipeDirection] instead]') final bool isVerticalSwipingEnabled; + final AllowedSwipeDirection cardSwipeDirection; double left = 0; double top = 0; @@ -53,12 +59,28 @@ class CardAnimation { } void update(double dx, double dy, bool inverseAngle) { + //TODO: remove [isHorizontalSwipingEnabled] checks in the next major release if (isHorizontalSwipingEnabled) { - left += dx; + if (cardSwipeDirection.right && cardSwipeDirection.left) { + left += dx; + } else if (cardSwipeDirection.right) { + if (left >= 0) left += dx; + } else if (cardSwipeDirection.left) { + if (left <= 0) left += dx; + } } + + //TODO: remove [isHorizontalSwipingEnabled] checks in the next major release if (isVerticalSwipingEnabled) { - top += dy; + if (cardSwipeDirection.up && cardSwipeDirection.down) { + top += dy; + } else if (cardSwipeDirection.up) { + if (top <= 0) top += dy; + } else if (cardSwipeDirection.down) { + if (top >= 0) top += dy; + } } + total = left + top; updateAngle(inverseAngle); updateScale(); diff --git a/lib/src/card_swipe_direction.dart b/lib/src/card_swipe_direction.dart new file mode 100644 index 0000000..287fb7c --- /dev/null +++ b/lib/src/card_swipe_direction.dart @@ -0,0 +1,62 @@ +/// Class to define the direction in which the card can be swiped +class AllowedSwipeDirection { + /// Set to true to allow the card to be swiped in the up direction + final bool up; + + /// Set to true to allow the card to be swiped in the down direction + final bool down; + + /// Set to true to allow the card to be swiped in the left direction + final bool left; + + /// Set to true to allow the card to be swiped in the right direction + final bool right; + + /// Define the direction in which the card can be swiped + const AllowedSwipeDirection._({ + required this.up, + required this.down, + required this.left, + required this.right, + }); + + /// Allow the card to be swiped in any direction + const AllowedSwipeDirection.all() + : up = true, + down = true, + right = true, + left = true; + + /// Does not allow the card to be swiped in any direction + const AllowedSwipeDirection.none() + : up = false, + down = false, + right = false, + left = false; + + /// Allow the card to be swiped in only the specified directions + factory AllowedSwipeDirection.only({ + up = false, + down = false, + left = false, + right = false, + }) => + AllowedSwipeDirection._( + up: up, + down: down, + left: left, + right: right, + ); + + /// Allow the card to be swiped in symmetrically in horizontal or vertical directions + factory AllowedSwipeDirection.symmetric({ + horizontal = false, + vertical = false, + }) => + AllowedSwipeDirection._( + up: vertical, + down: vertical, + right: horizontal, + left: horizontal, + ); +} diff --git a/lib/src/card_swiper.dart b/lib/src/card_swiper.dart index 0de5f5f..7a26268 100644 --- a/lib/src/card_swiper.dart +++ b/lib/src/card_swiper.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flutter/widgets.dart'; import 'package:flutter_card_swiper/src/card_animation.dart'; +import 'package:flutter_card_swiper/src/card_swipe_direction.dart'; import 'package:flutter_card_swiper/src/card_swiper_controller.dart'; import 'package:flutter_card_swiper/src/enums.dart'; import 'package:flutter_card_swiper/src/extensions.dart'; @@ -92,11 +93,21 @@ class CardSwiper extends StatefulWidget { final CardSwiperDirection direction; /// A boolean value that determines whether the card can be swiped horizontally. The default value is true. + @Deprecated( + 'Will be deprecated in the next major release. Use [AllowedSwipeDirection] instead', + ) final bool isHorizontalSwipingEnabled; /// A boolean value that determines whether the card can be swiped vertically. The default value is true. + @Deprecated( + 'Will be deprecated in the next major release. Use [AllowedSwipeDirection] instead', + ) final bool isVerticalSwipingEnabled; + /// Defined the directions in which the card is allowed to be swiped. + /// Defaults to [AllowedSwipeDirection.all] + final AllowedSwipeDirection allowedSwipeDirection; + /// A boolean value that determines whether the card stack should loop. When the last card is swiped, /// if isLoop is true, the first card will become the last card again. The default value is true. final bool isLoop; @@ -138,8 +149,11 @@ class CardSwiper extends StatefulWidget { this.onSwipe, this.onEnd, this.direction = CardSwiperDirection.right, - this.isHorizontalSwipingEnabled = true, - this.isVerticalSwipingEnabled = true, + @Deprecated('Will be deprecated in the next major release. Use [allowedSwipeDirection] instead') + this.isHorizontalSwipingEnabled = true, + @Deprecated('Will be deprecated in the next major release. Use [allowedSwipeDirection] instead') + this.isVerticalSwipingEnabled = true, + this.allowedSwipeDirection = const AllowedSwipeDirection.all(), this.isLoop = true, this.numberOfCardsDisplayed = 2, this.onUndo, @@ -187,7 +201,9 @@ class _CardSwiperState extends State final Queue _directionHistory = Queue(); int? get _currentIndex => _undoableIndex.state; + int? get _nextIndex => getValidIndexOffset(1); + bool get _canSwipe => _currentIndex != null && !widget.isDisabled; @override @@ -211,6 +227,7 @@ class _CardSwiperState extends State initialScale: widget.scale, isVerticalSwipingEnabled: widget.isVerticalSwipingEnabled, isHorizontalSwipingEnabled: widget.isHorizontalSwipingEnabled, + cardSwipeDirection: widget.allowedSwipeDirection, initialOffset: widget.backCardOffset, ); } @@ -376,12 +393,26 @@ class _CardSwiperState extends State final direction = _cardAnimation.left.isNegative ? CardSwiperDirection.left : CardSwiperDirection.right; - _swipe(direction); + if (direction == CardSwiperDirection.left && + widget.allowedSwipeDirection.left || + direction == CardSwiperDirection.right && + widget.allowedSwipeDirection.right) { + _swipe(direction); + } else { + _goBack(); + } } else if (_cardAnimation.top.abs() > widget.threshold) { final direction = _cardAnimation.top.isNegative ? CardSwiperDirection.top : CardSwiperDirection.bottom; - _swipe(direction); + if (direction == CardSwiperDirection.top && + widget.allowedSwipeDirection.up || + direction == CardSwiperDirection.bottom && + widget.allowedSwipeDirection.down) { + _swipe(direction); + } else { + _goBack(); + } } else { _goBack(); } diff --git a/test/card_swipe_direction_test.dart b/test/card_swipe_direction_test.dart new file mode 100644 index 0000000..3cb0902 --- /dev/null +++ b/test/card_swipe_direction_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter_card_swiper/flutter_card_swiper.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('CardSwipeDirection.all() has all directions as true', () { + const directions = AllowedSwipeDirection.all(); + expect(directions.up, true); + expect(directions.down, true); + expect(directions.right, true); + expect(directions.left, true); + }); + + test('CardSwipeDirection.none() has all directions as false', () { + const directions = AllowedSwipeDirection.none(); + expect(directions.up, false); + expect(directions.down, false); + expect(directions.right, false); + expect(directions.left, false); + }); + + group('CardSwipeDirection.only() tests', () { + test('CardSwipeDirection.only(up:true) has only the up direction as true', + () { + final directions = AllowedSwipeDirection.only(up: true); + expect(directions.up, true); + expect(directions.down, false); + expect(directions.right, false); + expect(directions.left, false); + }); + + test( + 'CardSwipeDirection.only(down:true) has only the set direction as true', + () { + final directions = AllowedSwipeDirection.only(down: true); + expect(directions.up, false); + expect(directions.down, true); + expect(directions.right, false); + expect(directions.left, false); + }); + + test( + 'CardSwipeDirection.only(right:true) has only the set direction as true', + () { + final directions = AllowedSwipeDirection.only(right: true); + expect(directions.up, false); + expect(directions.down, false); + expect(directions.right, true); + expect(directions.left, false); + }); + + test( + 'CardSwipeDirection.only(left:true) has only the set direction as true', + () { + final directions = AllowedSwipeDirection.only(left: true); + expect(directions.up, false); + expect(directions.down, false); + expect(directions.right, false); + expect(directions.left, true); + }); + }); + + group('CardSwipeDirection.symmetric() tests', () { + test( + 'CardSwipeDirection.symmetric(horizontal:true) has left and right as true', + () { + final directions = AllowedSwipeDirection.symmetric(horizontal: true); + expect(directions.up, false); + expect(directions.down, false); + expect(directions.right, true); + expect(directions.left, true); + }); + + test('CardSwipeDirection.symmetric(vertical:true) has up and down as true', + () { + final directions = AllowedSwipeDirection.symmetric(vertical: true); + expect(directions.up, true); + expect(directions.down, true); + expect(directions.right, false); + expect(directions.left, false); + }); + }); +} diff --git a/test/card_swiper_test.dart b/test/card_swiper_test.dart index 654ae1d..dc6c002 100644 --- a/test/card_swiper_test.dart +++ b/test/card_swiper_test.dart @@ -465,4 +465,174 @@ void main() { expect(isCalled, true); }); }); + + group('Card swipe direction tests', () { + testWidgets( + 'when swiping right and AllowedSwipeDirection.right=true,' + ' expect to see the next card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(right: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragRight(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(1), findsOneWidget); + }); + + testWidgets( + 'when swiping right and AllowedSwipeDirection.right=false,' + ' expect to see the same card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(left: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragRight(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(0), findsOneWidget); + }); + + testWidgets( + 'when swiping left and AllowedSwipeDirection.left=true,' + ' expect to see the next card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(left: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragLeft(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(1), findsOneWidget); + }); + + testWidgets( + 'when swiping left and AllowedSwipeDirection.left=false,' + ' expect to see the same card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(right: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragLeft(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(0), findsOneWidget); + }); + + testWidgets( + 'when swiping up and AllowedSwipeDirection.up=true,' + ' expect to see the next card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(up: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragUp(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(1), findsOneWidget); + }); + + testWidgets( + 'when swiping up and AllowedSwipeDirection.up=false,' + ' expect to see the same card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(down: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragUp(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(0), findsOneWidget); + }); + + testWidgets( + 'when swiping down and AllowedSwipeDirection.down=true,' + ' expect to see the next card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(down: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragDown(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(1), findsOneWidget); + }); + + testWidgets( + 'when swiping down and AllowedSwipeDirection.down=false,' + ' expect to see the same card', (WidgetTester tester) async { + final swiperKey = GlobalKey(); + + await tester.pumpApp( + CardSwiper( + key: swiperKey, + cardsCount: 10, + numberOfCardsDisplayed: 1, + allowedSwipeDirection: AllowedSwipeDirection.only(up: true), + cardBuilder: genericBuilder, + ), + ); + + await tester.dragDown(swiperKey); + await tester.pumpAndSettle(); + + expect(find.card(0), findsOneWidget); + }); + }); }