diff --git a/README.md b/README.md
index 857e754..041353b 100644
--- a/README.md
+++ b/README.md
@@ -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).
-
+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
+
+
+
+Trigger swipe right and swipe left however you want...
+
+
+
+Unswipe the cards however you want...
+
+
+
+Customize the angle...
+
+
+
+Customize the threshold of the swiper, when the card should slide away...
+
+
+
+## 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
-dependencies:
- swipeable_cards_stack:
+flutter pub add appinio_swiper
```
+in your project's root directory.
-### Usage
-Use the `SwipeableCardsStack` widget provided by the package
+In your library add the following import:
```dart
-import 'package:swipeable_cards_stack/swipeable_cards_stack.dart';
+import 'package:appinio_swiper/appinio_swiper.dart';
+```
-class MyWidget extends StatefulWidget {
- @override
- State createState() => _MyWidgetState();
-}
+For help getting started with Flutter, view the online [documentation](https://flutter.io/).
-class _MyWidgetState extends State {
- final _cardsController = SwipeableCardsStackController();
+## Usage
+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 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
Widget build(BuildContext context) {
- return SwipeableCardsStack(
- cardController: _cardsController,
- context: context,
- // Add the first 3 cards (widgets)
- items: [
- 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,
+ return CupertinoPageScaffold(
+ child: SizedBox(
+ height: MediaQuery.of(context).size.height * 0.75,
+ child: AppinioSwiper(
+ cards: cards,
+ ),
+ ),
);
}
}
```
-### 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)
-- [**CodeToArt Technology**](https://github.com/codetoart) (Original project creator)
+#### Controller
+
+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.
+
+
+
+Made with ❤ by Flutter team at Appinio GmbH
\ No newline at end of file
diff --git a/example/lib/card_view.dart b/example/lib/card_view.dart
deleted file mode 100644
index fd17758..0000000
--- a/example/lib/card_view.dart
+++ /dev/null
@@ -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: [
- 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)),
- ],
- ),
- );
- }
-}
diff --git a/example/lib/example_buttons.dart b/example/lib/example_buttons.dart
new file mode 100644
index 0000000..9129d29
--- /dev/null
+++ b/example/lib/example_buttons.dart
@@ -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,
+ ),
+ ),
+ );
+}
diff --git a/example/lib/example_candidate_model.dart b/example/lib/example_candidate_model.dart
new file mode 100644
index 0000000..d90820b
--- /dev/null
+++ b/example/lib/example_candidate_model.dart
@@ -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 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),
+ ],
+);
diff --git a/example/lib/example_card.dart b/example/lib/example_card.dart
new file mode 100644
index 0000000..1d340a9
--- /dev/null
+++ b/example/lib/example_card.dart
@@ -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,
+ ),
+ )
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index b15244f..dc77ce2 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,86 +1,114 @@
-import 'package:example/card_view.dart';
-import 'package:flutter/material.dart';
-import 'package:swipeable_cards_stack/swipeable_cards_stack.dart';
+import 'dart:developer';
+import 'package:flutter/cupertino.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() {
- runApp(const MaterialApp(
- title: 'Flutter Demo',
- home: const MyHomePage(),
- ));
+ runApp(const MyApp());
}
-class MyHomePage extends StatefulWidget {
- const MyHomePage({Key? key}) : super(key: key);
-
- @override
- State createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State {
- int counter = 4;
- final cardController = SwipeableCardsStackController();
+class MyApp extends StatelessWidget {
+ const MyApp({
+ Key? key,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
- return Scaffold(
- body: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ return const CupertinoApp(
+ debugShowCheckedModeBanner: false,
+ home: Example(),
+ );
+ }
+}
+
+class Example extends StatefulWidget {
+ const Example({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ State createState() => _ExamplePageState();
+}
+
+class _ExamplePageState extends State {
+ final AppinioSwiperController controller = AppinioSwiperController();
+
+ List 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: [
- SwipeableCardsStack(
- cardController: cardController,
- 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;
- },
+ const SizedBox(
+ height: 50,
),
- Container(
- margin: const EdgeInsets.symmetric(vertical: 20.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- FloatingActionButton(
- child: const Icon(Icons.chevron_left),
- onPressed: () => cardController.triggerSwipeLeft(),
- ),
- FloatingActionButton(
- child: const Icon(Icons.chevron_right),
- onPressed: () => cardController.triggerSwipeRight(),
- ),
- FloatingActionButton(
- child: const Icon(Icons.arrow_upward),
- onPressed: () => cardController.triggerSwipeUp(),
- ),
- FloatingActionButton(
- child: const Icon(Icons.arrow_downward),
- onPressed: () => cardController.triggerSwipeDown(),
- ),
- ],
+ SizedBox(
+ height: MediaQuery.of(context).size.height * 0.75,
+ child: AppinioSwiper(
+ unlimitedUnswipe: true,
+ controller: controller,
+ unswipe: _unswipe,
+ cards: cards,
+ onSwipe: _swipe,
+ padding: const EdgeInsets.only(
+ left: 25,
+ right: 25,
+ top: 50,
+ bottom: 40,
+ ),
),
+ ),
+ 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");
+ }
+ }
}
diff --git a/example/pubspec.lock b/example/pubspec.lock
index c4ae96a..69ae0a3 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -1,6 +1,13 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ appinio_swiper:
+ dependency: "direct main"
+ description:
+ path: ".."
+ relative: true
+ source: path
+ version: "1.1.1"
characters:
dependency: transitive
description:
@@ -39,13 +46,6 @@ packages:
description: flutter
source: sdk
version: "0.0.99"
- swipeable_cards_stack:
- dependency: "direct main"
- description:
- path: ".."
- relative: true
- source: path
- version: "1.0.2+1"
vector_math:
dependency: transitive
description:
@@ -54,5 +54,5 @@ packages:
source: hosted
version: "2.1.2"
sdks:
- dart: ">=2.18.6 <3.0.0"
- flutter: ">=3.0.0"
+ dart: ">=2.17.0-0 <3.0.0"
+ flutter: ">=1.17.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 23e79c7..570ad85 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,17 +1,16 @@
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
-version: 1.0.0+1
+publish_to: 'none'
+version: 0.0.1
environment:
- sdk: '>=2.18.6 <3.0.0'
+ sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
-
- swipeable_cards_stack:
+ appinio_swiper:
path: ../
flutter:
diff --git a/images/angle.gif b/images/angle.gif
new file mode 100644
index 0000000..a013cb3
Binary files /dev/null and b/images/angle.gif differ
diff --git a/images/swipe-card.gif b/images/swipe-card.gif
deleted file mode 100644
index 0e8f8e3..0000000
Binary files a/images/swipe-card.gif and /dev/null differ
diff --git a/images/swipe_button.gif b/images/swipe_button.gif
new file mode 100644
index 0000000..8d12fa2
Binary files /dev/null and b/images/swipe_button.gif differ
diff --git a/images/swipe_gif.gif b/images/swipe_gif.gif
deleted file mode 100644
index 460d571..0000000
Binary files a/images/swipe_gif.gif and /dev/null differ
diff --git a/images/swipe_left_right.gif b/images/swipe_left_right.gif
new file mode 100644
index 0000000..176d435
Binary files /dev/null and b/images/swipe_left_right.gif differ
diff --git a/images/swiping.gif b/images/swiping.gif
new file mode 100644
index 0000000..60893ef
Binary files /dev/null and b/images/swiping.gif differ
diff --git a/images/treshold.gif b/images/treshold.gif
new file mode 100644
index 0000000..b39edcf
Binary files /dev/null and b/images/treshold.gif differ
diff --git a/images/unswipe.gif b/images/unswipe.gif
new file mode 100644
index 0000000..eb3ea86
Binary files /dev/null and b/images/unswipe.gif differ
diff --git a/lib/appinio_swiper.dart b/lib/appinio_swiper.dart
new file mode 100644
index 0000000..81f989c
--- /dev/null
+++ b/lib/appinio_swiper.dart
@@ -0,0 +1,600 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+class AppinioSwiper extends StatefulWidget {
+ /// list of widgets for the swiper
+ final List? 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
+ 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 _leftAnimation;
+ late Animation _topAnimation;
+ late Animation _scaleAnimation;
+ late Animation _differenceAnimation;
+ late Animation _unSwipeLeftAnimation;
+ late Animation _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 _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(
+ 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(
+ begin: _top,
+ end: _top + _top,
+ ).animate(_animationController);
+ _scaleAnimation = Tween(
+ begin: _scale,
+ end: 1.0,
+ ).animate(_animationController);
+ _differenceAnimation = Tween(
+ 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(
+ begin: _left,
+ end: _left + _left,
+ ).animate(_animationController);
+ _topAnimation = Tween(
+ 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(
+ begin: _scale,
+ end: 1.0,
+ ).animate(_animationController);
+ _differenceAnimation = Tween(
+ 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(
+ begin: _left,
+ end: 0,
+ ).animate(_animationController);
+ _topAnimation = Tween(
+ begin: _top,
+ end: 0,
+ ).animate(_animationController);
+ _scaleAnimation = Tween(
+ begin: _scale,
+ end: 0.9,
+ ).animate(_animationController);
+ _differenceAnimation = Tween(
+ 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(
+ begin: (card.swipedDirectionHorizontal == 1)
+ ? MediaQuery.of(context).size.width
+ : -MediaQuery.of(context).size.width,
+ end: 0,
+ ).animate(_animationController);
+ _unSwipeTopAnimation = Tween(
+ begin: (card.swipedDirectionVertical == 1)
+ ? -MediaQuery.of(context).size.height / 4
+ : MediaQuery.of(context).size.height / 4,
+ end: 0,
+ ).animate(_animationController);
+ _scaleAnimation = Tween(
+ begin: 1.0,
+ end: _scale,
+ ).animate(_animationController);
+ _differenceAnimation = Tween(
+ begin: 0,
+ end: _difference,
+ ).animate(_animationController);
+ }
+ //unSwipe vertical
+ if (card.vertical == true) {
+ _unSwipeLeftAnimation = Tween(
+ begin: (card.swipedDirectionHorizontal == 1)
+ ? MediaQuery.of(context).size.width / 4
+ : -MediaQuery.of(context).size.width / 4,
+ end: 0,
+ ).animate(_animationController);
+ _unSwipeTopAnimation = Tween(
+ begin: (card.swipedDirectionVertical == 1)
+ ? -MediaQuery.of(context).size.height
+ : MediaQuery.of(context).size.height,
+ end: 0,
+ ).animate(_animationController);
+ _scaleAnimation = Tween(
+ begin: 1.0,
+ end: _scale,
+ ).animate(_animationController);
+ _differenceAnimation = Tween(
+ 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 }
diff --git a/lib/src/cards_animation.dart b/lib/src/cards_animation.dart
deleted file mode 100644
index 2e34e94..0000000
--- a/lib/src/cards_animation.dart
+++ /dev/null
@@ -1,81 +0,0 @@
-import 'package:flutter/widgets.dart';
-
-import 'swipeable_cards_stack.dart';
-
-class CardsAnimation {
- static Animation 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 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 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 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 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),
- ),
- );
- }
- }
-}
diff --git a/lib/src/swipeable_cards_stack.dart b/lib/src/swipeable_cards_stack.dart
deleted file mode 100644
index 0f0f599..0000000
--- a/lib/src/swipeable_cards_stack.dart
+++ /dev/null
@@ -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 cardsAlign = [
- Alignment(0.0, 1.0),
- Alignment(0.0, 0.8),
- Alignment(0.0, 0.0)
-];
-final List 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 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 createState() => _SwipeableCardsStackState();
-}
-
-class _SwipeableCardsStackState extends State
- with SingleTickerProviderStateMixin {
- int cardsCounter = 0;
- int index = 0;
- Widget? appendCard;
-
- List 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: [
- 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();
- }
-}
diff --git a/lib/src/swipeable_cards_stack_controller.dart b/lib/src/swipeable_cards_stack_controller.dart
deleted file mode 100644
index 93fc0ed..0000000
--- a/lib/src/swipeable_cards_stack_controller.dart
+++ /dev/null
@@ -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);
- }
-}
diff --git a/lib/swipeable_cards_stack.dart b/lib/swipeable_cards_stack.dart
deleted file mode 100644
index e3ebebb..0000000
--- a/lib/swipeable_cards_stack.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-library swipeable_cards_stack;
-
-export 'src/swipeable_cards_stack.dart';
-export 'src/swipeable_cards_stack_controller.dart';
diff --git a/pubspec.lock b/pubspec.lock
index bfd2d97..1823cd5 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -66,7 +66,7 @@ packages:
name: lints
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.1"
+ version: "2.0.0"
matcher:
dependency: transitive
description:
@@ -150,5 +150,5 @@ packages:
source: hosted
version: "2.1.2"
sdks:
- dart: ">=2.17.0 <3.0.0"
- flutter: ">=3.0.0"
+ dart: ">=2.17.0-206.0.dev <3.0.0"
+ flutter: ">=1.17.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index c8d6816..f742391 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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.
homepage: https://github.com/ricardodalarme/swipeable-cards-stack
issue_tracker: https://github.com/ricardodalarme/swipeable-cards-stack/issues
-version: 1.0.2+1
+version: 2.0.0
environment:
sdk: ">=2.12.0 <3.0.0"
- flutter: ">=3.0.0"
+ flutter: ">=1.17.0"
dependencies:
flutter:
@@ -19,4 +19,4 @@ dev_dependencies:
screenshots:
- description: 'Example of the widget.'
- path: images/swipe-card.gif
+ path: images/swiping.gif
diff --git a/test/swipeable_cards_stack.dart b/test/appinio_swiper_test.dart
similarity index 100%
rename from test/swipeable_cards_stack.dart
rename to test/appinio_swiper_test.dart