From b8e15e0e5b5a2b0f3fcb5f4c4d7fafe881ac0e61 Mon Sep 17 00:00:00 2001 From: ricardodalarme Date: Tue, 9 Jan 2024 21:16:25 -0300 Subject: [PATCH] refactor: improve code organization --- lib/flutter_card_swiper.dart | 5 +- .../allowed_swipe_direction.dart | 0 lib/src/typedefs.dart | 2 +- .../direction_extension.dart} | 8 +- lib/src/utils/number_extension.dart | 5 + lib/src/{ => utils}/undoable.dart | 0 lib/src/widget/card_swiper.dart | 181 ++++++++++++++++++ .../card_swiper_state.dart} | 180 +---------------- .../allowed_swipe_direction_test.dart} | 22 ++- test/test_helpers/gestures.dart | 2 +- test/utils/direction_extension_test.dart | 32 ++++ .../number_extension_test.dart} | 30 +-- test/{ => utils}/undoable_test.dart | 2 +- 13 files changed, 239 insertions(+), 230 deletions(-) rename lib/src/{ => properties}/allowed_swipe_direction.dart (100%) rename lib/src/{extensions.dart => utils/direction_extension.dart} (71%) create mode 100644 lib/src/utils/number_extension.dart rename lib/src/{ => utils}/undoable.dart (100%) create mode 100644 lib/src/widget/card_swiper.dart rename lib/src/{card_swiper.dart => widget/card_swiper_state.dart} (58%) rename test/{card_swipe_direction_test.dart => properties/allowed_swipe_direction_test.dart} (72%) create mode 100644 test/utils/direction_extension_test.dart rename test/{extensions_test.dart => utils/number_extension_test.dart} (50%) rename test/{ => utils}/undoable_test.dart (95%) diff --git a/lib/flutter_card_swiper.dart b/lib/flutter_card_swiper.dart index 4795a85..e42879e 100644 --- a/lib/flutter_card_swiper.dart +++ b/lib/flutter_card_swiper.dart @@ -3,7 +3,8 @@ /// animations supporting Android, iOS, Web & Desktop. library flutter_card_swiper; -export 'package:flutter_card_swiper/src/allowed_swipe_direction.dart'; -export 'package:flutter_card_swiper/src/card_swiper.dart'; export 'package:flutter_card_swiper/src/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'; +export 'package:flutter_card_swiper/src/widget/card_swiper.dart'; diff --git a/lib/src/allowed_swipe_direction.dart b/lib/src/properties/allowed_swipe_direction.dart similarity index 100% rename from lib/src/allowed_swipe_direction.dart rename to lib/src/properties/allowed_swipe_direction.dart diff --git a/lib/src/typedefs.dart b/lib/src/typedefs.dart index c6582d1..d19549a 100644 --- a/lib/src/typedefs.dart +++ b/lib/src/typedefs.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_card_swiper/src/enums.dart'; typedef CardSwiperOnSwipe = FutureOr Function( diff --git a/lib/src/extensions.dart b/lib/src/utils/direction_extension.dart similarity index 71% rename from lib/src/extensions.dart rename to lib/src/utils/direction_extension.dart index 4d14410..6ffad3b 100644 --- a/lib/src/extensions.dart +++ b/lib/src/utils/direction_extension.dart @@ -1,13 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_card_swiper/src/enums.dart'; -extension Range on num { - bool isBetween(num from, num to) { - return from <= this && this <= to; - } -} - -extension CardSwiperDirectionExtension on CardSwiperDirection { +extension DirectionExtension on CardSwiperDirection { Axis get axis { switch (this) { case CardSwiperDirection.left: diff --git a/lib/src/utils/number_extension.dart b/lib/src/utils/number_extension.dart new file mode 100644 index 0000000..0777ef5 --- /dev/null +++ b/lib/src/utils/number_extension.dart @@ -0,0 +1,5 @@ +extension NumberExtension on num { + bool isBetween(num from, num to) { + return from <= this && this <= to; + } +} diff --git a/lib/src/undoable.dart b/lib/src/utils/undoable.dart similarity index 100% rename from lib/src/undoable.dart rename to lib/src/utils/undoable.dart diff --git a/lib/src/widget/card_swiper.dart b/lib/src/widget/card_swiper.dart new file mode 100644 index 0000000..7fa7317 --- /dev/null +++ b/lib/src/widget/card_swiper.dart @@ -0,0 +1,181 @@ +import 'dart:collection'; +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/enums.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/utils/number_extension.dart'; +import 'package:flutter_card_swiper/src/utils/undoable.dart'; + +part 'card_swiper_state.dart'; + +class CardSwiper extends StatefulWidget { + /// Function that builds each card in the stack. + /// + /// The function is called with the index of the card to be built, the build context, the ratio + /// of vertical drag to [threshold] as a percentage, and the ratio of horizontal drag to [threshold] + /// as a percentage. The function should return a widget that represents the card at the given index. + /// It can return `null`, which will result in an empty card being displayed. + final NullableCardBuilder cardBuilder; + + /// The number of cards in the stack. + /// + /// The [cardsCount] parameter specifies the number of cards that will be displayed in the stack. + /// + /// This parameter is required and must be greater than 0. + final int cardsCount; + + /// The index of the card to display initially. + /// + /// Defaults to 0, meaning the first card in the stack is displayed initially. + final int initialIndex; + + /// The [CardSwiperController] used to control the swiper externally. + /// + /// If `null`, the swiper can only be controlled by user input. + final CardSwiperController? controller; + + /// The duration of each swipe animation. + /// + /// Defaults to 200 milliseconds. + final Duration duration; + + /// The padding around the swiper. + /// + /// Defaults to `EdgeInsets.symmetric(horizontal: 20, vertical: 25)`. + final EdgeInsetsGeometry padding; + + /// The maximum angle the card reaches while swiping. + /// + /// Must be between 0 and 360 degrees. Defaults to 30 degrees. + final double maxAngle; + + /// The threshold from which the card is swiped away. + /// + /// Must be between 1 and 100 percent of the card width. Defaults to 50 percent. + final int threshold; + + /// The scale of the card that is behind the front card. + /// + /// The [scale] and [backCardOffset] both impact the positions of the back cards. + /// In order to keep the back card position same after changing the [scale], + /// the [backCardOffset] should also be adjusted. + /// * As a rough rule of thumb, 0.1 change in [scale] effects an + /// [backCardOffset] of ~35px. + /// + /// Must be between 0 and 1. Defaults to 0.9. + final double scale; + + /// Whether swiping is disabled. + /// + /// If `true`, swiping is disabled, except when triggered by the [controller]. + /// + /// Defaults to `false`. + final bool isDisabled; + + /// Callback function that is called when a swipe action is performed. + /// + /// The function is called with the oldIndex, the currentIndex and the direction of the swipe. + /// If the function returns `false`, the swipe action is canceled and the current card remains + /// on top of the stack. If the function returns `true`, the swipe action is performed as expected. + final CardSwiperOnSwipe? onSwipe; + + /// Callback function that is called when there are no more cards to swipe. + final CardSwiperOnEnd? onEnd; + + /// 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; + + /// A boolean value that determines whether the card stack should loop. When the last card is swiped, + /// if isLoop is true, the first card will become the last card again. The default value is true. + final bool isLoop; + + /// An integer that determines the number of cards that are displayed at the same time. + /// The default value is 2. Note that you must display at least one card, and no more than the [cardsCount] parameter. + 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; + + /// Callback function that is called when a card swipe direction changes. + /// + /// The function is called with the last detected horizontal direction and the last detected vertical direction + final CardSwiperDirectionChange? onSwipeDirectionChange; + + /// The offset of the back card from the front card. + /// + /// In order to keep the back card position same after changing the [backCardOffset], + /// the [scale] should also be adjusted. + /// * As a rough rule of thumb, 35px change in [backCardOffset] effects a + /// [scale] change of 0.1. + /// + /// Must be a positive value. Defaults to Offset(0, 40). + final Offset backCardOffset; + + const CardSwiper({ + required this.cardBuilder, + required this.cardsCount, + Key? key, + this.controller, + this.initialIndex = 0, + this.padding = const EdgeInsets.symmetric(horizontal: 20, vertical: 25), + this.duration = const Duration(milliseconds: 200), + this.maxAngle = 30, + this.threshold = 50, + this.scale = 0.9, + this.isDisabled = false, + this.onTapDisabled, + this.onSwipe, + this.onEnd, + this.direction = CardSwiperDirection.right, + this.onSwipeDirectionChange, + this.allowedSwipeDirection = const AllowedSwipeDirection.all(), + this.isLoop = true, + this.numberOfCardsDisplayed = 2, + this.onUndo, + this.backCardOffset = const Offset(0, 40), + }) : assert( + maxAngle >= 0 && maxAngle <= 360, + 'maxAngle must be between 0 and 360', + ), + assert( + 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', + ), + assert( + numberOfCardsDisplayed >= 1 && numberOfCardsDisplayed <= cardsCount, + 'you must display at least one card, and no more than [cardsCount]', + ), + assert( + initialIndex >= 0 && initialIndex < cardsCount, + 'initialIndex must be between 0 and [cardsCount]', + ), + super(key: key); + + @override + State createState() => _CardSwiperState(); +} diff --git a/lib/src/card_swiper.dart b/lib/src/widget/card_swiper_state.dart similarity index 58% rename from lib/src/card_swiper.dart rename to lib/src/widget/card_swiper_state.dart index 2dea8fc..b80b08c 100644 --- a/lib/src/card_swiper.dart +++ b/lib/src/widget/card_swiper_state.dart @@ -1,182 +1,4 @@ -import 'dart:collection'; -import 'dart:math' as math; - -import 'package:flutter/widgets.dart'; -import 'package:flutter_card_swiper/src/allowed_swipe_direction.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/enums.dart'; -import 'package:flutter_card_swiper/src/extensions.dart'; -import 'package:flutter_card_swiper/src/typedefs.dart'; -import 'package:flutter_card_swiper/src/undoable.dart'; - -class CardSwiper extends StatefulWidget { - /// Function that builds each card in the stack. - /// - /// The function is called with the index of the card to be built, the build context, the ratio - /// of vertical drag to [threshold] as a percentage, and the ratio of horizontal drag to [threshold] - /// as a percentage. The function should return a widget that represents the card at the given index. - /// It can return `null`, which will result in an empty card being displayed. - final NullableCardBuilder cardBuilder; - - /// The number of cards in the stack. - /// - /// The [cardsCount] parameter specifies the number of cards that will be displayed in the stack. - /// - /// This parameter is required and must be greater than 0. - final int cardsCount; - - /// The index of the card to display initially. - /// - /// Defaults to 0, meaning the first card in the stack is displayed initially. - final int initialIndex; - - /// The [CardSwiperController] used to control the swiper externally. - /// - /// If `null`, the swiper can only be controlled by user input. - final CardSwiperController? controller; - - /// The duration of each swipe animation. - /// - /// Defaults to 200 milliseconds. - final Duration duration; - - /// The padding around the swiper. - /// - /// Defaults to `EdgeInsets.symmetric(horizontal: 20, vertical: 25)`. - final EdgeInsetsGeometry padding; - - /// The maximum angle the card reaches while swiping. - /// - /// Must be between 0 and 360 degrees. Defaults to 30 degrees. - final double maxAngle; - - /// The threshold from which the card is swiped away. - /// - /// Must be between 1 and 100 percent of the card width. Defaults to 50 percent. - final int threshold; - - /// The scale of the card that is behind the front card. - /// - /// The [scale] and [backCardOffset] both impact the positions of the back cards. - /// In order to keep the back card position same after changing the [scale], - /// the [backCardOffset] should also be adjusted. - /// * As a rough rule of thumb, 0.1 change in [scale] effects an - /// [backCardOffset] of ~35px. - /// - /// Must be between 0 and 1. Defaults to 0.9. - final double scale; - - /// Whether swiping is disabled. - /// - /// If `true`, swiping is disabled, except when triggered by the [controller]. - /// - /// Defaults to `false`. - final bool isDisabled; - - /// Callback function that is called when a swipe action is performed. - /// - /// The function is called with the oldIndex, the currentIndex and the direction of the swipe. - /// If the function returns `false`, the swipe action is canceled and the current card remains - /// on top of the stack. If the function returns `true`, the swipe action is performed as expected. - final CardSwiperOnSwipe? onSwipe; - - /// Callback function that is called when there are no more cards to swipe. - final CardSwiperOnEnd? onEnd; - - /// 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; - - /// A boolean value that determines whether the card stack should loop. When the last card is swiped, - /// if isLoop is true, the first card will become the last card again. The default value is true. - final bool isLoop; - - /// An integer that determines the number of cards that are displayed at the same time. - /// The default value is 2. Note that you must display at least one card, and no more than the [cardsCount] parameter. - 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; - - /// Callback function that is called when a card swipe direction changes. - /// - /// The function is called with the last detected horizontal direction and the last detected vertical direction - final CardSwiperDirectionChange? onSwipeDirectionChange; - - /// The offset of the back card from the front card. - /// - /// In order to keep the back card position same after changing the [backCardOffset], - /// the [scale] should also be adjusted. - /// * As a rough rule of thumb, 35px change in [backCardOffset] effects a - /// [scale] change of 0.1. - /// - /// Must be a positive value. Defaults to Offset(0, 40). - final Offset backCardOffset; - - const CardSwiper({ - required this.cardBuilder, - required this.cardsCount, - Key? key, - this.controller, - this.initialIndex = 0, - this.padding = const EdgeInsets.symmetric(horizontal: 20, vertical: 25), - this.duration = const Duration(milliseconds: 200), - this.maxAngle = 30, - this.threshold = 50, - this.scale = 0.9, - this.isDisabled = false, - this.onTapDisabled, - this.onSwipe, - this.onEnd, - this.direction = CardSwiperDirection.right, - this.onSwipeDirectionChange, - this.allowedSwipeDirection = const AllowedSwipeDirection.all(), - this.isLoop = true, - this.numberOfCardsDisplayed = 2, - this.onUndo, - this.backCardOffset = const Offset(0, 40), - }) : assert( - maxAngle >= 0 && maxAngle <= 360, - 'maxAngle must be between 0 and 360', - ), - assert( - 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', - ), - assert( - numberOfCardsDisplayed >= 1 && numberOfCardsDisplayed <= cardsCount, - 'you must display at least one card, and no more than [cardsCount]', - ), - assert( - initialIndex >= 0 && initialIndex < cardsCount, - 'initialIndex must be between 0 and [cardsCount]', - ), - super(key: key); - - @override - State createState() => _CardSwiperState(); -} +part of 'card_swiper.dart'; class _CardSwiperState extends State with SingleTickerProviderStateMixin { diff --git a/test/card_swipe_direction_test.dart b/test/properties/allowed_swipe_direction_test.dart similarity index 72% rename from test/card_swipe_direction_test.dart rename to test/properties/allowed_swipe_direction_test.dart index 2e6cddf..b824978 100644 --- a/test/card_swipe_direction_test.dart +++ b/test/properties/allowed_swipe_direction_test.dart @@ -2,7 +2,7 @@ import 'package:flutter_card_swiper/flutter_card_swiper.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - test('CardSwipeDirection.all() has all directions as true', () { + test('AllowedSwipeDirection.all() has all directions as true', () { const directions = AllowedSwipeDirection.all(); expect(directions.up, true); expect(directions.down, true); @@ -10,7 +10,7 @@ void main() { expect(directions.left, true); }); - test('CardSwipeDirection.none() has all directions as false', () { + test('AllowedSwipeDirection.none() has all directions as false', () { const directions = AllowedSwipeDirection.none(); expect(directions.up, false); expect(directions.down, false); @@ -18,8 +18,9 @@ void main() { expect(directions.left, false); }); - group('CardSwipeDirection.only() tests', () { - test('CardSwipeDirection.only(up:true) has only the up direction as true', + group('AllowedSwipeDirection.only() tests', () { + test( + 'AllowedSwipeDirection.only(up:true) has only the up direction as true', () { const directions = AllowedSwipeDirection.only(up: true); expect(directions.up, true); @@ -29,7 +30,7 @@ void main() { }); test( - 'CardSwipeDirection.only(down:true) has only the set direction as true', + 'AllowedSwipeDirection.only(down:true) has only the set direction as true', () { const directions = AllowedSwipeDirection.only(down: true); expect(directions.up, false); @@ -39,7 +40,7 @@ void main() { }); test( - 'CardSwipeDirection.only(right:true) has only the set direction as true', + 'AllowedSwipeDirection.only(right:true) has only the set direction as true', () { const directions = AllowedSwipeDirection.only(right: true); expect(directions.up, false); @@ -49,7 +50,7 @@ void main() { }); test( - 'CardSwipeDirection.only(left:true) has only the set direction as true', + 'AllowedSwipeDirection.only(left:true) has only the set direction as true', () { const directions = AllowedSwipeDirection.only(left: true); expect(directions.up, false); @@ -59,9 +60,9 @@ void main() { }); }); - group('CardSwipeDirection.symmetric() tests', () { + group('AllowedSwipeDirection.symmetric() tests', () { test( - 'CardSwipeDirection.symmetric(horizontal:true) has left and right as true', + 'AllowedSwipeDirection.symmetric(horizontal:true) has left and right as true', () { const directions = AllowedSwipeDirection.symmetric(horizontal: true); expect(directions.up, false); @@ -70,7 +71,8 @@ void main() { expect(directions.left, true); }); - test('CardSwipeDirection.symmetric(vertical:true) has up and down as true', + test( + 'AllowedSwipeDirection.symmetric(vertical:true) has up and down as true', () { const directions = AllowedSwipeDirection.symmetric(vertical: true); expect(directions.up, true); diff --git a/test/test_helpers/gestures.dart b/test/test_helpers/gestures.dart index dfe5ec0..288184a 100644 --- a/test/test_helpers/gestures.dart +++ b/test/test_helpers/gestures.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; extension Gestures on WidgetTester { diff --git a/test/utils/direction_extension_test.dart b/test/utils/direction_extension_test.dart new file mode 100644 index 0000000..1a0746a --- /dev/null +++ b/test/utils/direction_extension_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_card_swiper/src/enums.dart'; +import 'package:flutter_card_swiper/src/utils/direction_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CardSwiperDirection.axis', () { + test('when direction is left expect to return horizontal ', () { + final axis = CardSwiperDirection.left.axis; + expect(axis, Axis.horizontal); + }); + + test('when direction is right expect to return horizontal', () { + final axis = CardSwiperDirection.right.axis; + expect(axis, Axis.horizontal); + }); + + test('when direction is top expect to return vertical', () { + final axis = CardSwiperDirection.top.axis; + expect(axis, Axis.vertical); + }); + + test('when direction is bottom expect to return vertical ', () { + final axis = CardSwiperDirection.bottom.axis; + expect(axis, Axis.vertical); + }); + + test('when direction is none expect to throw exception ', () { + expect(() => CardSwiperDirection.none.axis, throwsException); + }); + }); +} diff --git a/test/extensions_test.dart b/test/utils/number_extension_test.dart similarity index 50% rename from test/extensions_test.dart rename to test/utils/number_extension_test.dart index 00ea268..cc2c48d 100644 --- a/test/extensions_test.dart +++ b/test/utils/number_extension_test.dart @@ -1,6 +1,4 @@ -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/utils/number_extension.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -45,30 +43,4 @@ void main() { expect(result, isFalse); }); }); - - group('CardSwiperDirection.axis', () { - test('when direction is left expect to return horizontal ', () { - final axis = CardSwiperDirection.left.axis; - expect(axis, Axis.horizontal); - }); - - test('when direction is right expect to return horizontal', () { - final axis = CardSwiperDirection.right.axis; - expect(axis, Axis.horizontal); - }); - - test('when direction is top expect to return vertical', () { - final axis = CardSwiperDirection.top.axis; - expect(axis, Axis.vertical); - }); - - test('when direction is bottom expect to return vertical ', () { - final axis = CardSwiperDirection.bottom.axis; - expect(axis, Axis.vertical); - }); - - test('when direction is none expect to throw exception ', () { - expect(() => CardSwiperDirection.none.axis, throwsException); - }); - }); } diff --git a/test/undoable_test.dart b/test/utils/undoable_test.dart similarity index 95% rename from test/undoable_test.dart rename to test/utils/undoable_test.dart index f8e6d46..ad2e55c 100644 --- a/test/undoable_test.dart +++ b/test/utils/undoable_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter_card_swiper/src/undoable.dart'; +import 'package:flutter_card_swiper/src/utils/undoable.dart'; import 'package:flutter_test/flutter_test.dart'; void main() {