chore(package): change the base to the `appinio_swiper` package

This commit is contained in:
Ricardo Dalarme 2023-01-15 21:58:45 -03:00
parent 1bd59b9946
commit 04b2346ad2
24 changed files with 1121 additions and 593 deletions

175
README.md
View File

@ -1,66 +1,159 @@
# swipeable_cards_stack ```appinio_swiper``` is a Flutter package for a Tinder Card Swiper. ✨
This is Tinder like swipeable cards package. You can add your own widgets to the stack, receive all four events, left, right, up and down. You can define your own business logic for each direction. It allows swiping in all directions with any Custom Widget (Stateless or Statefull).
<img alt="Demo" src="https://github.com/ricardodalarme/swipeable-cards-stack/raw/main/images/swipe-card.gif" height="500"> Very smooth animations supporting Android, iOS & WebApp.
## Documentation ## Why?
### Installation We build this package because we wanted to:
Add `swipeable_cards_stack` to your `pubspec.yaml`: - have a complete customizable slider
- be able to swipe in every direction
- trigger unswipe however we want
- choose our own settings for the swiper such as duration, angle, padding..
- NEW: trigger swipe, swipe left and swipe right however we want
- NEW: add functions while un-/swiping, on end or when the swiper is disabled
- NEW: detect the direction (left, right, top, bottom) in which the card was swiped away
- NEW: unswipe all cards
## ❗NEW Features ❗
### Trigger swipe left and swipe right through controller
You can now trigger swipe left and swipe right with our ```AppinioSwiperController``` regardless of the chosen ```AppinioSwipeDirection``` (which is still used when ```swipe``` is called through the controller). Just like the unswipe and swipe call, you can call ```swipeLeft``` or ```swipeRight``` through the controller anywhere you want.
### Unswipe all cards
You can now unswipe as many cards as possible. If you set the parameter ```unlimitedUnswipe```to ```true``` (default value: ```false```) the limit of 1 card is extended to the number of cards that have been swiped away. The way to call ```unswipe``` hasn't changed.
### Detect direction of swipe
We've added the direction in which the card was swiped away to the function ```onSwipe```. The ```AppinioSwipeDirection``` gets now returned when the function gets called.
### Sending Feedback when widget is unswiped
We've added the function ```unswipe``` that now gets returned with the boolean ```true``` when the last card is unswiped and with boolean ```false``` when there is no last card to unswipe.
### Trigger swipe through controller
You can now trigger the swipe with our ```AppinioSwiperController```. Just like the unswipe call, you can call the ```swipe``` trough the controller anywhere you want. Just make sure to pass the controller to the parameter ```controller``` from our ```AppinioSwiper```.
## Show Cases
<img src="https://github.com/appinioGmbH/flutter_packages/blob/main/assets/swiper/swiping.gif?raw=true" height="250" /> <img src="https://github.com/appinioGmbH/flutter_packages/blob/main/assets/swiper/swipe_button.gif?raw=true" height="250" />
Trigger swipe right and swipe left however you want...
<img src="https://github.com/appinioGmbH/flutter_packages/blob/main/assets/swiper/swipe_left_right.gif?raw=true" height="250" />
Unswipe the cards however you want...
<img src="https://github.com/appinioGmbH/flutter_packages/blob/main/assets/swiper/unswipe.gif?raw=true" height="250" />
Customize the angle...
<img src="https://github.com/appinioGmbH/flutter_packages/blob/main/assets/swiper/angle.gif?raw=true" height="250" />
Customize the threshold of the swiper, when the card should slide away...
<img src="https://github.com/appinioGmbH/flutter_packages/blob/main/assets/swiper/treshold.gif?raw=true" height="250" />
## Installation
Create a new project with the command
```yaml
flutter create MyApp
```
Add
```yaml
appinio_swiper: ...
```
to your `pubspec.yaml` of your flutter project.
**OR**
run
```yaml ```yaml
dependencies: flutter pub add appinio_swiper
swipeable_cards_stack: <latest version>
``` ```
in your project's root directory.
### Usage
Use the `SwipeableCardsStack` widget provided by the package In your library add the following import:
```dart ```dart
import 'package:swipeable_cards_stack/swipeable_cards_stack.dart'; import 'package:appinio_swiper/appinio_swiper.dart';
```
class MyWidget extends StatefulWidget { For help getting started with Flutter, view the online [documentation](https://flutter.io/).
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> { ## Usage
final _cardsController = SwipeableCardsStackController(); You can place your `AppinioSwiper` inside of a `Scaffold` or `CupertinoPageScaffold` like we did here. Optional parameters can be defined to enable different features. See the following example..
```dart
import 'package:appinio_swiper/appinio_swiper.dart';
import 'package:flutter/cupertino.dart';
class Example extends StatelessWidget {
List<Container> cards = [
Container(
alignment: Alignment.center,
child: const Text('1'),
color: CupertinoColors.activeBlue,
),
Container(
alignment: Alignment.center,
child: const Text('2'),
color: CupertinoColors.activeBlue,
),
Container(
alignment: Alignment.center,
child: const Text('3'),
color: CupertinoColors.activeBlue,
)
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SwipeableCardsStack( return CupertinoPageScaffold(
cardController: _cardsController, child: SizedBox(
context: context, height: MediaQuery.of(context).size.height * 0.75,
// Add the first 3 cards (widgets) child: AppinioSwiper(
items: [ cards: cards,
CardView(text: "First card"), ),
CardView(text: "Second card"), ),
CardView(text: "Third card"),
],
// Get card swipe event callbacks
onCardSwiped: (dir, index, widget) {
// Add the next card using _cardController
_cardsController.addItem(CardView(text: "Next card"));
// Take action on the swiped widget based on the direction of swipe
// Return false to not animate cards
},
enableSwipeUp: true,
enableSwipeDown: false,
); );
} }
} }
``` ```
### Contributing ## Constructor
#### Basic
If you want to contribute to this project, you may easily create issues and send PRs. Please take note that your code contributions will be applicable under MIT license unless specified otherwise.
### Credits | Parameter | Default | Description | Required |
| ------------- |:-------------|:-----|:-----:|
| cards | - | List of Widgets for the swiper | true
| controller | - | Trigger unswipe | false
| padding | EdgeInsets.symmetric(horizontal: 20, vertical: 25) | Control swiper padding | false
| duration | 200 milliseconds | The duration that every animation should last | false
| maxAngle | 30 | Maximum angle the card reaches while swiping | false
| threshold | 50 | Threshold from which the card is swiped away | false
| isDisabled | false | Set to ```true``` if swiping should be disabled, has no impact when triggered from the outside | false
| onTapDisabled | - | Function that get triggered when the swiper is disabled | false
| onSwipe | - | Called with the new index and detected swipe direction when the user swiped | false
| onEnd | - | Called when there is no Widget left to be swiped away | false
| direction | right | Direction in which the card is swiped away when triggered from the outside | false
| allowUnswipe | true | Set to ```false``` if unswipe should be disabled away | false
| unlimitedUnswipe | false | Set to ```true``` if the user can unswipe as many cards as possible | false
| unswipe | - | Called with the boolean ```true``` when the last card gets unswiped and with the boolean ```false``` if there is no card to unswipe | false
- **Ricardo Dalarme** (Package maintainer) #### Controller
- [**CodeToArt Technology**](https://github.com/codetoart) (Original project creator)
The ```Controller``` is used to control the ```swipe```, ```swipeLeft```, ```swipeRight``` or ```unswipe``` function of the swiper from outside of the widget. You can create a controller called ```AppinioSwiperController``` and save the instance for further usage. Please have a closer look to our Example for the usage.
| Method | Description
| ------------- |:-------------
| swipe | Changes the state of the controller to swipe and swipes the card in your selected direction.
| swipeLeft | Changes the state of the controller to swipe left and swipes the card to the left side.
| swipeRight | Changes the state of the controller to swipe right and swipes the card to the right side.
| unswipe | Changes the state of the controller to unswipe and brings back the last card that was swiped away.
<hr/>
Made with ❤ by Flutter team at <a href="https://appinio.com">Appinio GmbH</a>

View File

@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
class CardView extends StatelessWidget {
const CardView({
Key? key,
this.text = "Card View",
}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [
0.5,
1,
],
colors: [
Colors.blueAccent,
Color.fromARGB(255, 6, 28, 61),
],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
text,
style: const TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w700),
),
const Padding(padding: EdgeInsets.only(bottom: 8.0)),
Text("$text details",
textAlign: TextAlign.start,
style: const TextStyle(color: Colors.white)),
],
),
);
}
}

