Compare commits
3 Commits
main
...
new-card-s
| Author | SHA1 | Date |
|---|---|---|
|
|
9c3d16ce94 | |
|
|
d63bcd5c76 | |
|
|
5fb6777279 |
|
|
@ -1,3 +1,8 @@
|
||||||
|
## [7.1.0]
|
||||||
|
|
||||||
|
- Adds support for tapping a card
|
||||||
|
- `onTap` callback containing currentIndex.
|
||||||
|
|
||||||
## [7.0.2]
|
## [7.0.2]
|
||||||
|
|
||||||
- Added `CardAnimation.animateToAngle` helper to animate swipe the card to any given angle between 0-360°.
|
- Added `CardAnimation.animateToAngle` helper to animate swipe the card to any given angle between 0-360°.
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,36 @@ class CardSwiperController {
|
||||||
_eventController.add(ControllerSwipeEvent(direction));
|
_eventController.add(ControllerSwipeEvent(direction));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undo the last swipe
|
/// Undo the last swipe
|
||||||
void undo() {
|
void undo() {
|
||||||
_eventController.add(const ControllerUndoEvent());
|
_eventController.add(const ControllerUndoEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the top card to a specific index.
|
/// Change the top card to a specific index.
|
||||||
void moveTo(int index) {
|
void moveTo(int index) {
|
||||||
_eventController.add(ControllerMoveEvent(index));
|
_eventController.add(ControllerMoveEvent(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We have a new list of card data for this state machine to handle
|
||||||
|
/// 1. Set the length of the card list
|
||||||
|
/// 2. Move index to 0 (restart stack to first card)
|
||||||
|
void newStack(int length) {
|
||||||
|
_eventController.add(ControllerNewStackEvent(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We have a new added cards to the end
|
||||||
|
/// 0. Keep internal index the same (undo will work, can go backwards)
|
||||||
|
/// 1. Set the internal length of the card list
|
||||||
|
/// 2. Check if we need to redraw, if we are at end of card list
|
||||||
|
// void appendStack(int length) {
|
||||||
|
// _eventController.add(ControllerNewStackEvent(length));
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Set the length only, user can moveTo(0), or other actions
|
||||||
|
// void modifiedStack(int length) {
|
||||||
|
// _eventController.add(ControllerNewStackEvent(length));
|
||||||
|
// }
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _eventController.close();
|
await _eventController.close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,8 @@ class ControllerMoveEvent extends ControllerEvent {
|
||||||
final int index;
|
final int index;
|
||||||
const ControllerMoveEvent(this.index);
|
const ControllerMoveEvent(this.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ControllerNewStackEvent extends ControllerEvent {
|
||||||
|
final int length;
|
||||||
|
const ControllerNewStackEvent(this.length);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,5 @@ typedef CardSwiperOnUndo = bool Function(
|
||||||
int currentIndex,
|
int currentIndex,
|
||||||
CardSwiperDirection direction,
|
CardSwiperDirection direction,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
typedef CardSwiperOnTap = FutureOr<void> Function(int currentIndex);
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@ class CardSwiper extends StatefulWidget {
|
||||||
/// Callback function that is called when the swiper is disabled.
|
/// Callback function that is called when the swiper is disabled.
|
||||||
final CardSwiperOnTapDisabled? onTapDisabled;
|
final CardSwiperOnTapDisabled? onTapDisabled;
|
||||||
|
|
||||||
|
/// Callback function that is called when the card is tapped (will not trigger from swipe movement)
|
||||||
|
final CardSwiperOnTap? onTap;
|
||||||
|
|
||||||
/// Defined the directions in which the card is allowed to be swiped.
|
/// Defined the directions in which the card is allowed to be swiped.
|
||||||
/// Defaults to [AllowedSwipeDirection.all]
|
/// Defaults to [AllowedSwipeDirection.all]
|
||||||
final AllowedSwipeDirection allowedSwipeDirection;
|
final AllowedSwipeDirection allowedSwipeDirection;
|
||||||
|
|
@ -134,6 +137,7 @@ class CardSwiper extends StatefulWidget {
|
||||||
this.scale = 0.9,
|
this.scale = 0.9,
|
||||||
this.isDisabled = false,
|
this.isDisabled = false,
|
||||||
this.onTapDisabled,
|
this.onTapDisabled,
|
||||||
|
this.onTap,
|
||||||
this.onSwipe,
|
this.onSwipe,
|
||||||
this.onEnd,
|
this.onEnd,
|
||||||
this.onSwipeDirectionChange,
|
this.onSwipeDirectionChange,
|
||||||
|
|
@ -155,10 +159,10 @@ class CardSwiper extends StatefulWidget {
|
||||||
scale >= 0 && scale <= 1,
|
scale >= 0 && scale <= 1,
|
||||||
'scale must be between 0 and 1',
|
'scale must be between 0 and 1',
|
||||||
),
|
),
|
||||||
assert(
|
// assert(
|
||||||
numberOfCardsDisplayed >= 1 && numberOfCardsDisplayed <= cardsCount,
|
// numberOfCardsDisplayed >= 1 && numberOfCardsDisplayed <= cardsCount,
|
||||||
'you must display at least one card, and no more than [cardsCount]',
|
// 'you must display at least one card, and no more than [cardsCount]',
|
||||||
),
|
// ),
|
||||||
assert(
|
assert(
|
||||||
initialIndex >= 0 && initialIndex < cardsCount,
|
initialIndex >= 0 && initialIndex < cardsCount,
|
||||||
'initialIndex must be between 0 and [cardsCount]',
|
'initialIndex must be between 0 and [cardsCount]',
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,20 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
|
|
||||||
StreamSubscription<ControllerEvent>? controllerSubscription;
|
StreamSubscription<ControllerEvent>? controllerSubscription;
|
||||||
|
|
||||||
|
late NullableCardBuilder _cardBuilder;
|
||||||
|
late int _cardsCount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
debugPrint('[WTF] _CardSwiperState.initState !!!');
|
||||||
|
debugPrint('[WTF] _CardSwiperState.initState !!!');
|
||||||
|
debugPrint('[WTF] _CardSwiperState.initState !!!');
|
||||||
|
|
||||||
|
_cardBuilder = widget.cardBuilder;
|
||||||
|
_cardsCount = widget.cardsCount;
|
||||||
|
|
||||||
_undoableIndex.state = widget.initialIndex;
|
_undoableIndex.state = widget.initialIndex;
|
||||||
|
|
||||||
controllerSubscription =
|
controllerSubscription =
|
||||||
|
|
@ -67,6 +77,11 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
||||||
|
debugPrint('[WTF] _CardSwiperState.dispose !!!');
|
||||||
|
debugPrint('[WTF] _CardSwiperState.dispose !!!');
|
||||||
|
debugPrint('[WTF] _CardSwiperState.dispose !!!');
|
||||||
|
|
||||||
_animationController.dispose();
|
_animationController.dispose();
|
||||||
controllerSubscription?.cancel();
|
controllerSubscription?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
@ -104,7 +119,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
angle: _cardAnimation.angle,
|
angle: _cardAnimation.angle,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
child: widget.cardBuilder(
|
child: _cardBuilder(
|
||||||
context,
|
context,
|
||||||
_currentIndex!,
|
_currentIndex!,
|
||||||
(100 * _cardAnimation.left / widget.threshold).ceil(),
|
(100 * _cardAnimation.left / widget.threshold).ceil(),
|
||||||
|
|
@ -116,6 +131,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
if (widget.isDisabled) {
|
if (widget.isDisabled) {
|
||||||
await widget.onTapDisabled?.call();
|
await widget.onTapDisabled?.call();
|
||||||
}
|
}
|
||||||
|
await widget.onTap?.call(_currentIndex!);
|
||||||
},
|
},
|
||||||
onPanStart: (tapInfo) {
|
onPanStart: (tapInfo) {
|
||||||
if (!widget.isDisabled) {
|
if (!widget.isDisabled) {
|
||||||
|
|
@ -154,7 +170,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
scale: _cardAnimation.scale - ((1 - widget.scale) * (index - 1)),
|
scale: _cardAnimation.scale - ((1 - widget.scale) * (index - 1)),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
child: widget.cardBuilder(context, getValidIndexOffset(index)!, 0, 0),
|
child: _cardBuilder(context, getValidIndexOffset(index)!, 0, 0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -165,6 +181,8 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
ControllerSwipeEvent(:final direction) => _swipe(direction),
|
ControllerSwipeEvent(:final direction) => _swipe(direction),
|
||||||
ControllerUndoEvent() => _undo(),
|
ControllerUndoEvent() => _undo(),
|
||||||
ControllerMoveEvent(:final index) => _moveTo(index),
|
ControllerMoveEvent(:final index) => _moveTo(index),
|
||||||
|
// controller
|
||||||
|
ControllerNewStackEvent(:final length) => _newCardStack(length)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +206,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleCompleteSwipe() async {
|
Future<void> _handleCompleteSwipe() async {
|
||||||
final isLastCard = _currentIndex! == widget.cardsCount - 1;
|
final isLastCard = _currentIndex! == _cardsCount - 1;
|
||||||
final shouldCancelSwipe = await widget.onSwipe
|
final shouldCancelSwipe = await widget.onSwipe
|
||||||
?.call(_currentIndex!, _nextIndex, _detectedDirection) ==
|
?.call(_currentIndex!, _nextIndex, _detectedDirection) ==
|
||||||
false;
|
false;
|
||||||
|
|
@ -285,14 +303,20 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _moveTo(int index) {
|
void _moveTo(int index) {
|
||||||
|
debugPrint('[WTF] _moveTo() index: $index currentIndex: $_currentIndex cardsCount: $_cardsCount');
|
||||||
if (index == _currentIndex) return;
|
if (index == _currentIndex) return;
|
||||||
if (index < 0 || index >= widget.cardsCount) return;
|
if (index < 0 || index >= _cardsCount) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_undoableIndex.state = index;
|
_undoableIndex.state = index;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _newCardStack(int length) {
|
||||||
|
_cardsCount = length;
|
||||||
|
_moveTo(0); // move to first card, and force redraw if necessary
|
||||||
|
}
|
||||||
|
|
||||||
int numberOfCardsOnScreen() {
|
int numberOfCardsOnScreen() {
|
||||||
if (widget.isLoop) {
|
if (widget.isLoop) {
|
||||||
return widget.numberOfCardsDisplayed;
|
return widget.numberOfCardsDisplayed;
|
||||||
|
|
@ -303,7 +327,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
|
|
||||||
return math.min(
|
return math.min(
|
||||||
widget.numberOfCardsDisplayed,
|
widget.numberOfCardsDisplayed,
|
||||||
widget.cardsCount - _currentIndex!,
|
_cardsCount - _currentIndex!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,9 +337,9 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
}
|
}
|
||||||
|
|
||||||
final index = _currentIndex! + offset;
|
final index = _currentIndex! + offset;
|
||||||
if (!widget.isLoop && !index.isBetween(0, widget.cardsCount - 1)) {
|
if (!widget.isLoop && !index.isBetween(0, _cardsCount - 1)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return index % widget.cardsCount;
|
return index % _cardsCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ name: flutter_card_swiper
|
||||||
description: This is a Tinder-like card swiper package. It allows you to swipe left, right, up, and down and define your own business logic for each direction.
|
description: This is a Tinder-like card swiper package. It allows you to swipe left, right, up, and down and define your own business logic for each direction.
|
||||||
homepage: https://github.com/ricardodalarme/flutter_card_swiper
|
homepage: https://github.com/ricardodalarme/flutter_card_swiper
|
||||||
issue_tracker: https://github.com/ricardodalarme/flutter_card_swiper/issues
|
issue_tracker: https://github.com/ricardodalarme/flutter_card_swiper/issues
|
||||||
version: 7.0.2
|
version: 7.1.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
|
||||||
|
|
@ -634,5 +634,29 @@ void main() {
|
||||||
|
|
||||||
expect(find.card(0), findsOneWidget);
|
expect(find.card(0), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('when tapping a card, expect callback',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final swiperKey = GlobalKey();
|
||||||
|
var index = -1;
|
||||||
|
|
||||||
|
await tester.pumpApp(
|
||||||
|
CardSwiper(
|
||||||
|
key: swiperKey,
|
||||||
|
cardsCount: 3,
|
||||||
|
numberOfCardsDisplayed: 1,
|
||||||
|
initialIndex: 2,
|
||||||
|
onTap: (currentIndex) {
|
||||||
|
index = currentIndex;
|
||||||
|
},
|
||||||
|
cardBuilder: genericBuilder,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(swiperKey));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(index, 2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue