From a79449dfe65b873de7a9b257bbd810fec08578f2 Mon Sep 17 00:00:00 2001 From: ricardodalarme Date: Sun, 28 Jan 2024 14:04:19 -0300 Subject: [PATCH] feat: update controller to use stream --- CHANGELOG.md | 4 +- README.md | 9 +- example/lib/main.dart | 10 +- lib/flutter_card_swiper.dart | 2 +- lib/src/card_swiper_controller.dart | 43 -- .../controller/card_swiper_controller.dart | 26 ++ lib/src/controller/controller_event.dart | 15 + lib/src/enums.dart | 9 - lib/src/widget/card_swiper.dart | 13 +- lib/src/widget/card_swiper_state.dart | 29 +- test/card_swiper_controller_test.dart | 408 +++++------------- 11 files changed, 165 insertions(+), 403 deletions(-) delete mode 100644 lib/src/card_swiper_controller.dart create mode 100644 lib/src/controller/card_swiper_controller.dart create mode 100644 lib/src/controller/controller_event.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 48923f5..f5e559e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## [7.0.0] - **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] diff --git a/README.md b/README.md index 2a2216f..f640699 100644 --- a/README.md +++ b/README.md @@ -116,18 +116,15 @@ class Example extends StatelessWidget { | 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 | | 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 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 | | ----------- | :--------------------------------------------- | -| swipe | Swipes the card in the selected 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. | +| swipe | Swipes the card to a specific direction. | | undo | Bring back the last card that was swiped away. |
diff --git a/example/lib/main.dart b/example/lib/main.dart index c8d8f72..81e5dcf 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -66,19 +66,21 @@ class _ExamplePageState extends State { child: const Icon(Icons.rotate_left), ), FloatingActionButton( - onPressed: controller.swipeLeft, + onPressed: () => controller.swipe(CardSwiperDirection.left), child: const Icon(Icons.keyboard_arrow_left), ), FloatingActionButton( - onPressed: controller.swipeRight, + onPressed: () => + controller.swipe(CardSwiperDirection.right), child: const Icon(Icons.keyboard_arrow_right), ), FloatingActionButton( - onPressed: controller.swipeTop, + onPressed: () => controller.swipe(CardSwiperDirection.top), child: const Icon(Icons.keyboard_arrow_up), ), FloatingActionButton( - onPressed: controller.swipeBottom, + onPressed: () => + controller.swipe(CardSwiperDirection.bottom), child: const Icon(Icons.keyboard_arrow_down), ), ], diff --git a/lib/flutter_card_swiper.dart b/lib/flutter_card_swiper.dart index e42879e..57c3f02 100644 --- a/lib/flutter_card_swiper.dart +++ b/lib/flutter_card_swiper.dart @@ -3,7 +3,7 @@ /// animations supporting Android, iOS, Web & Desktop. 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/properties/allowed_swipe_direction.dart'; export 'package:flutter_card_swiper/src/typedefs.dart'; diff --git a/lib/src/card_swiper_controller.dart b/lib/src/card_swiper_controller.dart deleted file mode 100644 index 0f6ae39..0000000 --- a/lib/src/card_swiper_controller.dart +++ /dev/null @@ -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(); - } -} diff --git a/lib/src/controller/card_swiper_controller.dart b/lib/src/controller/card_swiper_controller.dart new file mode 100644 index 0000000..beba297 --- /dev/null +++ b/lib/src/controller/card_swiper_controller.dart @@ -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.broadcast(); + + /// Stream of events that can be used to swipe the card. + Stream 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 dispose() async { + await _eventController.close(); + } +} diff --git a/lib/src/controller/controller_event.dart b/lib/src/controller/controller_event.dart new file mode 100644 index 0000000..f5ad5d6 --- /dev/null +++ b/lib/src/controller/controller_event.dart @@ -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(); +} diff --git a/lib/src/enums.dart b/lib/src/enums.dart index 80c89e0..3430fec 100644 --- a/lib/src/enums.dart +++ b/lib/src/enums.dart @@ -1,12 +1,3 @@ -enum CardSwiperState { - swipe, - swipeLeft, - swipeRight, - swipeTop, - swipeBottom, - undo -} - enum CardSwiperDirection { none, left, right, top, bottom } enum SwipeType { none, swipe, back, undo } diff --git a/lib/src/widget/card_swiper.dart b/lib/src/widget/card_swiper.dart index dac2ab6..5d129ff 100644 --- a/lib/src/widget/card_swiper.dart +++ b/lib/src/widget/card_swiper.dart @@ -3,7 +3,8 @@ 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_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/properties/allowed_swipe_direction.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. 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. /// Defaults to [AllowedSwipeDirection.all] final AllowedSwipeDirection allowedSwipeDirection; @@ -142,7 +138,6 @@ class CardSwiper extends StatefulWidget { this.onTapDisabled, this.onSwipe, this.onEnd, - this.direction = CardSwiperDirection.right, this.onSwipeDirectionChange, this.allowedSwipeDirection = const AllowedSwipeDirection.all(), this.isLoop = true, @@ -158,10 +153,6 @@ class CardSwiper extends StatefulWidget { threshold >= 1 && threshold <= 100, 'threshold must be between 1 and 100', ), - assert( - direction != CardSwiperDirection.none, - 'direction must not be none', - ), assert( scale >= 0 && scale <= 1, 'scale must be between 0 and 1', diff --git a/lib/src/widget/card_swiper_state.dart b/lib/src/widget/card_swiper_state.dart index 64befea..0b67448 100644 --- a/lib/src/widget/card_swiper_state.dart +++ b/lib/src/widget/card_swiper_state.dart @@ -26,7 +26,7 @@ class _CardSwiperState extends State _undoableIndex.state = widget.initialIndex; - widget.controller?.addListener(_controllerListener); + widget.controller?.events.listen(_controllerListener); _animationController = AnimationController( duration: widget.duration, @@ -65,7 +65,7 @@ class _CardSwiperState extends State @override void dispose() { _animationController.dispose(); - widget.controller?.removeListener(_controllerListener); + widget.controller?.dispose(); super.dispose(); } @@ -157,23 +157,12 @@ class _CardSwiperState extends State ); } - void _controllerListener() { - switch (widget.controller?.state) { - case CardSwiperState.swipe: - return _swipe(widget.direction); - case CardSwiperState.swipeLeft: - 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 _controllerListener(ControllerEvent event) { + return switch (event) { + ControllerSwipeEvent(:final direction) => _swipe(direction), + ControllerUndoEvent() => _undo(), + _ => null + }; } void _animationListener() { @@ -225,7 +214,7 @@ class _CardSwiperState extends State void _onEndAnimation() { final direction = _getEndAnimationDirection(); - final isValidDirection = this._isValidDirection(direction); + final isValidDirection = _isValidDirection(direction); if (isValidDirection) { _swipe(direction); diff --git a/test/card_swiper_controller_test.dart b/test/card_swiper_controller_test.dart index f7109cb..958876a 100644 --- a/test/card_swiper_controller_test.dart +++ b/test/card_swiper_controller_test.dart @@ -1,4 +1,5 @@ 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 'test_helpers/card_builder.dart'; @@ -7,334 +8,125 @@ import 'test_helpers/pump_app.dart'; void main() { group('CardSwiperController', () { - test('swipe() should change the state to swipe', () { + test('Swipe event adds ControllerSwipeEvent to the stream', () { final controller = CardSwiperController(); - controller.swipe(); - expect(controller.state, CardSwiperState.swipe); - }); + const direction = CardSwiperDirection.right; - test('swipeLeft() should change the state to swipeLeft', () { - final controller = CardSwiperController(); - controller.swipeLeft(); - expect(controller.state, CardSwiperState.swipeLeft); - }); + expectLater( + controller.events, + emits( + isA() + .having((event) => event.direction, 'direction', direction), + ), + ); - test('swipeRight() should change the state to swipeRight', () { - final controller = CardSwiperController(); - controller.swipeRight(); - expect(controller.state, CardSwiperState.swipeRight); + controller.swipe(direction); }); - test('swipeTop() should change the state to swipeTop', () { + test('Undo event adds ControllerUndoEvent to the stream', () { 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); - }); + expectLater( + controller.events, + emits(isA()), + ); - 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(); - await tester.pumpAndSettle(); - - expect(direction, CardSwiperDirection.top); - }); - - testWidgets('swipeLeft() should swipe the card to the left', - (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(); - - await tester.pumpApp( - CardSwiper( - isDisabled: isDisabled, - controller: controller, - cardsCount: 10, - cardBuilder: genericBuilder, - ), - ); - - controller.swipe(); - await tester.pumpAndSettle(); - - expect(find.card(1), findsOneWidget); - - controller.undo(); - await tester.pumpAndSettle(); - - expect(find.card(0), findsOneWidget); - }); - - testWidgets('should undo the last swipe left', (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.swipeLeft(); - await tester.pumpAndSettle(); - - expect(find.card(1), findsOneWidget); - - controller.undo(); - await tester.pumpAndSettle(); - - expect(find.card(0), findsOneWidget); - expect(direction, CardSwiperDirection.left); - }); - - testWidgets('should undo the last swipe right', (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; + test('Dispose closes the stream', () { + final controller = CardSwiperController(); - await tester.pumpApp( - CardSwiper( - isDisabled: isDisabled, - controller: controller, - cardsCount: 10, - cardBuilder: genericBuilder, - onUndo: (_, __, swipeDirection) { - direction = swipeDirection; - return true; - }, - ), - ); + expect(controller.events.isBroadcast, isTrue); - controller.swipeBottom(); - await tester.pumpAndSettle(); + controller.dispose(); - expect(find.card(1), findsOneWidget); + expect( + () => controller.swipe(CardSwiperDirection.left), + throwsStateError, + ); + }); - controller.undo(); - await tester.pumpAndSettle(); + 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); + }); - expect(find.card(0), findsOneWidget); - expect(direction, CardSwiperDirection.bottom); - }); + testWidgets('undo() should undo the last swipe [direction]', + (tester) async { + final controller = CardSwiperController(); + var detectedDirection = CardSwiperDirection.none; + + await tester.pumpApp( + CardSwiper( + controller: controller, + cardsCount: 10, + cardBuilder: genericBuilder, + onUndo: (_, __, swipeDirection) { + detectedDirection = swipeDirection; + return true; + }, + ), + ); + + controller.swipe(direction); + await tester.pumpAndSettle(); + + expect(find.card(1), findsOneWidget); + + controller.undo(); + await tester.pumpAndSettle(); + + expect(find.card(0), findsOneWidget); + expect(detectedDirection, direction); + }); + } - testWidgets('should not undo if onUndo returns false', - (tester) async { - final controller = CardSwiperController(); + testWidgets('should not undo if onUndo returns false', (tester) async { + final controller = CardSwiperController(); - await tester.pumpApp( - CardSwiper( - isDisabled: isDisabled, - controller: controller, - cardsCount: 10, - cardBuilder: genericBuilder, - onUndo: (_, __, swipeDirection) { - return false; - }, - ), - ); + await tester.pumpApp( + CardSwiper( + controller: controller, + cardsCount: 10, + cardBuilder: genericBuilder, + onUndo: (_, __, swipeDirection) { + return false; + }, + ), + ); - controller.swipe(); - await tester.pumpAndSettle(); + controller.swipe(CardSwiperDirection.left); + await tester.pumpAndSettle(); - controller.undo(); - await tester.pumpAndSettle(); + controller.undo(); + await tester.pumpAndSettle(); - expect(find.card(0), findsNothing); - }); - }); - }); - } + expect(find.card(0), findsNothing); + }); }); }