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

@ -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]

View File

@ -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. |
<hr/>

View File

@ -66,19 +66,21 @@ class _ExamplePageState extends State<Example> {
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),
),
],

View File

@ -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';

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 SwipeType { none, swipe, back, undo }

View File

@ -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',

View File

@ -26,7 +26,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
_undoableIndex.state = widget.initialIndex;
widget.controller?.addListener(_controllerListener);
widget.controller?.events.listen(_controllerListener);
_animationController = AnimationController(
duration: widget.duration,
@ -65,7 +65,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
@override
void dispose() {
_animationController.dispose();
widget.controller?.removeListener(_controllerListener);
widget.controller?.dispose();
super.dispose();
}
@ -157,23 +157,12 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
);
}
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<T extends Widget> extends State<CardSwiper>
void _onEndAnimation() {
final direction = _getEndAnimationDirection();
final isValidDirection = this._isValidDirection(direction);
final isValidDirection = _isValidDirection(direction);
if (isValidDirection) {
_swipe(direction);

View File

@ -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;
expectLater(
controller.events,
emits(
isA<ControllerSwipeEvent>()
.having((event) => event.direction, 'direction', direction),
),
);
controller.swipe(direction);
});
test('swipeLeft() should change the state to swipeLeft', () {
test('Undo event adds ControllerUndoEvent to the stream', () {
final controller = CardSwiperController();
controller.swipeLeft();
expect(controller.state, CardSwiperState.swipeLeft);
});
test('swipeRight() should change the state to swipeRight', () {
final controller = CardSwiperController();
controller.swipeRight();
expect(controller.state, CardSwiperState.swipeRight);
});
expectLater(
controller.events,
emits(isA<ControllerUndoEvent>()),
);
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;
test('Dispose closes the stream', () {
final controller = CardSwiperController();
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
direction: CardSwiperDirection.top,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
expect(controller.events.isBroadcast, isTrue);
controller.swipe();
await tester.pumpAndSettle();
controller.dispose();
expect(direction, CardSwiperDirection.top);
});
expect(
() => controller.swipe(CardSwiperDirection.left),
throwsStateError,
);
});
testWidgets('swipeLeft() should swipe the card to the left',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
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(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
direction: CardSwiperDirection.left,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
await tester.pumpApp(
CardSwiper(
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
detectedDirection = swipeDirection;
return true;
},
),
);
controller.swipeLeft();
await tester.pumpAndSettle();
controller.swipe(direction);
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.left);
});
expect(detectedDirection, direction);
});
testWidgets('swipeRight() should swipe the card to the right',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
testWidgets('undo() should undo the last swipe [direction]',
(tester) async {
final controller = CardSwiperController();
var detectedDirection = CardSwiperDirection.none;
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onSwipe: (oldIndex, currentIndex, swipeDirection) {
direction = swipeDirection;
return true;
},
),
);
await tester.pumpApp(
CardSwiper(
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) {
detectedDirection = swipeDirection;
return true;
},
),
);
controller.swipeRight();
await tester.pumpAndSettle();
controller.swipe(direction);
await tester.pumpAndSettle();
expect(direction, CardSwiperDirection.right);
});
expect(find.card(1), findsOneWidget);
testWidgets('swipeTop() should swipe the card to the top',
(tester) async {
final controller = CardSwiperController();
var direction = CardSwiperDirection.none;
controller.undo();
await tester.pumpAndSettle();
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;
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();
await tester.pumpApp(
CardSwiper(
isDisabled: isDisabled,
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) {
return false;
},
),
);
controller.swipe();
await tester.pumpAndSettle();
controller.undo();
await tester.pumpAndSettle();
expect(find.card(0), findsNothing);
});
});
expect(find.card(0), findsOneWidget);
expect(detectedDirection, direction);
});
}
testWidgets('should not undo if onUndo returns false', (tester) async {
final controller = CardSwiperController();
await tester.pumpApp(
CardSwiper(
controller: controller,
cardsCount: 10,
cardBuilder: genericBuilder,
onUndo: (_, __, swipeDirection) {
return false;
},
),
);
controller.swipe(CardSwiperDirection.left);
await tester.pumpAndSettle();
controller.undo();
await tester.pumpAndSettle();
expect(find.card(0), findsNothing);
});
});
}