View File

@ -0,0 +1,98 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:appinio_swiper/appinio_swiper.dart';
class ExampleButton extends StatelessWidget {
final Function onTap;
final Widget child;
const ExampleButton({
required this.onTap,
required this.child,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap(),
child: child,
);
}
}
//swipe card to the right side
Widget swipeRightButton(AppinioSwiperController controller) {
return ExampleButton(
onTap: () => controller.swipeRight(),
child: Container(
height: 60,
width: 60,
decoration: BoxDecoration(
color: CupertinoColors.activeGreen,
borderRadius: BorderRadius.circular(50),
boxShadow: [
BoxShadow(
color: CupertinoColors.activeGreen.withOpacity(0.9),
spreadRadius: -10,
blurRadius: 20,
offset: const Offset(0, 20), // changes position of shadow
),
],
),
alignment: Alignment.center,
child: const Icon(
Icons.check,
color: CupertinoColors.white,
size: 40,
),
),
);
}
//swipe card to the left side
Widget swipeLeftButton(AppinioSwiperController controller) {
return ExampleButton(
onTap: () => controller.swipeLeft(),
child: Container(
height: 60,
width: 60,
decoration: BoxDecoration(
color: const Color(0xFFFF3868),
borderRadius: BorderRadius.circular(50),
boxShadow: [
BoxShadow(
color: const Color(0xFFFF3868).withOpacity(0.9),
spreadRadius: -10,
blurRadius: 20,
offset: const Offset(0, 20), // changes position of shadow
),
],
),
alignment: Alignment.center,
child: const Icon(
Icons.close,
color: CupertinoColors.white,
size: 40,
),
),
);
}
//unswipe card
Widget unswipeButton(AppinioSwiperController controller) {
return ExampleButton(
onTap: () => controller.unswipe(),
child: Container(
height: 60,
width: 60,
alignment: Alignment.center,
child: const Icon(
Icons.rotate_left_rounded,
color: CupertinoColors.systemGrey2,
size: 40,
),
),
);
}

View File

@ -0,0 +1,75 @@
import 'package:flutter/cupertino.dart';
class ExampleCandidateModel {
String? name;
String? job;
String? city;
LinearGradient? color;
ExampleCandidateModel({
this.name,
this.job,
this.city,
this.color,
});
}
List<ExampleCandidateModel> candidates = [
ExampleCandidateModel(
name: 'Two, 2',
job: 'Manager',
city: 'Town',
color: gradientPurple,
),
ExampleCandidateModel(
name: 'One, 1',
job: 'Manager',
city: 'Town',
color: gradientRed,
),
];
const LinearGradient gradientRed = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFF3868),
Color(0xFFFFB49A),
],
);
const LinearGradient gradientPurple = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF736EFE),
Color(0xFF62E4EC),
],
);
const LinearGradient gradientBlue = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF0BA4E0),
Color(0xFFA9E4BD),
],
);
const LinearGradient gradientPink = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFF6864),
Color(0xFFFFB92F),
],
);
const LinearGradient kNewFeedCardColorsIdentityGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF7960F1),
Color(0xFFE1A5C9),
],
);

View File

@ -0,0 +1,96 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'example_candidate_model.dart';
class ExampleCard extends StatelessWidget {
final ExampleCandidateModel candidate;
const ExampleCard({
Key? key,
required this.candidate,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: CupertinoColors.white,
boxShadow: [
BoxShadow(
color: CupertinoColors.systemGrey.withOpacity(0.2),
spreadRadius: 3,
blurRadius: 7,
offset: const Offset(0, 3),
)
],
),
alignment: Alignment.center,
child: Column(
children: [
Flexible(
child: Container(
decoration: BoxDecoration(
gradient: candidate.color,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
),
),
),
),
Container(
padding: const EdgeInsets.all(15),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
child: Row(
children: [
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
candidate.name!,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
const SizedBox(
height: 5,
),
Text(
candidate.job!,
style: const TextStyle(
color: Colors.grey,
fontSize: 15,
),
),
const SizedBox(
height: 5,
),
Text(
candidate.city!,
style: const TextStyle(
color: Colors.grey,
fontSize: 15,
),
)
],
),
],
),
),
],
),
);
}
}

View File

@ -1,86 +1,114 @@
import 'package:example/card_view.dart'; import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart';
import 'package:swipeable_cards_stack/swipeable_cards_stack.dart';
import 'package:appinio_swiper/appinio_swiper.dart';
import 'package:example/example_candidate_model.dart';
import 'package:example/example_card.dart';
import 'example_buttons.dart';
void main() { void main() {
runApp(const MaterialApp( runApp(const MyApp());
title: 'Flutter Demo',
home: const MyHomePage(),
));
} }
class MyHomePage extends StatefulWidget { class MyApp extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key); const MyApp({
Key? key,
@override }) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int counter = 4;
final cardController = SwipeableCardsStackController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const CupertinoApp(
body: Column( debugShowCheckedModeBanner: false,
mainAxisAlignment: MainAxisAlignment.spaceBetween, home: Example(),
);
}
}
class Example extends StatefulWidget {
const Example({
Key? key,
}) : super(key: key);
@override
State<Example> createState() => _ExamplePageState();
}
class _ExamplePageState extends State<Example> {
final AppinioSwiperController controller = AppinioSwiperController();
List<ExampleCard> cards = [];
@override
void initState() {
_loadCards();
super.initState();
}
void _loadCards() {
for (ExampleCandidateModel candidate in candidates) {
cards.add(
ExampleCard(
candidate: candidate,
),
);
}
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: [ children: [
SwipeableCardsStack( const SizedBox(
cardController: cardController, height: 50,
context: context,
//add the first 3 cards
items: const [
CardView(text: "First card"),
CardView(text: "Second card"),
CardView(text: "Third card"),
],
onCardSwiped: (dir, index, widget) {
//Add the next card
if (counter <= 20) {
cardController.addItem(CardView(text: "Card $counter"));
counter++;
}
if (dir == AxisDirection.left) {
debugPrint('onDisliked ${(widget as CardView).text} $index');
} else if (dir == AxisDirection.right) {
debugPrint('onLiked ${(widget as CardView).text} $index');
} else if (dir == AxisDirection.up) {
debugPrint('onUp ${(widget as CardView).text} $index');
} else if (dir == AxisDirection.down) {
debugPrint('onDown ${(widget as CardView).text} $index');
}
return true;
},
), ),
Container( SizedBox(
margin: const EdgeInsets.symmetric(vertical: 20.0), height: MediaQuery.of(context).size.height * 0.75,
child: Row( child: AppinioSwiper(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, unlimitedUnswipe: true,
children: [ controller: controller,
FloatingActionButton( unswipe: _unswipe,
child: const Icon(Icons.chevron_left), cards: cards,
onPressed: () => cardController.triggerSwipeLeft(), onSwipe: _swipe,
), padding: const EdgeInsets.only(
FloatingActionButton( left: 25,
child: const Icon(Icons.chevron_right), right: 25,
onPressed: () => cardController.triggerSwipeRight(), top: 50,
), bottom: 40,
FloatingActionButton( ),
child: const Icon(Icons.arrow_upward),
onPressed: () => cardController.triggerSwipeUp(),
),
FloatingActionButton(
child: const Icon(Icons.arrow_downward),
onPressed: () => cardController.triggerSwipeDown(),
),
],
), ),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
width: 80,
),
swipeLeftButton(controller),
const SizedBox(
width: 20,
),
swipeRightButton(controller),
const SizedBox(
width: 20,
),
unswipeButton(controller),
],
) )
], ],
), ),
); );
} }
void _swipe(int index, AppinioSwiperDirection direction) {
log("the card was swiped to the: " + direction.name);
}
void _unswipe(bool unswiped) {
if (unswiped) {
log("SUCCESS: card was unswiped");
} else {
log("FAIL: no card left to unswipe");
}
}
} }

View File

@ -1,6 +1,13 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
appinio_swiper:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.1.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -39,13 +46,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.99"
swipeable_cards_stack:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.2+1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -54,5 +54,5 @@ packages:
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
sdks: sdks:
dart: ">=2.18.6 <3.0.0" dart: ">=2.17.0-0 <3.0.0"
flutter: ">=3.0.0" flutter: ">=1.17.0"

View File

@ -1,17 +1,16 @@
name: example name: example
description: A new Flutter project. description: Example for the Appinio Swiper
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none'
version: 1.0.0+1 version: 0.0.1
environment: environment:
sdk: '>=2.18.6 <3.0.0' sdk: ">=2.12.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
appinio_swiper:
swipeable_cards_stack:
path: ../ path: ../
flutter: flutter:

BIN
images/angle.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

BIN
images/swipe_button.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

BIN
images/swipe_left_right.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

BIN
images/swiping.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

BIN
images/treshold.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

BIN
images/unswipe.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

600
lib/appinio_swiper.dart Normal file
View File

@ -0,0 +1,600 @@
import 'dart:math';
import 'package:flutter/material.dart';
class AppinioSwiper extends StatefulWidget {
/// list of widgets for the swiper
final List<Widget?>? cards;
/// controller to trigger unswipe action
final AppinioSwiperController? controller;
/// duration of every animation
final Duration duration;
/// padding of the swiper
final EdgeInsetsGeometry padding;
/// maximum angle the card reaches while swiping
final double maxAngle;
/// threshold from which the card is swiped away
final int threshold;
/// set to true if swiping should be disabled, exception: triggered from the outside
final bool isDisabled;
/// set to false if unswipe should be disabled
final bool allowUnswipe;
/// set to true if the user can unswipe as many cards as possible
final bool unlimitedUnswipe;
/// function that gets called with the new index and detected swipe direction when the user swiped or swipe is triggered by controller
final Function onSwipe;
/// function that gets called when there is no widget left to be swiped away
final Function onEnd;
/// function that gets triggered when the swiper is disabled
final Function onTapDisabled;
/// function that gets called with the boolean true when the last card gets unswiped and with the boolean false when there is no card to unswipe
final Function unswipe;
/// direction in which the card gets swiped when triggered by controller, default set to right
final AppinioSwiperDirection direction;
const AppinioSwiper({
Key? key,
required this.cards,
this.controller,
this.padding = const EdgeInsets.symmetric(horizontal: 20, vertical: 25),
this.duration = const Duration(milliseconds: 200),
this.maxAngle = 30,
this.threshold = 50,
this.isDisabled = false,
this.allowUnswipe = true,
this.unlimitedUnswipe = false,
this.onTapDisabled = emptyFunction,
this.onSwipe = emptyFunctionIndex,
this.onEnd = emptyFunction,
this.unswipe = emptyFunctionBool,
this.direction = AppinioSwiperDirection.right,
}) : assert(maxAngle >= 0 && maxAngle <= 360),
assert(threshold >= 1 && threshold <= 100),
assert(direction != AppinioSwiperDirection.none),
super(key: key);
@override
State createState() => _AppinioSwiperState();
}
class _AppinioSwiperState extends State<AppinioSwiper>
with SingleTickerProviderStateMixin {
double _left = 0;
double _top = 0;
double _total = 0;
double _angle = 0;
double _maxAngle = 0;
double _scale = 0.9;
double _difference = 40;
int _swipeTyp = 0; // 1 = swipe, 2 = unswipe, 3 = goBack
bool _tapOnTop = false; //position of starting drag point on card
late AnimationController _animationController;
late Animation<double> _leftAnimation;
late Animation<double> _topAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _differenceAnimation;
late Animation<double> _unSwipeLeftAnimation;
late Animation<double> _unSwipeTopAnimation;
bool _vertical = false;
bool _horizontal = false;
bool _isUnswiping = false;
int _swipedDirectionVertical = 0; //-1 left, 1 right
int _swipedDirectionHorizontal = 0; //-1 bottom, 1 top
AppinioUnswipeCard? _lastCard;
// ignore: prefer_final_fields
List<AppinioUnswipeCard?> _lastCards = [];
AppinioSwiperDirection detectedDirection = AppinioSwiperDirection.none;
@override
void initState() {
super.initState();
if (widget.controller != null) {
widget.controller!
//swipe widget from the outside
..addListener(() {
if (widget.controller!.state == AppinioSwiperState.swipe) {
if (widget.cards!.isNotEmpty) {
switch (widget.direction) {
case AppinioSwiperDirection.right:
_swipeHorizontal(context);
break;
case AppinioSwiperDirection.left:
_swipeHorizontal(context);
break;
case AppinioSwiperDirection.top:
_swipeVertical(context);
break;
case AppinioSwiperDirection.bottom:
_swipeVertical(context);
break;
case AppinioSwiperDirection.none:
break;
}
_animationController.forward();
}
}
})
//swipe widget left from the outside
..addListener(() {
if (widget.controller!.state == AppinioSwiperState.swipeLeft) {
if (widget.cards!.isNotEmpty) {
_left = -1;
_swipeHorizontal(context);
_animationController.forward();
}
}
})
//swipe widget right from the outside
..addListener(() {
if (widget.controller!.state == AppinioSwiperState.swipeRight) {
if (widget.cards!.isNotEmpty) {
_left = widget.threshold + 1;
_swipeHorizontal(context);
_animationController.forward();
}
}
})
//unswipe widget from the outside
..addListener(() {
if (widget.controller!.state == AppinioSwiperState.unswipe) {
if (widget.allowUnswipe) {
if (!_isUnswiping) {
if (_lastCard != null || _lastCards.isNotEmpty) {
if (widget.unlimitedUnswipe) {
_unswipe(_lastCards.last!);
} else {
_unswipe(_lastCard!);
}
widget.unswipe(true);
_animationController.forward();
} else {
widget.unswipe(false);
}
}
}
}
});
}
if (widget.maxAngle > 0) {
_maxAngle = widget.maxAngle * (pi / 180);
}
_animationController =
AnimationController(duration: widget.duration, vsync: this);
_animationController.addListener(() {
//when value of controller changes
if (_animationController.status == AnimationStatus.forward) {
setState(() {
if (_swipeTyp != 2) {
_left = _leftAnimation.value;
_top = _topAnimation.value;
}
if (_swipeTyp == 2) {
_left = _unSwipeLeftAnimation.value;
_top = _unSwipeTopAnimation.value;
}
_scale = _scaleAnimation.value;
_difference = _differenceAnimation.value;
});
}
});
_animationController.addStatusListener((status) {
//when status of controller changes
if (status == AnimationStatus.completed) {
setState(() {
if (_swipeTyp == 1) {
if (widget.unlimitedUnswipe) {
_lastCards.add(
AppinioUnswipeCard(
widget: widget.cards!.last!,
horizontal: _horizontal,
vertical: _vertical,
swipedDirectionHorizontal: _swipedDirectionHorizontal,
swipedDirectionVertical: _swipedDirectionVertical,
),
);
} else {
_lastCard = AppinioUnswipeCard(
widget: widget.cards!.last!,
horizontal: _horizontal,
vertical: _vertical,
swipedDirectionHorizontal: _swipedDirectionHorizontal,
swipedDirectionVertical: _swipedDirectionVertical,
);
}
_swipedDirectionHorizontal = 0;
_swipedDirectionVertical = 0;
_vertical = false;
_horizontal = false;
widget.cards!.removeLast();
widget.onSwipe(widget.cards!.length, detectedDirection);
if (widget.cards!.isEmpty) widget.onEnd();
} else if (_swipeTyp == 2) {
if (widget.unlimitedUnswipe) {
_lastCards.removeLast();
} else {
_lastCard = null;
}
_isUnswiping = false;
}
_animationController.reset();
_left = 0;
_top = 0;
_total = 0;
_angle = 0;
_scale = 0.9;
_difference = 40;
_swipeTyp = 0;
});
}
});
}
@override
void dispose() {
super.dispose();
_animationController.dispose();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
padding: widget.padding,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
clipBehavior: Clip.none,
fit: StackFit.expand,
children: [
...widget.cards!
.asMap()
.map((index, _) {
return MapEntry(
index,
_item(constraints, index),
);
})
.values
.toList(),
]);
},
),
);
},
);
}
Widget _item(BoxConstraints constraints, int index) {
if (index != widget.cards!.length - 1) {
return Visibility(
visible: widget.cards!.length - index <= 2,
child: Positioned(
top: _difference,
left: 0,
child: Container(
color: Colors.transparent,
child: Transform.scale(
scale: _scale,
child: Container(
constraints: constraints,
child: widget.cards![index],
),
),
),
),
);
}
return Positioned(
left: _left,
top: _top,
child: GestureDetector(
child: Transform.rotate(
angle: _angle,
child: Container(
constraints: constraints,
child: widget.cards![index],
),
),
onTap: () {
if (widget.isDisabled) {
widget.onTapDisabled();
}
},
onPanStart: (tapInfo) {
if (!widget.isDisabled) {
RenderBox renderBox = context.findRenderObject() as RenderBox;
Offset position = renderBox.globalToLocal(tapInfo.globalPosition);
if (position.dy < renderBox.size.height / 2) _tapOnTop = true;
}
},
onPanUpdate: (tapInfo) {
if (!widget.isDisabled) {
setState(() {
_left += tapInfo.delta.dx;
_top += tapInfo.delta.dy;
_total = _left + _top;
_calculateAngle();
_calculateScale();
_calculateDifference();
});
}
},
onPanEnd: (tapInfo) {
if (!widget.isDisabled) {
_tapOnTop = false;
_onEndAnimation();
_animationController.forward();
}
},
),
);
}
void _calculateAngle() {
if (_angle <= _maxAngle && _angle >= -_maxAngle) {
(_tapOnTop == true)
? _angle = (_maxAngle / 100) * (_left / 10)
: _angle = (_maxAngle / 100) * (_left / 10) * -1;
}
}
void _calculateScale() {
if (_scale <= 1.0 && _scale >= 0.9) {
_scale =
(_total > 0) ? 0.9 + (_total / 5000) : 0.9 + -1 * (_total / 5000);
}
}
void _calculateDifference() {
if (_difference >= 0 && _difference <= _difference) {
_difference = (_total > 0) ? 40 - (_total / 10) : 40 + (_total / 10);
}
}
void _onEndAnimation() {
if (_left < -widget.threshold || _left > widget.threshold) {
_swipeHorizontal(context);
} else if (_top < -widget.threshold || _top > widget.threshold) {
_swipeVertical(context);
} else {
_goBack(context);
}
}
//moves the card away to the left or right
void _swipeHorizontal(BuildContext context) {
setState(() {
_swipeTyp = 1;
_leftAnimation = Tween<double>(
begin: _left,
end: (_left == 0)
? (widget.direction == AppinioSwiperDirection.right)
? MediaQuery.of(context).size.width
: -MediaQuery.of(context).size.width
: (_left > widget.threshold)
? MediaQuery.of(context).size.width
: -MediaQuery.of(context).size.width,
).animate(_animationController);
_topAnimation = Tween<double>(
begin: _top,
end: _top + _top,
).animate(_animationController);
_scaleAnimation = Tween<double>(
begin: _scale,
end: 1.0,
).animate(_animationController);
_differenceAnimation = Tween<double>(
begin: _difference,
end: 0,
).animate(_animationController);
});
if (_left > widget.threshold ||
_left == 0 && widget.direction == AppinioSwiperDirection.right) {
_swipedDirectionHorizontal = 1;
detectedDirection = AppinioSwiperDirection.right;
} else {
_swipedDirectionHorizontal = -1;
detectedDirection = AppinioSwiperDirection.left;
}
(_top <= 0) ? _swipedDirectionVertical = 1 : _swipedDirectionVertical = -1;
_horizontal = true;
}
//moves the card away to the top or bottom
void _swipeVertical(BuildContext context) {
setState(() {
_swipeTyp = 1;
_leftAnimation = Tween<double>(
begin: _left,
end: _left + _left,
).animate(_animationController);
_topAnimation = Tween<double>(
begin: _top,
end: (_top == 0)
? (widget.direction == AppinioSwiperDirection.bottom)
? MediaQuery.of(context).size.height
: -MediaQuery.of(context).size.height
: (_top > widget.threshold)
? MediaQuery.of(context).size.height
: -MediaQuery.of(context).size.height,
).animate(_animationController);
_scaleAnimation = Tween<double>(
begin: _scale,
end: 1.0,
).animate(_animationController);
_differenceAnimation = Tween<double>(
begin: _difference,
end: 0,
).animate(_animationController);
});
if (_top > widget.threshold ||
_top == 0 && widget.direction == AppinioSwiperDirection.bottom) {
_swipedDirectionVertical = -1;
detectedDirection = AppinioSwiperDirection.bottom;
} else {
_swipedDirectionVertical = 1;
detectedDirection = AppinioSwiperDirection.top;
}
(_left >= 0)
? _swipedDirectionHorizontal = 1
: _swipedDirectionHorizontal = -1;
_vertical = true;
}
//moves the card back to starting position
void _goBack(BuildContext context) {
setState(() {
_swipeTyp = 3;
_leftAnimation = Tween<double>(
begin: _left,
end: 0,
).animate(_animationController);
_topAnimation = Tween<double>(
begin: _top,
end: 0,
).animate(_animationController);
_scaleAnimation = Tween<double>(
begin: _scale,
end: 0.9,
).animate(_animationController);
_differenceAnimation = Tween<double>(
begin: _difference,
end: 40,
).animate(_animationController);
});
}
//unswipe the card: brings back the last card that was swiped away
void _unswipe(AppinioUnswipeCard card) {
_isUnswiping = true;
widget.cards!.add(card.widget);
_swipeTyp = 2;
//unSwipe horizontal
if (card.horizontal == true) {
_unSwipeLeftAnimation = Tween<double>(
begin: (card.swipedDirectionHorizontal == 1)
? MediaQuery.of(context).size.width
: -MediaQuery.of(context).size.width,
end: 0,
).animate(_animationController);
_unSwipeTopAnimation = Tween<double>(
begin: (card.swipedDirectionVertical == 1)
? -MediaQuery.of(context).size.height / 4
: MediaQuery.of(context).size.height / 4,
end: 0,
).animate(_animationController);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: _scale,
).animate(_animationController);
_differenceAnimation = Tween<double>(
begin: 0,
end: _difference,
).animate(_animationController);
}
//unSwipe vertical
if (card.vertical == true) {
_unSwipeLeftAnimation = Tween<double>(
begin: (card.swipedDirectionHorizontal == 1)
? MediaQuery.of(context).size.width / 4
: -MediaQuery.of(context).size.width / 4,
end: 0,
).animate(_animationController);
_unSwipeTopAnimation = Tween<double>(
begin: (card.swipedDirectionVertical == 1)
? -MediaQuery.of(context).size.height
: MediaQuery.of(context).size.height,
end: 0,
).animate(_animationController);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: _scale,
).animate(_animationController);
_differenceAnimation = Tween<double>(
begin: 0,
end: _difference,
).animate(_animationController);
}
setState(() {});
}
}
//for null safety
void emptyFunction() {}
void emptyFunctionIndex(int index, AppinioSwiperDirection direction) {}
void emptyFunctionBool(bool unswiped) {}
//to call the swipe or unswipe function from outside of the appinio swiper
class AppinioSwiperController extends ChangeNotifier {
AppinioSwiperState? state;
//swipe the card by changing the status of the controller
void swipe() {
state = AppinioSwiperState.swipe;
notifyListeners();
}
//swipe the card to the left side by changing the status of the controller
void swipeLeft() {
state = AppinioSwiperState.swipeLeft;
notifyListeners();
}
//swipe the card to the right side by changing the status of the controller
void swipeRight() {
state = AppinioSwiperState.swipeRight;
notifyListeners();
}
//calls unswipe the card by changing the status of the controller
void unswipe() {
state = AppinioSwiperState.unswipe;
notifyListeners();
}
}
class AppinioUnswipeCard {
Widget widget;
bool horizontal;
bool vertical;
int swipedDirectionHorizontal;
int swipedDirectionVertical;
AppinioUnswipeCard({
required this.widget,
required this.horizontal,
required this.vertical,
required this.swipedDirectionHorizontal,
required this.swipedDirectionVertical,
});
}
enum AppinioSwiperState { swipe, swipeLeft, swipeRight, unswipe }
enum AppinioSwiperDirection { none, left, right, top, bottom }

