feat: add undo feature (#1)
This commit is contained in:
parent
23c5e9f0d3
commit
b5bb8f5c9a
|
|
@ -1,3 +1,7 @@
|
||||||
|
## [4.1.0]
|
||||||
|
|
||||||
|
- Adds option to undo swipes.
|
||||||
|
|
||||||
## [4.0.2]
|
## [4.0.2]
|
||||||
|
|
||||||
- Fixes `onSwipe` callback being called twice.
|
- Fixes `onSwipe` callback being called twice.
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ class Example extends StatelessWidget {
|
||||||
| isLoop | true | Set to `true` if the stack should loop | false |
|
| isLoop | true | Set to `true` if the stack should loop | false |
|
||||||
| onTapDisabled | - | Function that get triggered when the swiper is disabled | false |
|
| onTapDisabled | - | Function that get triggered when the swiper is disabled | false |
|
||||||
| onSwipe | - | Function that is called when the user swipes a card. If the function returns `false`, the swipe action is canceled. If it returns `true`, the swipe action is performed as expected | false |
|
| onSwipe | - | Function that is called when the user swipes a card. If the function returns `false`, the swipe action is canceled. If it returns `true`, the swipe action is performed as expected | false |
|
||||||
|
| onUndo | - | Function that is called when the controller calls undo. If the function returns `false`, the undo action is canceled. If it returns `true`, the undo action is performed as expected | false |
|
||||||
| onEnd | - | Function that is called when there are no more cards left to swipe | false |
|
| onEnd | - | Function that is called when there are no more cards left to swipe | false |
|
||||||
| direction | right | Direction in which the card is swiped away when triggered from the outside | false |
|
| direction | right | Direction in which the card is swiped away when triggered from the outside | false |
|
||||||
| numberOfCardsDisplayed | 2 | Number of cards to display at a time | false |
|
| numberOfCardsDisplayed | 2 | Number of cards to display at a time | false |
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,20 @@ class _ExamplePageState extends State<Example> {
|
||||||
cardsCount: cards.length,
|
cardsCount: cards.length,
|
||||||
numberOfCardsDisplayed: 3,
|
numberOfCardsDisplayed: 3,
|
||||||
onSwipe: _onSwipe,
|
onSwipe: _onSwipe,
|
||||||
|
onUndo: _onUndo,
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
cardBuilder: (context, index) => cards[index],
|
cardBuilder: (context, index) => cards[index],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: controller.undo,
|
||||||
|
child: const Icon(Icons.rotate_left),
|
||||||
|
),
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onPressed: controller.swipe,
|
onPressed: controller.swipe,
|
||||||
child: const Icon(Icons.rotate_right),
|
child: const Icon(Icons.rotate_right),
|
||||||
|
|
@ -77,12 +82,23 @@ class _ExamplePageState extends State<Example> {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _onSwipe(
|
bool _onSwipe(
|
||||||
int? previousIndex,
|
int previousIndex,
|
||||||
int? currentIndex,
|
int? currentIndex,
|
||||||
CardSwiperDirection direction,
|
CardSwiperDirection direction,
|
||||||
) {
|
) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'the card $previousIndex was swiped to the ${direction.name}. Now the card $currentIndex is on top',
|
'The card $previousIndex was swiped to the ${direction.name}. Now the card $currentIndex is on top',
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _onUndo(
|
||||||
|
int? previousIndex,
|
||||||
|
int currentIndex,
|
||||||
|
CardSwiperDirection direction,
|
||||||
|
) {
|
||||||
|
debugPrint(
|
||||||
|
'The card $currentIndex was undod from the ${direction.name}',
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,4 +162,63 @@ class CardAnimation {
|
||||||
).animate(animationController);
|
).animate(animationController);
|
||||||
animationController.forward();
|
animationController.forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void animateUndo(BuildContext context, CardSwiperDirection direction) {
|
||||||
|
switch (direction) {
|
||||||
|
case CardSwiperDirection.left:
|
||||||
|
return animateUndoHorizontally(context, false);
|
||||||
|
case CardSwiperDirection.right:
|
||||||
|
return animateUndoHorizontally(context, true);
|
||||||
|
case CardSwiperDirection.top:
|
||||||
|
return animateUndoVertically(context, false);
|
||||||
|
case CardSwiperDirection.bottom:
|
||||||
|
return animateUndoVertically(context, true);
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void animateUndoHorizontally(BuildContext context, bool isToRight) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
|
_leftAnimation = Tween<double>(
|
||||||
|
begin: isToRight ? size.width : -size.width,
|
||||||
|
end: 0,
|
||||||
|
).animate(animationController);
|
||||||
|
_topAnimation = Tween<double>(
|
||||||
|
begin: top,
|
||||||
|
end: top + top,
|
||||||
|
).animate(animationController);
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 1.0,
|
||||||
|
end: scale,
|
||||||
|
).animate(animationController);
|
||||||
|
_differenceAnimation = Tween<double>(
|
||||||
|
begin: 0,
|
||||||
|
end: difference,
|
||||||
|
).animate(animationController);
|
||||||
|
animationController.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
void animateUndoVertically(BuildContext context, bool isToBottom) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
|
_leftAnimation = Tween<double>(
|
||||||
|
begin: left,
|
||||||
|
end: left + left,
|
||||||
|
).animate(animationController);
|
||||||
|
_topAnimation = Tween<double>(
|
||||||
|
begin: isToBottom ? -size.height : size.height,
|
||||||
|
end: 0,
|
||||||
|
).animate(animationController);
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 1.0,
|
||||||
|
end: scale,
|
||||||
|
).animate(animationController);
|
||||||
|
_differenceAnimation = Tween<double>(
|
||||||
|
begin: 0,
|
||||||
|
end: difference,
|
||||||
|
).animate(animationController);
|
||||||
|
animationController.forward();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
@ -6,6 +7,7 @@ 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';
|
||||||
import 'package:flutter_card_swiper/src/typedefs.dart';
|
import 'package:flutter_card_swiper/src/typedefs.dart';
|
||||||
|
import 'package:flutter_card_swiper/src/undoable.dart';
|
||||||
|
|
||||||
class CardSwiper extends StatefulWidget {
|
class CardSwiper extends StatefulWidget {
|
||||||
/// Function that builds each card in the stack.
|
/// Function that builds each card in the stack.
|
||||||
|
|
@ -97,6 +99,13 @@ class CardSwiper extends StatefulWidget {
|
||||||
/// The default value is 2. Note that you must display at least one card, and no more than the [cardsCount] parameter.
|
/// The default value is 2. Note that you must display at least one card, and no more than the [cardsCount] parameter.
|
||||||
final int numberOfCardsDisplayed;
|
final int numberOfCardsDisplayed;
|
||||||
|
|
||||||
|
/// Callback function that is called when a card is unswiped.
|
||||||
|
///
|
||||||
|
/// The function is called with the oldIndex, the currentIndex and the direction of the previous swipe.
|
||||||
|
/// If the function returns `false`, the undo action is canceled and the current card remains
|
||||||
|
/// on top of the stack. If the function returns `true`, the undo action is performed as expected.
|
||||||
|
final CardSwiperOnUndo? onUndo;
|
||||||
|
|
||||||
const CardSwiper({
|
const CardSwiper({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.cardBuilder,
|
required this.cardBuilder,
|
||||||
|
|
@ -117,6 +126,7 @@ class CardSwiper extends StatefulWidget {
|
||||||
this.isVerticalSwipingEnabled = true,
|
this.isVerticalSwipingEnabled = true,
|
||||||
this.isLoop = true,
|
this.isLoop = true,
|
||||||
this.numberOfCardsDisplayed = 2,
|
this.numberOfCardsDisplayed = 2,
|
||||||
|
this.onUndo,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
maxAngle >= 0 && maxAngle <= 360,
|
maxAngle >= 0 && maxAngle <= 360,
|
||||||
'maxAngle must be between 0 and 360',
|
'maxAngle must be between 0 and 360',
|
||||||
|
|
@ -135,11 +145,11 @@ class CardSwiper extends StatefulWidget {
|
||||||
),
|
),
|
||||||
assert(
|
assert(
|
||||||
numberOfCardsDisplayed >= 1 && numberOfCardsDisplayed <= cardsCount,
|
numberOfCardsDisplayed >= 1 && numberOfCardsDisplayed <= cardsCount,
|
||||||
'you must display at least one card, and no more than the length of cards parameter',
|
'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]',
|
||||||
),
|
),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
|
|
@ -156,16 +166,18 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
CardSwiperDirection _detectedDirection = CardSwiperDirection.none;
|
CardSwiperDirection _detectedDirection = CardSwiperDirection.none;
|
||||||
bool _tappedOnTop = false;
|
bool _tappedOnTop = false;
|
||||||
|
|
||||||
bool get _canSwipe => _currentIndex != null && !widget.isDisabled;
|
final _undoableIndex = Undoable<int?>(null);
|
||||||
|
final Queue<CardSwiperDirection> _directionHistory = Queue();
|
||||||
|
|
||||||
int? _currentIndex;
|
int? get _currentIndex => _undoableIndex.state;
|
||||||
int? get _nextIndex => getValidIndexOffset(1);
|
int? get _nextIndex => getValidIndexOffset(1);
|
||||||
|
bool get _canSwipe => _currentIndex != null && !widget.isDisabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_currentIndex = widget.initialIndex;
|
_undoableIndex.state = widget.initialIndex;
|
||||||
|
|
||||||
widget.controller?.addListener(_controllerListener);
|
widget.controller?.addListener(_controllerListener);
|
||||||
|
|
||||||
|
|
@ -307,6 +319,8 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
return _swipe(CardSwiperDirection.top);
|
return _swipe(CardSwiperDirection.top);
|
||||||
case CardSwiperState.swipeBottom:
|
case CardSwiperState.swipeBottom:
|
||||||
return _swipe(CardSwiperDirection.bottom);
|
return _swipe(CardSwiperDirection.bottom);
|
||||||
|
case CardSwiperState.undo:
|
||||||
|
return _undo();
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -333,17 +347,18 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleCompleteSwipe() {
|
void _handleCompleteSwipe() {
|
||||||
|
final isLastCard = _currentIndex! == widget.cardsCount - 1;
|
||||||
final shouldCancelSwipe =
|
final shouldCancelSwipe =
|
||||||
widget.onSwipe?.call(_currentIndex, _nextIndex, _detectedDirection) ==
|
widget.onSwipe?.call(_currentIndex!, _nextIndex, _detectedDirection) ==
|
||||||
false;
|
false;
|
||||||
|
|
||||||
if (shouldCancelSwipe) {
|
if (shouldCancelSwipe) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentIndex = _nextIndex;
|
_undoableIndex.state = _nextIndex;
|
||||||
|
_directionHistory.add(_detectedDirection);
|
||||||
|
|
||||||
final isLastCard = _currentIndex == widget.cardsCount - 1;
|
|
||||||
if (isLastCard) {
|
if (isLastCard) {
|
||||||
widget.onEnd?.call();
|
widget.onEnd?.call();
|
||||||
}
|
}
|
||||||
|
|
@ -387,6 +402,28 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
|
||||||
_cardAnimation.animateBack(context);
|
_cardAnimation.animateBack(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _undo() {
|
||||||
|
if (_directionHistory.isEmpty) return;
|
||||||
|
if (_undoableIndex.previousState == null) return;
|
||||||
|
|
||||||
|
final direction = _directionHistory.last;
|
||||||
|
final shouldCancelUndo = widget.onUndo?.call(
|
||||||
|
_currentIndex,
|
||||||
|
_undoableIndex.previousState!,
|
||||||
|
direction,
|
||||||
|
) ==
|
||||||
|
false;
|
||||||
|
|
||||||
|
if (shouldCancelUndo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_undoableIndex.undo();
|
||||||
|
_directionHistory.removeLast();
|
||||||
|
_swipeType = SwipeType.undo;
|
||||||
|
_cardAnimation.animateUndo(context, direction);
|
||||||
|
}
|
||||||
|
|
||||||
int numberOfCardsOnScreen() {
|
int numberOfCardsOnScreen() {
|
||||||
if (widget.isLoop) {
|
if (widget.isLoop) {
|
||||||
return widget.numberOfCardsDisplayed;
|
return widget.numberOfCardsDisplayed;
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,10 @@ class CardSwiperController extends ChangeNotifier {
|
||||||
state = CardSwiperState.swipeBottom;
|
state = CardSwiperState.swipeBottom;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Undo the last swipe by changing the status of the controller
|
||||||
|
void undo() {
|
||||||
|
state = CardSwiperState.undo;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
enum CardSwiperState { swipe, swipeLeft, swipeRight, swipeTop, swipeBottom }
|
enum CardSwiperState {
|
||||||
|
swipe,
|
||||||
|
swipeLeft,
|
||||||
|
swipeRight,
|
||||||
|
swipeTop,
|
||||||
|
swipeBottom,
|
||||||
|
undo
|
||||||
|
}
|
||||||
|
|
||||||
enum CardSwiperDirection { none, left, right, top, bottom }
|
enum CardSwiperDirection { none, left, right, top, bottom }
|
||||||
|
|
||||||
enum SwipeType { none, swipe, back }
|
enum SwipeType { none, swipe, back, undo }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,23 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_card_swiper/src/enums.dart';
|
||||||
|
|
||||||
extension Range on num {
|
extension Range on num {
|
||||||
bool isBetween(num from, num to) {
|
bool isBetween(num from, num to) {
|
||||||
return from <= this && this <= to;
|
return from <= this && this <= to;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CardSwiperDirectionExtension on CardSwiperDirection {
|
||||||
|
Axis get axis {
|
||||||
|
switch (this) {
|
||||||
|
case CardSwiperDirection.left:
|
||||||
|
case CardSwiperDirection.right:
|
||||||
|
return Axis.horizontal;
|
||||||
|
case CardSwiperDirection.top:
|
||||||
|
case CardSwiperDirection.bottom:
|
||||||
|
return Axis.vertical;
|
||||||
|
case CardSwiperDirection.none:
|
||||||
|
throw Exception('Direction is none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter_card_swiper/src/enums.dart';
|
import 'package:flutter_card_swiper/src/enums.dart';
|
||||||
|
|
||||||
typedef CardSwiperOnSwipe = bool Function(
|
typedef CardSwiperOnSwipe = bool Function(
|
||||||
int? previousIndex,
|
int previousIndex,
|
||||||
int? currentIndex,
|
int? currentIndex,
|
||||||
CardSwiperDirection direction,
|
CardSwiperDirection direction,
|
||||||
);
|
);
|
||||||
|
|
@ -9,3 +9,9 @@ typedef CardSwiperOnSwipe = bool Function(
|
||||||
typedef CardSwiperOnEnd = void Function();
|
typedef CardSwiperOnEnd = void Function();
|
||||||
|
|
||||||
typedef CardSwiperOnTapDisabled = void Function();
|
typedef CardSwiperOnTapDisabled = void Function();
|
||||||
|
|
||||||
|
typedef CardSwiperOnUndo = bool Function(
|
||||||
|
int? previousIndex,
|
||||||
|
int currentIndex,
|
||||||
|
CardSwiperDirection direction,
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
class Undoable<T> {
|
||||||
|
Undoable(this._value, {Undoable? previousValue}) : _previous = previousValue;
|
||||||
|
|
||||||
|
T _value;
|
||||||
|
Undoable? _previous;
|
||||||
|
|
||||||
|
T get state => _value;
|
||||||
|
T? get previousState => _previous?.state;
|
||||||
|
|
||||||
|
set state(T newValue) {
|
||||||
|
_previous = Undoable(_value, previousValue: _previous);
|
||||||
|
_value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void undo() {
|
||||||
|
if (_previous != null) {
|
||||||
|
_value = _previous!._value;
|
||||||
|
_previous = _previous?._previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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: 4.0.2
|
version: 4.1.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,11 @@ void main() {
|
||||||
controller.swipeBottom();
|
controller.swipeBottom();
|
||||||
expect(controller.state, CardSwiperState.swipeBottom);
|
expect(controller.state, CardSwiperState.swipeBottom);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('undo() changes state to undo', () {
|
||||||
|
final controller = CardSwiperController();
|
||||||
|
controller.undo();
|
||||||
|
expect(controller.state, CardSwiperState.undo);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'package:flutter/widgets.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';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('isBetween', () {
|
group('num.isBetween', () {
|
||||||
test('should return true when value is within range', () {
|
test('should return true when value is within range', () {
|
||||||
const value = 5;
|
const value = 5;
|
||||||
const from = 1;
|
const from = 1;
|
||||||
|
|
@ -43,4 +45,30 @@ void main() {
|
||||||
expect(result, isFalse);
|
expect(result, isFalse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('CardSwiperDirection.axis', () {
|
||||||
|
test('should return horizontal when direction is left', () {
|
||||||
|
final axis = CardSwiperDirection.left.axis;
|
||||||
|
expect(axis, Axis.horizontal);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return horizontal when direction is right', () {
|
||||||
|
final axis = CardSwiperDirection.right.axis;
|
||||||
|
expect(axis, Axis.horizontal);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return vertical when direction is top', () {
|
||||||
|
final axis = CardSwiperDirection.top.axis;
|
||||||
|
expect(axis, Axis.vertical);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return vertical when direction is bottom', () {
|
||||||
|
final axis = CardSwiperDirection.bottom.axis;
|
||||||
|
expect(axis, Axis.vertical);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw exception when direction is none', () {
|
||||||
|
expect(() => CardSwiperDirection.none.axis, throwsException);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import 'package:flutter_card_swiper/src/undoable.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('should store and retrieve state', () {
|
||||||
|
final undoable = Undoable<int>(0);
|
||||||
|
expect(undoable.state, equals(0));
|
||||||
|
expect(undoable.previousState, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should store previous state when state is changed', () {
|
||||||
|
final undoable = Undoable<int>(0);
|
||||||
|
undoable.state = 1;
|
||||||
|
expect(undoable.state, equals(1));
|
||||||
|
expect(undoable.previousState, equals(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should store previous state when state is changed multiple times', () {
|
||||||
|
final undoable = Undoable<int>(0);
|
||||||
|
undoable.state = 1;
|
||||||
|
undoable.state = 2;
|
||||||
|
expect(undoable.state, equals(2));
|
||||||
|
expect(undoable.previousState, equals(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return previous state when undo is called', () {
|
||||||
|
final undoable = Undoable<int>(0);
|
||||||
|
undoable.state = 1;
|
||||||
|
undoable.undo();
|
||||||
|
expect(undoable.state, equals(0));
|
||||||
|
expect(undoable.previousState, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return previous state when undo is called multiple times', () {
|
||||||
|
final undoable = Undoable<int>(0);
|
||||||
|
undoable.state = 1;
|
||||||
|
undoable.state = 2;
|
||||||
|
undoable.undo();
|
||||||
|
expect(undoable.state, equals(1));
|
||||||
|
expect(undoable.previousState, equals(0));
|
||||||
|
undoable.undo();
|
||||||
|
expect(undoable.state, equals(0));
|
||||||
|
expect(undoable.previousState, isNull);
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue