feat: update controller to use stream

This commit is contained in:
ricardodalarme 2024-01-28 14:04:19 -03:00
parent 7b79e303f7
commit a79449dfe6
11 changed files with 162 additions and 400 deletions

View File

@ -3,6 +3,8 @@
- **BREAKING CHANGE**: - **BREAKING CHANGE**:
- Upgrade min dart sdk to 3.0.0 - Upgrade min dart sdk to 3.0.0
- Replace `swipe`, `swipeLeft`, `swipeRight`, `swipeUp`, `swipeDown` with `swipe(CardSwiperDirection direction)`
- It also removes `direction` from the `CardSwiper` widget
## [6.1.0] ## [6.1.0]

View File

@ -116,18 +116,15 @@ class Example extends StatelessWidget {
| padding | EdgeInsets.symmetric(horizontal: 20, vertical: 25) | The padding around the swiper | false | | padding | EdgeInsets.symmetric(horizontal: 20, vertical: 25) | The padding around the swiper | false |
| scale | 0.9 | Scale of the card that is behind the front card | false | | scale | 0.9 | Scale of the card that is behind the front card | false |
| threshold | 50 | Threshold from which the card is swiped away | false | | threshold | 50 | Threshold from which the card is swiped away | false |
| onSwipeDirectionChange | - | A callback containing the horizontal and vertical swipe direction | false | | onSwipeDirectionChange | - | A callback containing the horizontal and vertical swipe direction
| false |
#### Controller #### Controller
The `Controller` is used to swipe the card from outside of the widget. You can create a controller called `CardSwiperController` and save the instance for further usage. Please have a closer look at our [Example](https://github.com/ricardodalarme/flutter_card_swiper/tree/main/example) for the usage. The `Controller` is used to swipe the card from outside of the widget. You can create a controller called `CardSwiperController` and save the instance for further usage. Please have a closer look at our [Example](https://github.com/ricardodalarme/flutter_card_swiper/tree/main/example) for the usage.
| Method | Description | | Method | Description |
| ----------- | :--------------------------------------------- | | ----------- | :--------------------------------------------- |
| swipe | Swipes the card in the selected direction. | | swipe | Swipes the card to a specific direction. |
| swipeLeft | Swipes the card to the left side. |
| swipeRight | Swipes the card to the right side. |
| swipeTop | Swipes the card to the top side. |
| swipeBottom | Swipes the card to the bottom side. |
| undo | Bring back the last card that was swiped away. | | undo | Bring back the last card that was swiped away. |
<hr/> <hr/>

View File

@ -66,19 +66,21 @@ class _ExamplePageState extends State<Example> {
child: const Icon(Icons.rotate_left), child: const Icon(Icons.rotate_left),
), ),
FloatingActionButton( FloatingActionButton(
onPressed: controller.swipeLeft, onPressed: () => controller.swipe(CardSwiperDirection.left),
child: const Icon(Icons.keyboard_arrow_left), child: const Icon(Icons.keyboard_arrow_left),
), ),
FloatingActionButton( FloatingActionButton(
onPressed: controller.swipeRight, onPressed: () =>
controller.swipe(CardSwiperDirection.right),
child: const Icon(Icons.keyboard_arrow_right), child: const Icon(Icons.keyboard_arrow_right),
), ),
FloatingActionButton( FloatingActionButton(
onPressed: controller.swipeTop, onPressed: () => controller.swipe(CardSwiperDirection.top),
child: const Icon(Icons.keyboard_arrow_up), child: const Icon(Icons.keyboard_arrow_up),
), ),
FloatingActionButton( FloatingActionButton(
onPressed: controller.swipeBottom, onPressed: () =>
controller.swipe(CardSwiperDirection.bottom),
child: const Icon(Icons.keyboard_arrow_down), child: const Icon(Icons.keyboard_arrow_down),
), ),
], ],

View File

@ -3,7 +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_swiper_controller.dart'; export 'package:flutter_card_swiper/src/controller/card_swiper_controller.dart';
export 'package:flutter_card_swiper/src/enums.dart'; export 'package:flutter_card_swiper/src/enums.dart';
export 'package:flutter_card_swiper/src/properties/allowed_swipe_direction.dart'; export 'package:flutter_card_swiper/src/properties/allowed_swipe_direction.dart';
export 'package:flutter_card_swiper/src/typedefs.dart'; export 'package:flutter_card_swiper/src/typedefs.dart';

View File

@ -1,43 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_card_swiper/src/enums.dart';
/// A controller that can be used to trigger swipes on a CardSwiper widget.
class CardSwiperController extends ChangeNotifier {
CardSwiperState? state;
/// Swipe the card by changing the status of the controller
void swipe() {
state = CardSwiperState.swipe;
notifyListeners();
}
/// Swipe the card to the left side by changing the status of the controller
void swipeLeft() {
state = CardSwiperState.swipeLeft;
notifyListeners();
}
/// Swipe the card to the right side by changing the status of the controller
void swipeRight() {
state = CardSwiperState.swipeRight;
notifyListeners();
}
/// Swipe the card to the top side by changing the status of the controller
void swipeTop() {
state = CardSwiperState.swipeTop;
notifyListeners();
}
/// Swipe the card to the bottom side by changing the status of the controller
void swipeBottom() {
state = CardSwiperState.swipeBottom;
notifyListeners();
}
// Undo the last swipe by changing the status of the controller
void undo() {
state = CardSwiperState.undo;
notifyListeners();
}
}

View File

@ -0,0 +1,26 @@
import 'dart:async';
import 'package:flutter_card_swiper/src/controller/controller_event.dart';
import 'package:flutter_card_swiper/src/enums.dart';
/// A controller that can be used to trigger swipes on a CardSwiper widget.
class CardSwiperController {
final _eventController = StreamController<ControllerEvent>.broadcast();
/// Stream of events that can be used to swipe the card.
Stream<ControllerEvent> get events => _eventController.stream;
/// Swipe the card to a specific direction.
void swipe(CardSwiperDirection direction) {
_eventController.add(ControllerSwipeEvent(direction));
}
// Undo the last swipe
void undo() {
_eventController.add(const ControllerUndoEvent());
}
Future<void> dispose() async {
await _eventController.close();
}
}

View File

@ -0,0 +1,15 @@
import 'package:flutter_card_swiper/flutter_card_swiper.dart';
abstract class ControllerEvent {
const ControllerEvent();
}
class ControllerSwipeEvent extends ControllerEvent {
final CardSwiperDirection direction;
const ControllerSwipeEvent(this.direction);
}
class ControllerUndoEvent extends ControllerEvent {
const ControllerUndoEvent();
}

View File

@ -1,12 +1,3 @@
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, undo } enum SwipeType { none, swipe, back, undo }

View File

@ -3,7 +3,8 @@ 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_swiper_controller.dart'; import 'package:flutter_card_swiper/src/controller/card_swiper_controller.dart';
import 'package:flutter_card_swiper/src/controller/controller_event.dart';
import 'package:flutter_card_swiper/src/enums.dart'; import 'package:flutter_card_swiper/src/enums.dart';
import 'package:flutter_card_swiper/src/properties/allowed_swipe_direction.dart'; import 'package:flutter_card_swiper/src/properties/allowed_swipe_direction.dart';
import 'package:flutter_card_swiper/src/typedefs.dart'; import 'package:flutter_card_swiper/src/typedefs.dart';
@ -89,11 +90,6 @@ 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;
/// The direction in which the card is swiped when triggered by the [controller].
///
/// Defaults to [CardSwiperDirection.right].
final CardSwiperDirection direction;
/// 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;
@ -142,7 +138,6 @@ class CardSwiper extends StatefulWidget {
this.onTapDisabled, this.onTapDisabled,
this.onSwipe, this.onSwipe,
this.onEnd, this.onEnd,
this.direction = CardSwiperDirection.right,
this.onSwipeDirectionChange, this.onSwipeDirectionChange,
this.allowedSwipeDirection = const AllowedSwipeDirection.all(), this.allowedSwipeDirection = const AllowedSwipeDirection.all(),
this.isLoop = true, this.isLoop = true,
@ -158,10 +153,6 @@ class CardSwiper extends StatefulWidget {
threshold >= 1 && threshold <= 100, threshold >= 1 && threshold <= 100,
'threshold must be between 1 and 100', 'threshold must be between 1 and 100',
), ),
assert(
direction != CardSwiperDirection.none,
'direction must not be none',
),
assert( assert(
scale >= 0 && scale <= 1, scale >= 0 && scale <= 1,
'scale must be between 0 and 1', 'scale must be between 0 and 1',

View File

@ -26,7 +26,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
_undoableIndex.state = widget.initialIndex; _undoableIndex.state = widget.initialIndex;
widget.controller?.addListener(_controllerListener); widget.controller?.events.listen(_controllerListener);
_animationController = AnimationController( _animationController = AnimationController(
duration: widget.duration, duration: widget.duration,
@ -65,7 +65,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
@override @override
void dispose() { void dispose() {
_animationController.dispose(); _animationController.dispose();
widget.controller?.removeListener(_controllerListener); widget.controller?.dispose();
super.dispose(); super.dispose();
} }
@ -157,23 +157,12 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
); );
} }
void _controllerListener() { void _controllerListener(ControllerEvent event) {
switch (widget.controller?.state) { return switch (event) {
case CardSwiperState.swipe: ControllerSwipeEvent(:final direction) => _swipe(direction),
return _swipe(widget.direction); ControllerUndoEvent() => _undo(),
case CardSwiperState.swipeLeft: _ => null
return _swipe(CardSwiperDirection.left); };
case CardSwiperState.swipeRight:
return _swipe(CardSwiperDirection.right);
case CardSwiperState.swipeTop:
return _swipe(CardSwiperDirection.top);
case CardSwiperState.swipeBottom:
return _swipe(CardSwiperDirection.bottom);
case CardSwiperState.undo:
return _undo();
default:
return;
}
} }
void _animationListener() { void _animationListener() {
@ -225,7 +214,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
void _onEndAnimation() { void _onEndAnimation() {
final direction = _getEndAnimationDirection(); final direction = _getEndAnimationDirection();
final isValidDirection = this._isValidDirection(direction); final isValidDirection = _isValidDirection(direction);
if (isValidDirection) { if (isValidDirection) {
_swipe(direction); _swipe(direction);

View File

@ -1,4 +1,5 @@
import 'package:flutter_card_swiper/flutter_card_swiper.dart'; import 'package:flutter_card_swiper/flutter_card_swiper.dart';
import 'package:flutter_card_swiper/src/controller/controller_event.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'test_helpers/card_builder.dart'; import 'test_helpers/card_builder.dart';
@ -7,210 +8,92 @@ import 'test_helpers/pump_app.dart';
void main() { void main() {
group('CardSwiperController', () { group('CardSwiperController', () {
test('swipe() should change the state to swipe', () { test('Swipe event adds ControllerSwipeEvent to the stream', () {
final controller = CardSwiperController(); final controller = CardSwiperController();
controller.swipe(); const direction = CardSwiperDirection.right;
expect(controller.state, CardSwiperState.swipe);
});
test('swipeLeft() should change the state to swipeLeft', () { expectLater(
final controller = CardSwiperController(); controller.events,
controller.swipeLeft(); emits(
expect(controller.state, CardSwiperState.swipeLeft); isA<ControllerSwipeEvent>()
}); .having((event) => event.direction, 'direction', direction),
test('swipeRight() should change the state to swipeRight', () {
final controller = CardSwiperController();
controller.swipeRight();
expect(controller.state, CardSwiperState.swipeRight);
});
test('swipeTop() should change the state to swipeTop', () {
final controller = CardSwiperController();
controller.swipeTop();
expect(controller.state, CardSwiperState.swipeTop);
});
test('swipeBottom() should change the state to swipeBottom', () {
final controller = CardSwiperController();
controller.swipeBottom();
expect(controller.state, CardSwiperState.swipeBottom);
});
test('undo() changes state to undo', () {
final controller = CardSwiperController();
controller.undo();
expect(controller.state, CardSwiperState.undo);
});
for (final isDisabled in [false, true]) {
group('isDisabled=$isDisabled', () {
testWidgets('swipe() should swipe the card to the defined direction',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
direction: CardSwiperDirection.top,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
), ),
); );
controller.swipe(); controller.swipe(direction);
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.top);
}); });
testWidgets('swipeLeft() should swipe the card to the left', test('Undo event adds ControllerUndoEvent to the stream', () {
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
direction: CardSwiperDirection.left,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeLeft();
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.left);
});
testWidgets('swipeRight() should swipe the card to the right',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeRight();
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.right);
});
testWidgets('swipeTop() should swipe the card to the top',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
direction: CardSwiperDirection.top,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeTop();
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.top);
});
testWidgets('swipeBottom() should swipe the card to the bottom',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
direction: CardSwiperDirection.bottom,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeBottom();
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.bottom);
});
group('undo()', () {
testWidgets('should undo the last swipe', (tester) async {
final controller = CardSwiperController(); final controller = CardSwiperController();
await tester.pumpApp( expectLater(
CardSwiper( controller.events,
isDisabled: isDisabled, emits(isA<ControllerUndoEvent>()),
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
),
); );
controller.swipe();
await tester.pumpAndSettle();
expect(find.card(1), findsOneWidget);
controller.undo(); controller.undo();
await tester.pumpAndSettle();
expect(find.card(0), findsOneWidget);
}); });
testWidgets('should undo the last swipe left', (tester) async { test('Dispose closes the stream', () {
final controller = CardSwiperController(); final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
expect(controller.events.isBroadcast, isTrue);
controller.dispose();
expect(
() => controller.swipe(CardSwiperDirection.left),
throwsStateError,
);
});
for (final direction in [
CardSwiperDirection.left,
CardSwiperDirection.right,
CardSwiperDirection.top,
CardSwiperDirection.bottom,
]) {
testWidgets('swipe([direction]) should swipe the card to the [direction]',
(tester) async {
final controller = CardSwiperController();
var detectedDirection = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
detectedDirection = swipeDirection;
return true;
},
),
);
controller.swipe(direction);
await tester.pumpAndSettle();
expect(detectedDirection, direction);
});
testWidgets('undo() should undo the last swipe [direction]',
(tester) async {
final controller = CardSwiperController();
var detectedDirection = CardSwiperDirection.none;
await tester.pumpApp( await tester.pumpApp(
CardSwiper( CardSwiper(
isDisabled: isDisabled,
controller: controller, controller: controller,
cardsCount: 10, cardsCount: 10,
cardBuilder: genericBuilder, cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) { onUndo: (_, __, swipeDirection) {
direction = swipeDirection; detectedDirection = swipeDirection;
return true; return true;
}, },
), ),
); );
controller.swipeLeft(); controller.swipe(direction);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.card(1), findsOneWidget); expect(find.card(1), findsOneWidget);
@ -219,103 +102,15 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.card(0), findsOneWidget); expect(find.card(0), findsOneWidget);
expect(direction, CardSwiperDirection.left); expect(detectedDirection, direction);
}); });
}
testWidgets('should undo the last swipe right', (tester) async { testWidgets('should not undo if onUndo returns false', (tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeRight();
await tester.pumpAndSettle();
expect(find.card(1), findsOneWidget);
controller.undo();
await tester.pumpAndSettle();
expect(find.card(0), findsOneWidget);
expect(direction, CardSwiperDirection.right);
});
testWidgets('should undo the last swipe top', (tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeTop();
await tester.pumpAndSettle();
expect(find.card(1), findsOneWidget);
controller.undo();
await tester.pumpAndSettle();
expect(find.card(0), findsOneWidget);
expect(direction, CardSwiperDirection.top);
});
testWidgets('should undo the last swipe bottom', (tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
controller.swipeBottom();
await tester.pumpAndSettle();
expect(find.card(1), findsOneWidget);
controller.undo();
await tester.pumpAndSettle();
expect(find.card(0), findsOneWidget);
expect(direction, CardSwiperDirection.bottom);
});
testWidgets('should not undo if onUndo returns false',
(tester) async {
final controller = CardSwiperController(); final controller = CardSwiperController();
await tester.pumpApp( await tester.pumpApp(
CardSwiper( CardSwiper(
isDisabled: isDisabled,
controller: controller, controller: controller,
cardsCount: 10, cardsCount: 10,
cardBuilder: genericBuilder, cardBuilder: genericBuilder,
@ -325,7 +120,7 @@ void main() {
), ),
); );
controller.swipe(); controller.swipe(CardSwiperDirection.left);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
controller.undo(); controller.undo();
@ -334,7 +129,4 @@ void main() {
expect(find.card(0), findsNothing); expect(find.card(0), findsNothing);
}); });
}); });
});
}
});
} }