diff --git a/CHANGELOG.md b/CHANGELOG.md index cff2d9d..58ce65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ -## NEXT +## [6.0.0] + +- Adds `onSwipeDirectionChange` callback containing the horizontal and vertical swipe direction +- **BREAKING CHANGE**: + - Modifies the `cardBuilder` callback, to include the ratio of horizontal drag to threshold as a percentage + and the ratio of vertical drag to threshold as a percentage. - **BREAKING CHANGE**: - `isHorizontalSwipingEnabled` and `isVerticalSwipingEnabled` have been removed. Use `allowedSwipeDirection` instead. diff --git a/README.md b/README.md index d36297f..2a2216f 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ class Example extends StatelessWidget { body: Flexible( child: CardSwiper( cardsCount: cards.length, - cardBuilder: (context, index) => cards[index], + cardBuilder: (context, index, percentThresholdX, percentThresholdY) => cards[index], ), ), ); @@ -97,7 +97,7 @@ class Example extends StatelessWidget { #### Basic | Parameter | Default | Description | Required | -|-------------------------------------------| :------------------------------------------------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------| :------: | +|-------------------------------------------|:---------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------| :------: | | cardBuilder | - | Widget builder for rendering cards | true | | cardsCount | - | Number of cards | true | | controller | - | Controller to trigger swipe actions | false | @@ -116,7 +116,7 @@ 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 | #### 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. diff --git a/example/lib/main.dart b/example/lib/main.dart index dc6164c..5e22383 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -47,7 +47,13 @@ class _ExamplePageState extends State { numberOfCardsDisplayed: 3, backCardOffset: const Offset(40, 40), padding: const EdgeInsets.all(24.0), - cardBuilder: (context, index) => cards[index], + cardBuilder: ( + context, + index, + horizontalThresholdPercentage, + verticalThresholdPercentage, + ) => + cards[index], ), ), Padding( diff --git a/lib/src/card_animation.dart b/lib/src/card_animation.dart index c1da3f0..183efc7 100644 --- a/lib/src/card_animation.dart +++ b/lib/src/card_animation.dart @@ -13,6 +13,7 @@ class CardAnimation { this.isHorizontalSwipingEnabled = true, this.isVerticalSwipingEnabled = true, this.allowedSwipeDirection = const AllowedSwipeDirection.all(), + this.onSwipeDirectionChanged, }) : scale = initialScale; final double maxAngle; @@ -22,6 +23,7 @@ class CardAnimation { final bool isHorizontalSwipingEnabled; final bool isVerticalSwipingEnabled; final AllowedSwipeDirection allowedSwipeDirection; + final ValueChanged? onSwipeDirectionChanged; double left = 0; double top = 0; @@ -56,19 +58,41 @@ class CardAnimation { void update(double dx, double dy, bool inverseAngle) { if (allowedSwipeDirection.right && allowedSwipeDirection.left) { + if (left > 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.right); + } else if (left < 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.left); + } left += dx; } else if (allowedSwipeDirection.right) { - if (left >= 0) left += dx; + if (left >= 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.right); + left += dx; + } } else if (allowedSwipeDirection.left) { - if (left <= 0) left += dx; + if (left <= 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.left); + left += dx; + } } if (allowedSwipeDirection.up && allowedSwipeDirection.down) { + if (top > 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.bottom); + } else if (top < 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.top); + } top += dy; } else if (allowedSwipeDirection.up) { - if (top <= 0) top += dy; + if (top <= 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.top); + top += dy; + } } else if (allowedSwipeDirection.down) { - if (top >= 0) top += dy; + if (top >= 0) { + onSwipeDirectionChanged?.call(CardSwiperDirection.bottom); + top += dy; + } } total = left + top; diff --git a/lib/src/card_swiper.dart b/lib/src/card_swiper.dart index 19d15d9..3f3b2e4 100644 --- a/lib/src/card_swiper.dart +++ b/lib/src/card_swiper.dart @@ -13,11 +13,11 @@ import 'package:flutter_card_swiper/src/undoable.dart'; class CardSwiper extends StatefulWidget { /// Function that builds each card in the stack. /// - /// The [int] parameter specifies the index of the card to build, and the [BuildContext] - /// parameter provides the build context. 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 NullableIndexedWidgetBuilder cardBuilder; + /// 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. /// @@ -111,6 +111,11 @@ class CardSwiper extends StatefulWidget { /// 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], @@ -137,6 +142,7 @@ class CardSwiper extends StatefulWidget { this.onSwipe, this.onEnd, this.direction = CardSwiperDirection.right, + this.onSwipeDirectionChange, this.allowedSwipeDirection = const AllowedSwipeDirection.all(), this.isLoop = true, this.numberOfCardsDisplayed = 2, @@ -179,6 +185,8 @@ class _CardSwiperState extends State SwipeType _swipeType = SwipeType.none; CardSwiperDirection _detectedDirection = CardSwiperDirection.none; + CardSwiperDirection _detectedHorizontalDirection = CardSwiperDirection.none; + CardSwiperDirection _detectedVerticalDirection = CardSwiperDirection.none; bool _tappedOnTop = false; final _undoableIndex = Undoable(null); @@ -211,9 +219,33 @@ class _CardSwiperState extends State initialScale: widget.scale, allowedSwipeDirection: widget.allowedSwipeDirection, initialOffset: widget.backCardOffset, + onSwipeDirectionChanged: onSwipeDirectionChanged, ); } + void onSwipeDirectionChanged(CardSwiperDirection direction) { + if (direction == CardSwiperDirection.none) { + _detectedVerticalDirection = direction; + _detectedHorizontalDirection = direction; + widget.onSwipeDirectionChange + ?.call(_detectedHorizontalDirection, _detectedVerticalDirection); + } else if (direction == CardSwiperDirection.right || + direction == CardSwiperDirection.left) { + if (_detectedHorizontalDirection != direction) { + _detectedHorizontalDirection = direction; + widget.onSwipeDirectionChange + ?.call(_detectedHorizontalDirection, _detectedVerticalDirection); + } + } else if (direction == CardSwiperDirection.top || + direction == CardSwiperDirection.bottom) { + if (_detectedVerticalDirection != direction) { + _detectedVerticalDirection = direction; + widget.onSwipeDirectionChange + ?.call(_detectedHorizontalDirection, _detectedVerticalDirection); + } + } + } + @override void dispose() { super.dispose(); @@ -234,7 +266,6 @@ class _CardSwiperState extends State fit: StackFit.expand, children: List.generate(numberOfCardsOnScreen(), (index) { if (index == 0) return _frontItem(constraints); - return _backItem(constraints, index); }).reversed.toList(), ); @@ -254,7 +285,12 @@ class _CardSwiperState extends State angle: _cardAnimation.angle, child: ConstrainedBox( constraints: constraints, - child: widget.cardBuilder(context, _currentIndex!), + child: widget.cardBuilder( + context, + _currentIndex!, + (100 * _cardAnimation.left / widget.threshold).ceil(), + (100 * _cardAnimation.top / widget.threshold).ceil(), + ), ), ), onTap: () async { @@ -299,7 +335,7 @@ class _CardSwiperState extends State scale: _cardAnimation.scale - ((1 - widget.scale) * (index - 1)), child: ConstrainedBox( constraints: constraints, - child: widget.cardBuilder(context, getValidIndexOffset(index)!), + child: widget.cardBuilder(context, getValidIndexOffset(index)!, 0, 0), ), ), ); @@ -363,6 +399,8 @@ class _CardSwiperState extends State } void _reset() { + onSwipeDirectionChanged(CardSwiperDirection.none); + _detectedDirection = CardSwiperDirection.none; setState(() { _animationController.reset(); _cardAnimation.reset(); @@ -402,7 +440,6 @@ class _CardSwiperState extends State void _swipe(CardSwiperDirection direction) { if (_currentIndex == null) return; - _swipeType = SwipeType.swipe; _detectedDirection = direction; _cardAnimation.animate(context, direction); @@ -410,7 +447,6 @@ class _CardSwiperState extends State void _goBack() { _swipeType = SwipeType.back; - _detectedDirection = CardSwiperDirection.none; _cardAnimation.animateBack(context); } diff --git a/lib/src/typedefs.dart b/lib/src/typedefs.dart index d628f6f..fd49f18 100644 --- a/lib/src/typedefs.dart +++ b/lib/src/typedefs.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:flutter_card_swiper/src/enums.dart'; typedef CardSwiperOnSwipe = FutureOr Function( @@ -8,6 +9,23 @@ typedef CardSwiperOnSwipe = FutureOr Function( CardSwiperDirection direction, ); +typedef CardSwiperOnSwipeUpdate = Function( + int? currentIndex, + CardSwiperDirection direction, +); + +typedef NullableCardBuilder = Widget? Function( + BuildContext context, + int index, + int horizontalOffsetPercentage, + int verticalOffsetPercentage, +); + +typedef CardSwiperDirectionChange = Function( + CardSwiperDirection horizontalDirection, + CardSwiperDirection verticalDirection, +); + typedef CardSwiperOnEnd = FutureOr Function(); typedef CardSwiperOnTapDisabled = FutureOr Function(); diff --git a/pubspec.yaml b/pubspec.yaml index c1db8ae..5369429 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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. homepage: https://github.com/ricardodalarme/flutter_card_swiper issue_tracker: https://github.com/ricardodalarme/flutter_card_swiper/issues -version: 5.1.0 +version: 6.0.0 environment: sdk: ">=2.12.0 <4.0.0" diff --git a/test/test_helpers/card_builder.dart b/test/test_helpers/card_builder.dart index ba57003..43204c5 100644 --- a/test/test_helpers/card_builder.dart +++ b/test/test_helpers/card_builder.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; -Widget? genericBuilder(BuildContext context, int index) { +Widget? genericBuilder( + BuildContext context, + int index, + int horizontalPercentage, + int verticalPercentage, +) { return Container( width: 200, height: 200,