feat: add the functionality to allow swipes individually in left, right, up or/and down directions

This commit is contained in:
jawwadhassan 2023-05-11 17:30:28 +05:00
parent c55edd8d44
commit 9976486d02
6 changed files with 376 additions and 8 deletions

View File

@ -3,6 +3,7 @@
/// animations supporting Android, iOS, Web & Desktop. /// animations supporting Android, iOS, Web & Desktop.
library flutter_card_swiper; 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.dart';
export 'package:flutter_card_swiper/src/card_swiper_controller.dart'; export 'package:flutter_card_swiper/src/card_swiper_controller.dart';
export 'package:flutter_card_swiper/src/enums.dart'; export 'package:flutter_card_swiper/src/enums.dart';

View File

@ -10,16 +10,22 @@ class CardAnimation {
required this.maxAngle, required this.maxAngle,
required this.initialScale, required this.initialScale,
required this.initialOffset, required this.initialOffset,
this.isHorizontalSwipingEnabled = true, @Deprecated('Use [cardSwipeDirection] instead]')
this.isVerticalSwipingEnabled = true, this.isHorizontalSwipingEnabled = true,
@Deprecated('Use [cardSwipeDirection] instead]')
this.isVerticalSwipingEnabled = true,
this.cardSwipeDirection = const AllowedSwipeDirection.all(),
}) : scale = initialScale; }) : scale = initialScale;
final double maxAngle; final double maxAngle;
final double initialScale; final double initialScale;
final Offset initialOffset; final Offset initialOffset;
final AnimationController animationController; final AnimationController animationController;
@Deprecated('Use [cardSwipeDirection] instead]')
final bool isHorizontalSwipingEnabled; final bool isHorizontalSwipingEnabled;
@Deprecated('Use [cardSwipeDirection] instead]')
final bool isVerticalSwipingEnabled; final bool isVerticalSwipingEnabled;
final AllowedSwipeDirection cardSwipeDirection;
double left = 0; double left = 0;
double top = 0; double top = 0;
@ -53,12 +59,28 @@ class CardAnimation {
} }
void update(double dx, double dy, bool inverseAngle) { void update(double dx, double dy, bool inverseAngle) {
//TODO: remove [isHorizontalSwipingEnabled] checks in the next major release
if (isHorizontalSwipingEnabled) { 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) { 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; total = left + top;
updateAngle(inverseAngle); updateAngle(inverseAngle);
updateScale(); updateScale();

View File

@ -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,
);
}

View File

@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_card_swiper/src/card_animation.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/card_swiper_controller.dart';
import 'package:flutter_card_swiper/src/enums.dart'; import 'package:flutter_card_swiper/src/enums.dart';
import 'package:flutter_card_swiper/src/extensions.dart'; import 'package:flutter_card_swiper/src/extensions.dart';
@ -92,11 +93,21 @@ class CardSwiper extends StatefulWidget {
final CardSwiperDirection direction; final CardSwiperDirection direction;
/// A boolean value that determines whether the card can be swiped horizontally. The default value is true. /// 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; final bool isHorizontalSwipingEnabled;
/// A boolean value that determines whether the card can be swiped vertically. The default value is true. /// 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; 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, /// 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. /// if isLoop is true, the first card will become the last card again. The default value is true.
final bool isLoop; final bool isLoop;
@ -138,8 +149,11 @@ class CardSwiper extends StatefulWidget {
this.onSwipe, this.onSwipe,
this.onEnd, this.onEnd,
this.direction = CardSwiperDirection.right, this.direction = CardSwiperDirection.right,
this.isHorizontalSwipingEnabled = true, @Deprecated('Will be deprecated in the next major release. Use [allowedSwipeDirection] instead')
this.isVerticalSwipingEnabled = true, 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.isLoop = true,
this.numberOfCardsDisplayed = 2, this.numberOfCardsDisplayed = 2,
this.onUndo, this.onUndo,
@ -187,7 +201,9 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
final Queue<CardSwiperDirection> _directionHistory = Queue(); final Queue<CardSwiperDirection> _directionHistory = Queue();
int? get _currentIndex => _undoableIndex.state; int? get _currentIndex => _undoableIndex.state;
int? get _nextIndex => getValidIndexOffset(1); int? get _nextIndex => getValidIndexOffset(1);
bool get _canSwipe => _currentIndex != null && !widget.isDisabled; bool get _canSwipe => _currentIndex != null && !widget.isDisabled;
@override @override
@ -211,6 +227,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
initialScale: widget.scale, initialScale: widget.scale,
isVerticalSwipingEnabled: widget.isVerticalSwipingEnabled, isVerticalSwipingEnabled: widget.isVerticalSwipingEnabled,
isHorizontalSwipingEnabled: widget.isHorizontalSwipingEnabled, isHorizontalSwipingEnabled: widget.isHorizontalSwipingEnabled,
cardSwipeDirection: widget.allowedSwipeDirection,
initialOffset: widget.backCardOffset, initialOffset: widget.backCardOffset,
); );
} }
@ -376,12 +393,26 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
final direction = _cardAnimation.left.isNegative final direction = _cardAnimation.left.isNegative
? CardSwiperDirection.left ? CardSwiperDirection.left
: CardSwiperDirection.right; : 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) { } else if (_cardAnimation.top.abs() > widget.threshold) {
final direction = _cardAnimation.top.isNegative final direction = _cardAnimation.top.isNegative
? CardSwiperDirection.top ? CardSwiperDirection.top
: CardSwiperDirection.bottom; : CardSwiperDirection.bottom;
_swipe(direction); if (direction == CardSwiperDirection.top &&
widget.allowedSwipeDirection.up ||
direction == CardSwiperDirection.bottom &&
widget.allowedSwipeDirection.down) {
_swipe(direction);
} else {
_goBack();
}
} else { } else {
_goBack(); _goBack();
} }

View File

@ -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);
});
});
}

View File

@ -465,4 +465,174 @@ void main() {
expect(isCalled, true); 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);
});
});
} }