View File

@ -1,81 +0,0 @@
import 'package:flutter/widgets.dart';
import 'swipeable_cards_stack.dart';
class CardsAnimation {
static Animation<Alignment> backCardAlignmentAnim(
AnimationController parent,
) {
return AlignmentTween(begin: cardsAlign[0], end: cardsAlign[1]).animate(
CurvedAnimation(
parent: parent,
curve: const Interval(0.4, 0.7, curve: Curves.easeIn),
),
);
}
static Animation<Size?> backCardSizeAnim(AnimationController parent) {
return SizeTween(begin: cardsSize[2], end: cardsSize[1]).animate(
CurvedAnimation(
parent: parent,
curve: const Interval(0.4, 0.7, curve: Curves.easeIn),
),
);
}
static Animation<Alignment> middleCardAlignmentAnim(
AnimationController parent,
) {
return AlignmentTween(begin: cardsAlign[1], end: cardsAlign[2]).animate(
CurvedAnimation(
parent: parent,
curve: const Interval(0.2, 0.5, curve: Curves.easeIn),
),
);
}
static Animation<Size?> middleCardSizeAnim(AnimationController parent) {
return SizeTween(begin: cardsSize[1], end: cardsSize[0]).animate(
CurvedAnimation(
parent: parent,
curve: const Interval(0.2, 0.5, curve: Curves.easeIn),
),
);
}
static Animation<Alignment> frontCardDisappearAlignmentAnim(
AnimationController parent,
Alignment beginAlign,
) {
if (beginAlign.x == -0.001 ||
beginAlign.x == 0.001 ||
beginAlign.x > 3.0 ||
beginAlign.x < -3.0) {
return AlignmentTween(
begin: beginAlign,
end: Alignment(
beginAlign.x > 0 ? beginAlign.x + 30.0 : beginAlign.x - 30.0,
0.0,
), // Has swiped to the left or right?
).animate(
CurvedAnimation(
parent: parent,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
),
);
} else {
return AlignmentTween(
begin: beginAlign,
end: Alignment(
0.0,
beginAlign.y > 0 ? beginAlign.y + 30.0 : beginAlign.y - 30.0,
), // Has swiped to the top or bottom?
).animate(
CurvedAnimation(
parent: parent,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
),
);
}
}
}

View File

@ -1,294 +0,0 @@
library swipeable_cards_stack;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:swipeable_cards_stack/src/cards_animation.dart';
import 'package:swipeable_cards_stack/src/swipeable_cards_stack_controller.dart';
const List<Alignment> cardsAlign = [
Alignment(0.0, 1.0),
Alignment(0.0, 0.8),
Alignment(0.0, 0.0)
];
final List<Size> cardsSize = List.filled(3, const Size(1, 1));
typedef OnCardSwiped = bool Function(
AxisDirection dir, int index, Widget? widget);
class SwipeableCardsStack extends StatefulWidget {
final SwipeableCardsStackController? cardController;
//First 3 widgets
final List<Widget> items;
final OnCardSwiped? onCardSwiped;
final double cardWidthTopMul;
final double cardWidthMiddleMul;
final double cardWidthBottomMul;
final double cardHeightTopMul;
final double cardHeightMiddleMul;
final double cardHeightBottomMul;
final bool enableSwipeUp;
final bool enableSwipeDown;
final bool enableSwipeLeft;
final bool enableSwipeRight;
SwipeableCardsStack({
Key? key,
this.cardController,
required BuildContext context,
required this.items,
this.onCardSwiped,
this.cardWidthTopMul = 0.9,
this.cardWidthMiddleMul = 0.85,
this.cardWidthBottomMul = 0.8,
this.cardHeightTopMul = 0.6,
this.cardHeightMiddleMul = 0.55,
this.cardHeightBottomMul = 0.5,
this.enableSwipeUp = true,
this.enableSwipeDown = true,
this.enableSwipeLeft = true,
this.enableSwipeRight = true,
}) : super(key: key) {
cardsSize[0] = Size(
MediaQuery.of(context).size.width * cardWidthTopMul,
MediaQuery.of(context).size.height * cardHeightTopMul,
);
cardsSize[1] = Size(
MediaQuery.of(context).size.width * cardWidthMiddleMul,
MediaQuery.of(context).size.height * cardHeightMiddleMul,
);
cardsSize[2] = Size(
MediaQuery.of(context).size.width * cardWidthBottomMul,
MediaQuery.of(context).size.height * cardHeightBottomMul,
);
}
@override
State<SwipeableCardsStack> createState() => _SwipeableCardsStackState();
}
class _SwipeableCardsStackState extends State<SwipeableCardsStack>
with SingleTickerProviderStateMixin {
int cardsCounter = 0;
int index = 0;
Widget? appendCard;
List<Widget?> cards = [];
late AnimationController _controller;
bool enableSwipe = true;
final Alignment defaultFrontCardAlign = const Alignment(0.0, 0.0);
Alignment frontCardAlign = cardsAlign[2];
double frontCardRot = 0.0;
void _triggerSwipe(AxisDirection dir) {
final swipedCallback = widget.onCardSwiped ?? (_, __, ___) => true;
bool? shouldAnimate = false;
if (dir == AxisDirection.left) {
shouldAnimate = swipedCallback(AxisDirection.left, index, cards[0]);
frontCardAlign = const Alignment(-0.001, 0.0);
} else if (dir == AxisDirection.right) {
shouldAnimate = swipedCallback(AxisDirection.right, index, cards[0]);
frontCardAlign = const Alignment(0.001, 0.0);
} else if (dir == AxisDirection.up) {
shouldAnimate = swipedCallback(AxisDirection.up, index, cards[0]);
frontCardAlign = const Alignment(0.0, -0.001);
} else if (dir == AxisDirection.down) {
shouldAnimate = swipedCallback(AxisDirection.down, index, cards[0]);
frontCardAlign = const Alignment(0.0, 0.001);
}
if (shouldAnimate) {
animateCards();
}
}
void _appendItem(Widget newCard) {
appendCard = newCard;
}
void _enableSwipe(bool isSwipeEnabled) {
setState(() {
enableSwipe = isSwipeEnabled;
});
}
@override
void initState() {
super.initState();
final cardController = widget.cardController;
if (cardController != null) {
cardController.listener = _triggerSwipe;
cardController.addItem = _appendItem;
cardController.enableSwipeListener = _enableSwipe;
}
// Init cards
for (cardsCounter = 0; cardsCounter < 3; cardsCounter++) {
if (widget.items.isNotEmpty && cardsCounter < widget.items.length) {
cards.add(widget.items[cardsCounter]);
} else {
cards.add(null);
}
}
frontCardAlign = cardsAlign[2];
// Init the animation controller
_controller = AnimationController(
duration: const Duration(milliseconds: 700),
vsync: this,
);
_controller.addListener(() => setState(() {}));
_controller.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) changeCardsOrder();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Expanded(
child: IgnorePointer(
ignoring: !enableSwipe,
child: Stack(
children: <Widget>[
if (cards[2] != null) backCard(),
if (cards[1] != null) middleCard(),
if (cards[0] != null) frontCard(),
// Prevent swiping if the cards are animating
if (_controller.status != AnimationStatus.forward)
SizedBox.expand(
child: GestureDetector(
// While dragging the first card
onPanUpdate: (DragUpdateDetails details) {
// Add what the user swiped in the last frame to the alignment of the card
setState(() {
frontCardAlign = Alignment(
frontCardAlign.x +
20 *
details.delta.dx /
MediaQuery.of(context).size.width,
frontCardAlign.y +
20 *
details.delta.dy /
MediaQuery.of(context).size.height,
);
frontCardRot = frontCardAlign.x; // * rotation speed;
});
},
// When releasing the first card
onPanEnd: (_) {
// If the front card was swiped far enough to count as swiped
final onCardSwiped =
widget.onCardSwiped ?? (_, __, ___) => true;
bool? shouldAnimate = false;
if (frontCardAlign.x > 3.0 && widget.enableSwipeRight) {
shouldAnimate =
onCardSwiped(AxisDirection.right, index, cards[0]);
} else if (frontCardAlign.x < -3.0 &&
widget.enableSwipeLeft) {
shouldAnimate =
onCardSwiped(AxisDirection.left, index, cards[0]);
} else if (frontCardAlign.y < -3.0 &&
widget.enableSwipeUp) {
shouldAnimate =
onCardSwiped(AxisDirection.up, index, cards[0]);
} else if (frontCardAlign.y > 3.0 &&
widget.enableSwipeDown) {
shouldAnimate =
onCardSwiped(AxisDirection.down, index, cards[0]);
} else {
// Return to the initial rotation and alignment
setState(() {
frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
}
if (shouldAnimate) {
animateCards();
}
},
),
)
],
),
),
);
}
Widget backCard() {
return Align(
alignment: _controller.status == AnimationStatus.forward
? CardsAnimation.backCardAlignmentAnim(_controller).value
: cardsAlign[0],
child: SizedBox.fromSize(
size: _controller.status == AnimationStatus.forward
? CardsAnimation.backCardSizeAnim(_controller).value
: cardsSize[2],
child: cards[2],
),
);
}
Widget middleCard() {
return Align(
alignment: _controller.status == AnimationStatus.forward
? CardsAnimation.middleCardAlignmentAnim(_controller).value
: cardsAlign[1],
child: SizedBox.fromSize(
size: _controller.status == AnimationStatus.forward
? CardsAnimation.middleCardSizeAnim(_controller).value
: cardsSize[1],
child: cards[1],
),
);
}
Widget frontCard() {
return Align(
alignment: _controller.status == AnimationStatus.forward
? CardsAnimation.frontCardDisappearAlignmentAnim(
_controller,
frontCardAlign,
).value
: frontCardAlign,
child: Transform.rotate(
angle: (pi / 180.0) * frontCardRot,
child: SizedBox.fromSize(size: cardsSize[0], child: cards[0]),
),
);
}
void changeCardsOrder() {
setState(() {
// Swap cards (back card becomes the middle card; middle card becomes the front card)
cards[0] = cards[1];
cards[1] = cards[2];
cards[2] = appendCard;
appendCard = null;
cardsCounter++;
index++;
frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
}
void animateCards() {
_controller
..stop()
..value = 0.0
..forward();
}
}

View File

@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
typedef TriggerListener = void Function(AxisDirection dir);
typedef AppendItem = void Function(Widget item);
typedef EnableSwipe = void Function(bool dir);
class SwipeableCardsStackController {
late TriggerListener listener;
late AppendItem addItem;
late EnableSwipe enableSwipeListener;
void triggerSwipeLeft() {
return listener.call(AxisDirection.left);
}
void triggerSwipeRight() {
return listener.call(AxisDirection.right);
}
void triggerSwipeUp() {
return listener.call(AxisDirection.up);
}
void triggerSwipeDown() {
return listener.call(AxisDirection.down);
}
void appendItem(Widget item) {
return addItem.call(item);
}
void enableSwipe(bool isSwipeEnabled) {
return enableSwipeListener.call(isSwipeEnabled);
}
}

View File

@ -1,4 +0,0 @@
library swipeable_cards_stack;
export 'src/swipeable_cards_stack.dart';
export 'src/swipeable_cards_stack_controller.dart';

View File

@ -66,7 +66,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -150,5 +150,5 @@ packages:
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
sdks: sdks:
dart: ">=2.17.0 <3.0.0" dart: ">=2.17.0-206.0.dev <3.0.0"
flutter: ">=3.0.0" flutter: ">=1.17.0"

View File

@ -2,11 +2,11 @@ name: swipeable_cards_stack
description: This is Tinder like swipeable cards package. You can add your own widgets to the stack, receive all four events, left, right, up and down. You can define your own business logic for each direction. description: This is Tinder like swipeable cards package. You can add your own widgets to the stack, receive all four events, left, right, up and down. You can define your own business logic for each direction.
homepage: https://github.com/ricardodalarme/swipeable-cards-stack homepage: https://github.com/ricardodalarme/swipeable-cards-stack
issue_tracker: https://github.com/ricardodalarme/swipeable-cards-stack/issues issue_tracker: https://github.com/ricardodalarme/swipeable-cards-stack/issues
version: 1.0.2+1 version: 2.0.0
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
flutter: ">=3.0.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter: flutter:
@ -19,4 +19,4 @@ dev_dependencies:
screenshots: screenshots:
- description: 'Example of the widget.' - description: 'Example of the widget.'
path: images/swipe-card.gif path: images/swiping.gif