From 50b2409ea0a455e0a7f981f21bd8c9dec4363b66 Mon Sep 17 00:00:00 2001 From: Nikhil Rajput Date: Sun, 8 Sep 2024 18:47:27 +0530 Subject: [PATCH] bumped to version 3.0.0 --- CHANGELOG.md | 216 ++--- README.md | 129 +-- example/lib/app_themes.dart | 2 +- example/lib/components/demo_item.dart | 49 ++ example/lib/components/slide.dart | 13 + example/lib/data/expandable_sliders.dart | 109 +++ example/lib/data/sliders.dart | 38 + example/lib/main.dart | 774 +----------------- example/lib/views/custom_indicator.dart | 66 ++ example/lib/views/enlarge.dart | 30 + example/lib/views/expandable.dart | 49 ++ example/lib/views/fullscreen.dart | 34 + example/lib/views/home.dart | 45 + example/lib/views/indicator_halo.dart | 103 +++ example/lib/views/manual.dart | 81 ++ example/lib/views/multiple_items.dart | 56 ++ example/lib/views/page_change_reason.dart | 73 ++ example/lib/views/standard.dart | 37 + lib/flutter_carousel_widget.dart | 13 +- lib/src/_expandable_carousel_widget.dart | 59 +- lib/src/_flutter_carousel_widget.dart | 57 +- .../expandable_carousel_controller.dart} | 60 +- .../flutter_carousel_controller.dart | 67 +- .../base_carousel_options.dart} | 101 +-- .../expandable_carousel_options.dart | 171 ++++ .../flutter_carousel_options.dart | 170 ++++ .../expandable_carousel_state.dart} | 9 +- .../flutter_carousel_state.dart | 15 +- lib/src/helpers/flutter_carousel_options.dart | 299 ------- .../flutter_expandable_carousel_options.dart | 300 ------- .../indicators/circular_slide_indicator.dart | 10 +- .../indicators/circular_static_indicator.dart | 3 + .../circular_wave_slide_indicator.dart | 5 +- .../indicators/sequential_fill_indicator.dart | 3 + lib/src/indicators/slide_indicator.dart | 1 - pubspec.yaml | 12 + test/flutter_carousel_widget/widget_test.dart | 97 --- .../integration_test.dart | 110 ++- .../unit_test.dart | 36 +- test/widget_test.dart | 391 +++++++++ 40 files changed, 2089 insertions(+), 1804 deletions(-) create mode 100644 example/lib/components/demo_item.dart create mode 100644 example/lib/components/slide.dart create mode 100644 example/lib/data/expandable_sliders.dart create mode 100644 example/lib/data/sliders.dart create mode 100644 example/lib/views/custom_indicator.dart create mode 100644 example/lib/views/enlarge.dart create mode 100644 example/lib/views/expandable.dart create mode 100644 example/lib/views/fullscreen.dart create mode 100644 example/lib/views/home.dart create mode 100644 example/lib/views/indicator_halo.dart create mode 100644 example/lib/views/manual.dart create mode 100644 example/lib/views/multiple_items.dart create mode 100644 example/lib/views/page_change_reason.dart create mode 100644 example/lib/views/standard.dart rename lib/src/{helpers/flutter_expandable_carousel_controller.dart => carousel_controller/expandable_carousel_controller.dart} (69%) rename lib/src/{helpers => carousel_controller}/flutter_carousel_controller.dart (66%) rename lib/src/{helpers/carousel_options.dart => carousel_options/base_carousel_options.dart} (77%) create mode 100644 lib/src/carousel_options/expandable_carousel_options.dart create mode 100644 lib/src/carousel_options/flutter_carousel_options.dart rename lib/src/{helpers/flutter_expandable_carousel_state.dart => carousel_state/expandable_carousel_state.dart} (83%) rename lib/src/{helpers => carousel_state}/flutter_carousel_state.dart (77%) delete mode 100644 lib/src/helpers/flutter_carousel_options.dart delete mode 100644 lib/src/helpers/flutter_expandable_carousel_options.dart delete mode 100644 test/flutter_carousel_widget/widget_test.dart rename test/{flutter_carousel_widget => }/integration_test.dart (70%) rename test/{flutter_carousel_widget => }/unit_test.dart (85%) create mode 100644 test/widget_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb0909..a815cb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,189 +1,205 @@ # Changelog +## 3.0.0 + +- **Breaking Change**: New `FlutterCarouselOptions` introduced for `FlutterCarousel`. +- **Breaking Change**: New `FlutterCarouselController` introduced for `FlutterCarousel`. +- **Breaking Change**: New `FlutterCarouselState` introduced for `FlutterCarousel`. +- **Breaking Change**: New `ExpandableCarouselOptions` introduced for `ExpandableCarousel`. +- **Breaking Change**: New `ExpandableCarouselController` introduced for `ExpandableCarousel`. +- **Breaking Change**: New `ExpandableCarouselState` introduced for `ExpandableCarousel`. +- **Documentation**: Updated the documentation to include breaking changes. +- **Fix**: `issue #52` fixed. +- **Fix**: `issue #51` fixed. +- **Fix**: `issue #50` fixed. +- **Fix**: `issue #48` fixed. +- **Fix**: `issue #38` fixed. +- **Fix**: `issue #37` fixed. +- **Improvement**: Performance improvements. ## 2.3.0 -* **Breaking Change**: New `ExpandableCarouselOptions` introduced for `ExpandableCarousel`. -* **Breaking Change**: New `ExpandableCarouselController` introduced for `ExpandableCarousel`. -* **Breaking Change**: New `ExpandableCarouselState` introduced for `ExpandableCarousel`. -* **Breaking Change**: All slide indicator properties have been consolidated into the `SlideIndicatorOptions` class. -* **Enhancement**: `issue #44` Added optional halo effect for slide indicators, customizable via `SlideIndicatorOptions` properties (`enableHalo`, `haloPadding`, `haloDecoration`). -* **Chore**: Removed dead code. -* **Fix**: `issue #46` fixed. -* **Fix**: `issue #44` fixed. -* **Fix**: `issue #40` fixed. +- **Breaking Change**: New `ExpandableCarouselOptions` introduced for `ExpandableCarousel`. +- **Breaking Change**: New `ExpandableCarouselController` introduced for `ExpandableCarousel`. +- **Breaking Change**: New `ExpandableCarouselState` introduced for `ExpandableCarousel`. +- **Breaking Change**: All slide indicator properties have been consolidated into the `SlideIndicatorOptions` class. +- **Enhancement**: `issue #44` Added optional halo effect for slide indicators, customizable via `SlideIndicatorOptions` properties (`enableHalo`, `haloPadding`, `haloDecoration`). +- **Chore**: Removed dead code. +- **Fix**: `issue #46` fixed. +- **Fix**: `issue #44` fixed. +- **Fix**: `issue #40` fixed. ## 2.2.0 -* **New Feature**: Support for custom slide indicators added. -* **Documentation**: Updated the documentation to show all contributors. -* **Documentation**: Updated the documentation to demonstrate the use of custom slide indicator. -* **Fix**: `issue #28` fixed. -* **Fix**: `issue #30` fixed. -* **Improvement**: Performance improvements. -* **Optimization**: Removed unnecessary codes. +- **New Feature**: Support for custom slide indicators added. +- **Documentation**: Updated the documentation to show all contributors. +- **Documentation**: Updated the documentation to demonstrate the use of custom slide indicator. +- **Fix**: `issue #28` fixed. +- **Fix**: `issue #30` fixed. +- **Improvement**: Performance improvements. +- **Optimization**: Removed unnecessary codes. ## 2.1.2 -* **Documentation**: Updated the documentation to display the screenshots. +- **Documentation**: Updated the documentation to display the screenshots. ## 2.1.1 -* **Documentation**: Updated the documentation to display the screenshots. +- **Documentation**: Updated the documentation to display the screenshots. ## 2.1.0 -* **Fix**: `issue #27` LICENSE updated to MIT. -* **Fix**: `issue #24` fixed. -* **Fix**: `issue #22` fixed. -* **Fix**: `issue #20` fixed. -* **Fix**: `issue #17` fixed. -* **Improvement**: Performance improvements. +- **Fix**: `issue #27` LICENSE updated to MIT. +- **Fix**: `issue #24` fixed. +- **Fix**: `issue #22` fixed. +- **Fix**: `issue #20` fixed. +- **Fix**: `issue #17` fixed. +- **Improvement**: Performance improvements. ## 2.0.4 -* **Optimization**: Removed unnecessary codes. +- **Optimization**: Removed unnecessary codes. ## 2.0.3 -* **Improvement**: Removed unnecessary dependencies. -* **Fix**: All known bug fixed and removed. -* **Improvement**: Performance improvements. -* **Optimization**: Removed unnecessary codes. +- **Improvement**: Removed unnecessary dependencies. +- **Fix**: All known bug fixed and removed. +- **Improvement**: Performance improvements. +- **Optimization**: Removed unnecessary codes. ## 2.0.2 -* **Add**: `indicatorMargin` property added to `CarouselOptions`. -* **Improvement**: Removed unnecessary dependencies. -* **Fix**: All known bug fixed and removed. -* **Improvement**: Performance improvements. -* **Optimization**: Removed unnecessary codes. +- **Add**: `indicatorMargin` property added to `CarouselOptions`. +- **Improvement**: Removed unnecessary dependencies. +- **Fix**: All known bug fixed and removed. +- **Improvement**: Performance improvements. +- **Optimization**: Removed unnecessary codes. ## 2.0.1 -* **Documentation**: Updated the documentation to reflect the new Github Pages deployment. -* **Fix**: All known bug fixed and removed. +- **Documentation**: Updated the documentation to reflect the new Github Pages deployment. +- **Fix**: All known bug fixed and removed. ## 2.0.0+1 -* **Fix**: Enums are now exported. -* **New Feature**: `ExpandableCarousel` widget added. -* **Breaking Change**: `scrollPhysics` is now `physics` in `CarouselOptions`. -* **Breaking Change**: `carouselController` is now `controller` in `CarouselOptions`. -* **Breaking Change**: Project architecture changed. -* **Fix**: All known bug fixed and removed. -* **Improvement**: Performance improvements. -* **Optimization**: Removed unnecessary codes. +- **Fix**: Enums are now exported. +- **New Feature**: `ExpandableCarousel` widget added. +- **Breaking Change**: `scrollPhysics` is now `physics` in `CarouselOptions`. +- **Breaking Change**: `carouselController` is now `controller` in `CarouselOptions`. +- **Breaking Change**: Project architecture changed. +- **Fix**: All known bug fixed and removed. +- **Improvement**: Performance improvements. +- **Optimization**: Removed unnecessary codes. ## 2.0.0 -* **New Feature**: `ExpandableCarousel` widget added. -* **Breaking Change**: `scrollPhysics` is now `physics` in `CarouselOptions`. -* **Breaking Change**: `carouselController` is now `controller` in `CarouselOptions`. -* **Breaking Change**: Project architecture changed. -* **Fix**: All known bug fixed and removed. -* **Improvement**: Performance improvements. -* **Optimization**: Removed unnecessary codes. +- **New Feature**: `ExpandableCarousel` widget added. +- **Breaking Change**: `scrollPhysics` is now `physics` in `CarouselOptions`. +- **Breaking Change**: `carouselController` is now `controller` in `CarouselOptions`. +- **Breaking Change**: Project architecture changed. +- **Fix**: All known bug fixed and removed. +- **Improvement**: Performance improvements. +- **Optimization**: Removed unnecessary codes. ## 1.2.3 -* **Fix**: bug auto play carousel is not working. -* All known bug fixed and removed. -* Performance improvements. -* Removed unnecessary codes. +- **Fix**: bug auto play carousel is not working. +- All known bug fixed and removed. +- Performance improvements. +- Removed unnecessary codes. ## 1.2.2 -* **Fix**: `issue #7` bug show indicator when no custom `CarouselController` is set and `showIndicator` is set to `true` and `onPageChanged` is called. -* Custom `CarouselController` can be set via `CarouselOptions` property. -* All known bugs are fixed. -* Performance improvements. +- **Fix**: `issue #7` bug show indicator when no custom `CarouselController` is set and `showIndicator` is set to `true` and `onPageChanged` is called. +- Custom `CarouselController` can be set via `CarouselOptions` property. +- All known bugs are fixed. +- Performance improvements. ## 1.2.1 -* Github Actions: Web Demo is now deployed to Github Pages -* Documentation: Updated the documentation to reflect the new Github Pages deployment +- Github Actions: Web Demo is now deployed to Github Pages +- Documentation: Updated the documentation to reflect the new Github Pages deployment ## 1.2.0 -* Project structure changed. -* Minor bug fixes. -* Removed unnecessary codes. +- Project structure changed. +- Minor bug fixes. +- Removed unnecessary codes. ## 1.1.0 -* Minor bug fixes. -* Performance improvements. +- Minor bug fixes. +- Performance improvements. ## 1.0.5 -* Minor bug fixes. -* Performance improvements. +- Minor bug fixes. +- Performance improvements. ## 1.0.4 -* Minor bug fixes. -* Performance improvements. +- Minor bug fixes. +- Performance improvements. ## 1.0.3 -* Minor bug fixes. -* Performance improvements. +- Minor bug fixes. +- Performance improvements. ## 1.0.2 -* Option to float Slide Indicator over Carousel or to show below Carousel added. -* Screenshots updated. -* Source code optimized. -* Performance improvements. +- Option to float Slide Indicator over Carousel or to show below Carousel added. +- Screenshots updated. +- Source code optimized. +- Performance improvements. ## 1.0.1 -* Web app example demo added. -* Indicator color will change according to System theme. -* Screenshots updated. -* Documentation updated. -* Source code optimized. -* Performance improvements. +- Web app example demo added. +- Indicator color will change according to System theme. +- Screenshots updated. +- Documentation updated. +- Source code optimized. +- Performance improvements. ## 1.0.0 -* Source code optimized. -* Bug fixes. -* Performance improvements. -* Documentation updated. +- Source code optimized. +- Bug fixes. +- Performance improvements. +- Documentation updated. ## 0.1.5 -* Default Slide Indicator added. -* Bug fixes. -* Performance improvements. +- Default Slide Indicator added. +- Bug fixes. +- Performance improvements. ## 0.1.4 -* Bug fixes. -* Documentation updated. +- Bug fixes. +- Documentation updated. ## 0.1.3 -* Minor bug fixes. +- Minor bug fixes. ## 0.1.2 -* Auto-sized child support. -* Bug fixes. +- Auto-sized child support. +- Bug fixes. ## 0.1.1 -* Bug fixes. -* Documentation updated. +- Bug fixes. +- Documentation updated. ## 0.1.0 -* Pre-built Carousel indicator support added. -* Bug fixes. -* Documentation updated. +- Pre-built Carousel indicator support added. +- Bug fixes. +- Documentation updated. ## 0.0.1 -* Initial Version. +- Initial Version. diff --git a/README.md b/README.md index ce34cc9..f9659b9 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,16 @@ A customizable carousel slider widget for Flutter, offering features such as inf [![GitHub last commit](https://img.shields.io/github/last-commit/nixrajput/flutter_carousel_widget?label=Last+Commit&style=flat)][repo] [![GitHub issues](https://img.shields.io/github/issues/nixrajput/flutter_carousel_widget?label=Issues&style=flat)][issues] [![GitHub pull requests](https://img.shields.io/github/issues-pr/nixrajput/flutter_carousel_widget?label=Pull+Requests&style=flat)][pulls] -[![GitHub Licence](https://img.shields.io/github/license/nixrajput/flutter_carousel_widget?label=Licence&style=flat)][license] +[![GitHub License](https://img.shields.io/github/license/nixrajput/flutter_carousel_widget?label=Licence&style=flat)][license] ## Table of Contents - [flutter\_carousel\_widget](#flutter_carousel_widget) - [Table of Contents](#table-of-contents) - [Features](#features) + - [Breaking Changes for v3.0.0](#breaking-changes-for-v300) + - [Separation of Carousel Options, Controller, and State](#separation-of-carousel-options-controller-and-state) + - [Impact](#impact) - [Demo](#demo) - [Click here to experience the demo in a Web App](#click-here-to-experience-the-demo-in-a-web-app) - [Installation](#installation) @@ -28,11 +31,13 @@ A customizable carousel slider widget for Flutter, offering features such as inf - [Carousel Options Customization](#carousel-options-customization) - [Build item widgets on demand](#build-item-widgets-on-demand) - [Carousel Controller](#carousel-controller) - - [`CarouselController` methods](#carouselcontroller-methods) + - [`FlutterCarouselController` methods](#fluttercarouselcontroller-methods) - [`.nextPage({Duration duration, Curve curve})`](#nextpageduration-duration-curve-curve) - [`.previousPage({Duration duration, Curve curve})`](#previouspageduration-duration-curve-curve) - [`.jumpToPage(int page)`](#jumptopageint-page) - [`.animateToPage(int page, {Duration duration, Curve curve})`](#animatetopageint-page-duration-duration-curve-curve) + - [Predefined Slide Indicators](#predefined-slide-indicators) + - [Slide Indicator Options Customization](#slide-indicator-options-customization) - [Custom Slide Indicators](#custom-slide-indicators) - [Contributing](#contributing) - [License](#license) @@ -51,6 +56,35 @@ A customizable carousel slider widget for Flutter, offering features such as inf - **Auto-sized Child Support:** Automatically adjust the size of the carousel items to fit their content. - **Enlarge Center Page:** The focused item can be enlarged. +## Breaking Changes for v3.0.0 + +In version 3.0.0 of the package, the following breaking changes have been introduced: + +### Separation of Carousel Options, Controller, and State + +- `FlutterCarousel` **Changes**: + - Previously used classes: + - CarouselOptions + - CarouselController + - CarouselState + - From v3.0.0, these classes have been replaced by: + - FlutterCarouselOptions + - FlutterCarouselController + - FlutterCarouselState +- `ExpandableCarousel` **Changes**: + - Previously used classes: + - CarouselOptions + - CarouselController + - CarouselState + - From v3.0.0, these classes have been replaced by: + - ExpandableCarouselOptions + - ExpandableCarouselController + - ExpandableCarouselState + +### Impact + +If you have been using CarouselOptions, CarouselController, and CarouselState for both FlutterCarousel and ExpandableCarousel, you will need to update your code to use the newly introduced classes specific to each carousel type. + ## Demo ![Demo](https://raw.githubusercontent.com/nixrajput/flutter_carousel_widget/master/screenshots/flutter_carousel_widget_demo.gif) @@ -76,8 +110,8 @@ Flutter Carousel is a carousel widget which supports infinite scrolling, auto sc ```dart FlutterCarousel( - options: CarouselOptions( - height: 400.0, + options: FlutterCarouselOptions( + height: 400.0, showIndicator: true, slideIndicator: CircularSlideIndicator(), ), @@ -104,7 +138,7 @@ Expandable Carousel is a carousel widget which automatically expands to the size ```dart ExpandableCarousel( - options: CarouselOptions( + options: ExpandableCarouselOptions( autoPlay: true, autoPlayInterval: const Duration(seconds: 2), ), @@ -130,7 +164,7 @@ ExpandableCarousel( ```dart FlutterCarousel( items: items, - options: CarouselOptions( + options: FlutterCarouselOptions( height: 400.0, // Sets the height of the carousel widget. @@ -229,18 +263,18 @@ ExpandableCarousel.builder( ## Carousel Controller -In order to manually control the pageview's position, you can create your own `CarouselController`, and pass it to `CarouselSlider`. Then you can use the `CarouselController` instance to manipulate the position. +In order to manually control the PageView's position, you can create your own `FlutterCarouselController`, and pass it to `FlutterCarouselOptions`. Then you can use the `FlutterCarouselController` instance to manipulate the position. ```dart class CarouselDemo extends StatelessWidget { - CarouselController buttonCarouselController = CarouselController(); + FlutterCarouselController buttonCarouselController = FlutterCarouselController(); @override Widget build(BuildContext context) => Column( children: [ FlutterCarousel( items: child, - options: CarouselOptions( + options: FlutterCarouselOptions( autoPlay: false, controller: buttonCarouselController, enlargeCenterPage: true, @@ -259,7 +293,7 @@ class CarouselDemo extends StatelessWidget { } ``` -### `CarouselController` methods +### `FlutterCarouselController` methods #### `.nextPage({Duration duration, Curve curve})` @@ -277,62 +311,61 @@ Jump to the given page. Animate to the given page. - ## Predefined Slide Indicators The `flutter_carousel_widget` package comes with a few [predefined slide indicators](https://github.com/nixrajput/flutter_carousel_widget/tree/master/lib/src/indicators) each with its own distinct behavior. To customize the slide indicators, you can pass an instance of `SlideIndicatorOptions` to the indicator you're using. ### Slide Indicator Options Customization -``` dart +```dart FlutterCarousel( - ... - options: CarouselOptions( - ... - slideIndicator: CircularSlideIndicator( - slideIndicatorOptions: SlideIndicatorOptions( - /// The alignment of the indicator. - alignment: Alignment.bottomCenter, + ... + options: FlutterCarouselOptions( + ... + slideIndicator: CircularSlideIndicator( + slideIndicatorOptions: SlideIndicatorOptions( + /// The alignment of the indicator. + alignment: Alignment.bottomCenter, - /// The color of the currently active item indicator. - currentIndicatorColor: Colors.white, + /// The color of the currently active item indicator. + currentIndicatorColor: Colors.white, - /// The background color of all inactive item indicators. - indicatorBackgroundColor: Colors.white.withOpacity(0.5), + /// The background color of all inactive item indicators. + indicatorBackgroundColor: Colors.white.withOpacity(0.5), - /// The border color of all item indicators. - indicatorBorderColor: Colors.white, + /// The border color of all item indicators. + indicatorBorderColor: Colors.white, - /// The border width of all item indicators. - indicatorBorderWidth: 1, + /// The border width of all item indicators. + indicatorBorderWidth: 1, - /// The radius of all item indicators. - indicatorRadius: 6, + /// The radius of all item indicators. + indicatorRadius: 6, - /// The spacing between each item indicator. - itemSpacing: 20, + /// The spacing between each item indicator. + itemSpacing: 20, - /// The padding of the indicator. - padding: const EdgeInsets.all(8.0), + /// The padding of the indicator. + padding: const EdgeInsets.all(8.0), - /// The decoration of the indicator halo. - haloDecoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(15.0)), - color: Colors.black.withOpacity(0.5)), + /// The decoration of the indicator halo. + haloDecoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(15.0)), + color: Colors.black.withOpacity(0.5)), - /// The padding of the indicator halo. - haloPadding: const EdgeInsets.all(8.0), + /// The padding of the indicator halo. + haloPadding: const EdgeInsets.all(8.0), - /// Whether to enable the indicator halo. - enableHalo: true, + /// Whether to enable the indicator halo. + enableHalo: true, - /// Whether to enable the animation. Only used in [CircularStaticIndicator] and [SequentialFillIndicator]. - enableAnimation: true), - ), - - ), - ); -``` + /// Whether to enable the animation. Only used in [CircularStaticIndicator] and [SequentialFillIndicator]. + enableAnimation: true, + ), + ), + ), + ); +``` ## Custom Slide Indicators diff --git a/example/lib/app_themes.dart b/example/lib/app_themes.dart index cb21bd9..c1bba48 100644 --- a/example/lib/app_themes.dart +++ b/example/lib/app_themes.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide CarouselController; import 'colors.dart'; diff --git a/example/lib/components/demo_item.dart b/example/lib/components/demo_item.dart new file mode 100644 index 0000000..fdda3ab --- /dev/null +++ b/example/lib/components/demo_item.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart' hide CarouselController; + +class DemoItem extends StatelessWidget { + const DemoItem(this.title, this.route, {Key? key}) : super(key: key); + + final String route; + final String title; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + Navigator.pushNamed(context, route); + }, + child: Card( + color: Colors.green, + margin: const EdgeInsets.only( + bottom: 16.0, + left: 16.0, + right: 16.0, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 16.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + title, + style: const TextStyle( + fontSize: 18.0, + color: Colors.white, + ), + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: Colors.white, + ) + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/components/slide.dart b/example/lib/components/slide.dart new file mode 100644 index 0000000..8ebca8f --- /dev/null +++ b/example/lib/components/slide.dart @@ -0,0 +1,13 @@ +import 'dart:ui'; + +class Slide { + Slide({ + required this.title, + required this.height, + required this.color, + }); + + final Color color; + final double height; + final String title; +} diff --git a/example/lib/data/expandable_sliders.dart b/example/lib/data/expandable_sliders.dart new file mode 100644 index 0000000..38ca797 --- /dev/null +++ b/example/lib/data/expandable_sliders.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart' hide CarouselController; + +final expandableSliders = [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + child: Container( + color: Colors.blue, + width: double.infinity, + height: 200, + child: const Center( + child: Text( + "Slide 1", + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + child: Container( + color: Colors.red, + width: double.infinity, + height: 300, + child: const Center( + child: Text( + "Slide 2", + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + child: Container( + color: Colors.yellow, + width: double.infinity, + height: 250, + child: const Center( + child: Text( + "Slide 3", + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + child: Container( + color: Colors.pink, + width: double.infinity, + height: 400, + child: const Center( + child: Text( + "Slide 4", + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + child: Container( + color: Colors.green, + width: double.infinity, + height: 350, + child: const Center( + child: Text( + "Slide 5", + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ) +]; diff --git a/example/lib/data/sliders.dart b/example/lib/data/sliders.dart new file mode 100644 index 0000000..e9398b4 --- /dev/null +++ b/example/lib/data/sliders.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +import '../components/slide.dart'; + +var slides = List.generate( + 6, + (index) => Slide( + title: 'Slide ${index + 1}', + height: 100.0 + index * 50, + color: Colors.primaries[index % Colors.primaries.length], + ), +); + +final List sliders = slides + .map( + (item) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + child: Container( + color: item.color, + width: double.infinity, + height: item.height, + child: Center( + child: Text( + item.title, + style: const TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ) + .toList(); diff --git a/example/lib/main.dart b/example/lib/main.dart index f76b83a..275c016 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,62 +1,24 @@ -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; +import 'package:flutter/material.dart'; import 'app_themes.dart'; +import 'views/custom_indicator.dart'; +import 'views/enlarge.dart'; +import 'views/expandable.dart'; +import 'views/fullscreen.dart'; +import 'views/home.dart'; +import 'views/indicator_halo.dart'; +import 'views/manual.dart'; +import 'views/multiple_items.dart'; +import 'views/page_change_reason.dart'; +import 'views/standard.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - runApp(const FlutterCarouselWidgetDemo()); + runApp(const FlutterCarouselDemo()); } -class Slide { - Slide({ - required this.title, - required this.height, - required this.color, - }); - - final Color color; - final double height; - final String title; -} - -var slides = List.generate( - 6, - (index) => Slide( - title: 'Slide ${index + 1}', - height: 100.0 + index * 50, - color: Colors.primaries[index % Colors.primaries.length], - ), -); - -final List sliders = slides - .map( - (item) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - child: Container( - color: item.color, - width: double.infinity, - height: item.height, - child: Center( - child: Text( - item.title, - style: const TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - ) - .toList(); - -class FlutterCarouselWidgetDemo extends StatelessWidget { - const FlutterCarouselWidgetDemo({Key? key}) : super(key: key); +class FlutterCarouselDemo extends StatelessWidget { + const FlutterCarouselDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -65,15 +27,15 @@ class FlutterCarouselWidgetDemo extends StatelessWidget { initialRoute: '/', routes: { '/': (ctx) => const CarouselDemoHome(), - '/complicated': (ctx) => const ComplicatedImageDemo(), - '/enlarge': (ctx) => const EnlargeStrategyDemo(), - '/manual': (ctx) => const ManuallyControlledSlider(), - '/fullscreen': (ctx) => const FullscreenSliderDemo(), - '/indicator_halo': (ctx) => const CarouselWithIndicatorHalo(), - '/indicator': (ctx) => const CarouselWithIndicatorDemo(), - '/multiple': (ctx) => const MultipleItemDemo(), + '/standard': (ctx) => const StandardCarouselDemo(), + '/enlarge': (ctx) => const EnlargeStrategyCarouselDemo(), + '/manual': (ctx) => const ManuallyControlledCarouselDemo(), + '/fullscreen': (ctx) => const FullscreenCarouselDemo(), + '/custom_indicator': (ctx) => const CustomIndicatorCarouselDemo(), + '/indicator_halo': (ctx) => const HaloIndicatorCarouselDemo(), + '/multiple_items': (ctx) => const MultipleItemsCarouselDemo(), '/expandable': (ctx) => const ExpandableCarouselDemo(), - '/page_changed_reason': (ctx) => const PageChangedReason(), + '/page_changed_reason': (ctx) => const PageChangedReasonCarouselDemo(), }, theme: AppThemes.lightTheme, darkTheme: AppThemes.darkTheme, @@ -81,695 +43,3 @@ class FlutterCarouselWidgetDemo extends StatelessWidget { ); } } - -class DemoItem extends StatelessWidget { - const DemoItem(this.title, this.route, {Key? key}) : super(key: key); - - final String route; - final String title; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () { - Navigator.pushNamed(context, route); - }, - child: Card( - color: Colors.green, - margin: const EdgeInsets.only( - bottom: 16.0, - left: 16.0, - right: 16.0, - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 16.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 18.0, - color: Colors.white, - ), - ), - const Icon( - Icons.arrow_forward_ios, - color: Colors.white, - ) - ], - ), - ), - ), - ); - } -} - -class CarouselDemoHome extends StatelessWidget { - const CarouselDemoHome({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text( - 'Carousel Demo', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 24.0, - ), - ), - centerTitle: true, - ), - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600.0), - child: ListView( - shrinkWrap: true, - children: const [ - SizedBox(height: 8.0), - DemoItem('Image Slider Demo', '/complicated'), - DemoItem('Enlarge Strategy Demo', '/enlarge'), - DemoItem('Manually Controlled Slider', '/manual'), - DemoItem('Fullscreen Carousel Slider', '/fullscreen'), - DemoItem('Carousel with Indicator Halo', '/indicator_halo'), - DemoItem('Carousel with Custom Indicator Demo', '/indicator'), - DemoItem('Multiple Item in One Screen Demo', '/multiple'), - DemoItem('Expandable Carousel Demo', '/expandable'), - DemoItem('Page Changed Reason Demo', "/page_changed_reason"), - ], - ), - ), - ), - ); - } -} - -class ComplicatedImageDemo extends StatelessWidget { - const ComplicatedImageDemo({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final deviceSize = MediaQuery.of(context).size; - return Scaffold( - appBar: AppBar(title: const Text('Image Slider Demo')), - body: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.width, - ), - child: FlutterCarousel( - options: CarouselOptions( - autoPlay: true, - autoPlayInterval: const Duration(seconds: 3), - disableCenter: true, - viewportFraction: deviceSize.width > 800.0 ? 0.8 : 1.0, - height: deviceSize.height * 0.45, - indicatorMargin: 12.0, - enableInfiniteScroll: true, - slideIndicator: const CircularSlideIndicator(), - initialPage: 2, - ), - items: sliders, - ), - ), - ), - ), - ); - } -} - -class EnlargeStrategyDemo extends StatelessWidget { - const EnlargeStrategyDemo({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Center Enlarge Strategy Demo')), - body: Center( - child: FlutterCarousel( - options: CarouselOptions( - autoPlay: true, - enableInfiniteScroll: true, - autoPlayInterval: const Duration(seconds: 3), - viewportFraction: 0.8, - enlargeCenterPage: true, - slideIndicator: CircularWaveSlideIndicator(), - floatingIndicator: false, - scrollDirection: Axis.vertical, - ), - items: sliders, - ), - ), - ); - } -} - -class PageChangedReason extends StatefulWidget { - const PageChangedReason({Key? key}) : super(key: key); - - @override - State createState() => _PageChangedReasonState(); -} - -class _PageChangedReasonState extends State { - CarouselController? controller = CarouselController(); - bool autoplay = false; - CarouselPageChangedReason? lastReason; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - elevation: 10, - title: const Text('Page Changed Reason'), - actions: [ - IconButton( - onPressed: () => setState(() { - autoplay = !autoplay; - print('autoplay toggled'); - }), - icon: Icon(autoplay ? Icons.pause : Icons.play_arrow), - ), - IconButton( - onPressed: () => setState(() { - controller = controller == null ? CarouselController() : null; - print('controller toggled'); - }), - icon: Icon( - controller == null ? Icons.gamepad_outlined : Icons.gamepad, - ), - ) - ], - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Last changed reason:\n${lastReason != null ? lastReason!.toString() : 'None'}'), - Expanded( - child: FlutterCarousel( - items: const [ - Padding( - padding: EdgeInsets.all(8.0), - child: Placeholder( - fallbackHeight: 200.0, - fallbackWidth: 200.0, - ), - ) - ], - options: CarouselOptions( - onPageChanged: (_, reason) => setState( - () { - lastReason = reason; - print(reason); - }, - ), - controller: controller, - enableInfiniteScroll: true, - autoPlay: autoplay, - ), - ), - ), - ], - ), - ), - ); - } -} - -class ManuallyControlledSlider extends StatefulWidget { - const ManuallyControlledSlider({Key? key}) : super(key: key); - - @override - State createState() { - return _ManuallyControlledSliderState(); - } -} - -class _ManuallyControlledSliderState extends State { - final CarouselController _controller = CarouselController(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Manually Controlled Slider')), - body: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: FlutterCarousel( - items: sliders, - options: CarouselOptions( - viewportFraction: 1.0, - autoPlay: false, - floatingIndicator: false, - enableInfiniteScroll: true, - initialPage: 3, - controller: _controller, - slideIndicator: CircularWaveSlideIndicator(), - ), - ), - ), - const SizedBox(height: 16.0), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 16.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: ElevatedButton( - onPressed: _controller.previousPage, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.arrow_back), - ), - ), - ), - Flexible( - child: ElevatedButton( - onPressed: _controller.nextPage, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.arrow_forward), - ), - ), - ), - ], - ), - ) - ], - ), - ), - ), - ); - } -} - -class FullscreenSliderDemo extends StatelessWidget { - const FullscreenSliderDemo({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Fullscreen Slider Demo')), - body: Center( - child: Builder( - builder: (context) { - final height = MediaQuery.of(context).size.height; - return FlutterCarousel( - options: CarouselOptions( - height: height, - viewportFraction: 1.0, - enlargeCenterPage: false, - autoPlay: true, - enableInfiniteScroll: true, - autoPlayInterval: const Duration(seconds: 2), - slideIndicator: CircularWaveSlideIndicator(), - ), - items: sliders, - ); - }, - ), - ), - ); - } -} - -class CarouselWithIndicatorHalo extends StatefulWidget { - const CarouselWithIndicatorHalo({Key? key}) : super(key: key); - - @override - State createState() => - _CarouselWithIndicatorHaloState(); -} - -class _CarouselWithIndicatorHaloState extends State { - bool useCustomIndicatorOptions = false; - - SlideIndicatorOptions defaultOptions = const SlideIndicatorOptions( - enableHalo: true, - ); - - SlideIndicatorOptions customOptions = const SlideIndicatorOptions( - enableHalo: true, - currentIndicatorColor: Colors.white, - haloDecoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Color(0xFF9B2BFF), - Color(0xFF2BFF88), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.all(Radius.circular(15.0)), - ), - ); - - @override - Widget build(BuildContext context) { - final deviceSize = MediaQuery.of(context).size; - - return Scaffold( - appBar: AppBar(title: const Text('Carousel with Indicator Halo')), - body: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const Spacer( - flex: 1, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton( - onPressed: () { - setState(() { - useCustomIndicatorOptions = false; - }); - }, - child: const Text("Default Halo")), - ElevatedButton( - onPressed: () { - setState(() { - useCustomIndicatorOptions = true; - }); - }, - child: const Text("Custom Halo")), - ], - ), - const SizedBox(height: 20), - Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.width, - ), - child: FlutterCarousel( - options: CarouselOptions( - autoPlay: true, - autoPlayInterval: const Duration(seconds: 3), - disableCenter: true, - viewportFraction: deviceSize.width > 800.0 ? 0.8 : 1.0, - height: deviceSize.height * 0.45, - indicatorMargin: 12.0, - enableInfiniteScroll: true, - slideIndicator: CircularSlideIndicator( - slideIndicatorOptions: useCustomIndicatorOptions - ? customOptions - : defaultOptions, - ), - initialPage: 2, - ), - items: sliders, - ), - ), - const Spacer( - flex: 5, - ) - ], - ), - ), - ), - ); - } -} - -class CarouselWithIndicatorDemo extends StatefulWidget { - const CarouselWithIndicatorDemo({Key? key}) : super(key: key); - - @override - State createState() { - return _CarouselWithIndicatorState(); - } -} - -class _CarouselWithIndicatorState extends State { - int _current = 0; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Custom Indicator Demo')), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - FlutterCarousel( - items: sliders, - options: CarouselOptions( - autoPlay: true, - autoPlayInterval: const Duration(seconds: 4), - viewportFraction: 1.0, - initialPage: 2, - showIndicator: false, - height: 400.0, - onPageChanged: (int index, CarouselPageChangedReason reason) { - setState(() { - _current = index; - }); - }), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: slides.asMap().entries.map((entry) { - return Container( - width: 12.0, - height: 12.0, - margin: const EdgeInsets.symmetric( - vertical: 8.0, horizontal: 4.0), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: (Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black) - .withOpacity(_current == entry.key ? 0.9 : 0.4), - ), - ); - }).toList(), - ), - ], - ), - ), - ); - } -} - -class MultipleItemDemo extends StatelessWidget { - const MultipleItemDemo({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Multiple Item in One Slide Demo')), - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 800.0), - child: FlutterCarousel.builder( - options: CarouselOptions( - aspectRatio: 2.0, - enlargeCenterPage: false, - viewportFraction: 1, - showIndicator: true, - enableInfiniteScroll: true, - autoPlayInterval: const Duration(seconds: 2), - autoPlay: true, - slideIndicator: CircularStaticIndicator(), - ), - itemCount: (slides.length / 2).round(), - itemBuilder: (context, index, realIdx) { - final first = index * 2; - final second = first + 1; - return Row( - children: [first, second].map((idx) { - return Expanded( - flex: 1, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - child: Container( - color: slides[idx].color, - height: slides[idx].height, - child: Center( - child: Text(slides[idx].title), - ), - ), - ), - ); - }).toList(), - ); - }, - ), - ), - ), - ); - } -} - -final expandableItems = [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - child: Container( - color: Colors.blue, - width: double.infinity, - height: 200, - child: const Center( - child: Text( - "Slide 1", - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - child: Container( - color: Colors.red, - width: double.infinity, - height: 300, - child: const Center( - child: Text( - "Slide 2", - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - child: Container( - color: Colors.yellow, - width: double.infinity, - height: 250, - child: const Center( - child: Text( - "Slide 3", - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - child: Container( - color: Colors.pink, - width: double.infinity, - height: 400, - child: const Center( - child: Text( - "Slide 4", - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - child: Container( - color: Colors.green, - width: double.infinity, - height: 350, - child: const Center( - child: Text( - "Slide 5", - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ) -]; - -class ExpandableCarouselDemo extends StatefulWidget { - const ExpandableCarouselDemo({Key? key}) : super(key: key); - - @override - State createState() => _ExpandableCarouselDemoState(); -} - -class _ExpandableCarouselDemoState extends State { - final ExpandableCarouselController _controller = - ExpandableCarouselController(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Expandable Carousel Demo')), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - ExpandableCarousel.builder( - options: ExpandableCarouselOptions( - // viewportFraction: 1.0, - enableInfiniteScroll: true, - enlargeCenterPage: true, - initialPage: 1, - autoPlay: true, - controller: _controller, - //showIndicator: false, - floatingIndicator: false, - restorationId: 'expandable_carousel', - ), - itemCount: expandableItems.length, - itemBuilder: (context, index, realIdx) { - return expandableItems[index]; - }, - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/views/custom_indicator.dart b/example/lib/views/custom_indicator.dart new file mode 100644 index 0000000..508cbc9 --- /dev/null +++ b/example/lib/views/custom_indicator.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class CustomIndicatorCarouselDemo extends StatefulWidget { + const CustomIndicatorCarouselDemo({Key? key}) : super(key: key); + + @override + State createState() { + return _CarouselWithIndicatorState(); + } +} + +class _CarouselWithIndicatorState extends State { + int _current = 3; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Carousel with Custom Indicator Demo')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + FlutterCarousel( + items: sliders, + options: FlutterCarouselOptions( + autoPlay: true, + autoPlayInterval: const Duration(seconds: 4), + viewportFraction: 1.0, + initialPage: _current, + showIndicator: false, + height: 400.0, + onPageChanged: (int index, CarouselPageChangedReason reason) { + setState(() { + _current = index; + }); + }), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: slides.asMap().entries.map((entry) { + return Container( + width: 12.0, + height: 12.0, + margin: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 4.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: (Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black) + .withOpacity(_current == entry.key ? 0.9 : 0.4), + ), + ); + }).toList(), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/views/enlarge.dart b/example/lib/views/enlarge.dart new file mode 100644 index 0000000..1547320 --- /dev/null +++ b/example/lib/views/enlarge.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class EnlargeStrategyCarouselDemo extends StatelessWidget { + const EnlargeStrategyCarouselDemo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Enlarge Strategy Carousel Demo')), + body: Center( + child: FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + enableInfiniteScroll: true, + autoPlayInterval: const Duration(seconds: 3), + viewportFraction: 0.8, + enlargeCenterPage: true, + slideIndicator: CircularWaveSlideIndicator(), + floatingIndicator: false, + scrollDirection: Axis.vertical, + ), + items: sliders, + ), + ), + ); + } +} diff --git a/example/lib/views/expandable.dart b/example/lib/views/expandable.dart new file mode 100644 index 0000000..05c63ab --- /dev/null +++ b/example/lib/views/expandable.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart' hide CarouselController; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/expandable_sliders.dart'; + +class ExpandableCarouselDemo extends StatefulWidget { + const ExpandableCarouselDemo({Key? key}) : super(key: key); + + @override + State createState() => _ExpandableCarouselDemoState(); +} + +class _ExpandableCarouselDemoState extends State { + final ExpandableCarouselController _controller = + ExpandableCarouselController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Expandable Carousel Demo')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ExpandableCarousel.builder( + options: ExpandableCarouselOptions( + // viewportFraction: 1.0, + enableInfiniteScroll: true, + // enlargeCenterPage: true, + // initialPage: 1, + autoPlay: true, + controller: _controller, + //showIndicator: false, + floatingIndicator: false, + restorationId: 'expandable_carousel', + ), + itemCount: expandableSliders.length, + itemBuilder: (context, index, realIdx) { + return expandableSliders[index]; + }, + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/views/fullscreen.dart b/example/lib/views/fullscreen.dart new file mode 100644 index 0000000..36ebde6 --- /dev/null +++ b/example/lib/views/fullscreen.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class FullscreenCarouselDemo extends StatelessWidget { + const FullscreenCarouselDemo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Fullscreen Carousel Demo')), + body: Center( + child: Builder( + builder: (context) { + final height = MediaQuery.of(context).size.height; + return FlutterCarousel( + options: FlutterCarouselOptions( + height: height, + viewportFraction: 1.0, + enlargeCenterPage: false, + autoPlay: true, + enableInfiniteScroll: true, + autoPlayInterval: const Duration(seconds: 5), + slideIndicator: CircularWaveSlideIndicator(), + ), + items: sliders, + ); + }, + ), + ), + ); + } +} diff --git a/example/lib/views/home.dart b/example/lib/views/home.dart new file mode 100644 index 0000000..43b9e00 --- /dev/null +++ b/example/lib/views/home.dart @@ -0,0 +1,45 @@ +import 'package:example/components/demo_item.dart'; +import 'package:flutter/material.dart'; + +class CarouselDemoHome extends StatelessWidget { + const CarouselDemoHome({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + 'Carousel Demo', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24.0, + ), + ), + centerTitle: true, + ), + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600.0), + child: ListView( + shrinkWrap: true, + children: const [ + SizedBox(height: 8.0), + DemoItem('Standard Carousel Demo', '/standard'), + DemoItem('Enlarge Strategy Carousel Demo', '/enlarge'), + DemoItem('Manually Controlled Carousel Demo', '/manual'), + DemoItem('Fullscreen Carousel Demo', '/fullscreen'), + DemoItem( + 'Carousel with Custom Indicator Demo', '/custom_indicator'), + DemoItem('Carousel with Indicator Halo Demo', '/indicator_halo'), + DemoItem('Multiple Items in One Screen Carousel Demo', + '/multiple_items'), + DemoItem('Expandable Carousel Demo', '/expandable'), + DemoItem( + 'Page Changed Reason Carousel Demo', '/page_changed_reason'), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/views/indicator_halo.dart b/example/lib/views/indicator_halo.dart new file mode 100644 index 0000000..e9143b1 --- /dev/null +++ b/example/lib/views/indicator_halo.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class HaloIndicatorCarouselDemo extends StatefulWidget { + const HaloIndicatorCarouselDemo({Key? key}) : super(key: key); + + @override + State createState() => + _HaloIndicatorCarouselDemoState(); +} + +class _HaloIndicatorCarouselDemoState extends State { + bool useCustomIndicatorOptions = false; + + SlideIndicatorOptions defaultOptions = const SlideIndicatorOptions( + enableHalo: true, + ); + + SlideIndicatorOptions customOptions = const SlideIndicatorOptions( + enableHalo: true, + currentIndicatorColor: Colors.white, + haloDecoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFF9B2BFF), + Color(0xFF2BFF88), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.all(Radius.circular(15.0)), + ), + ); + + @override + Widget build(BuildContext context) { + final deviceSize = MediaQuery.of(context).size; + + return Scaffold( + appBar: AppBar(title: const Text('Carousel with Indicator Halo Demo')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + const Spacer( + flex: 1, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + setState(() { + useCustomIndicatorOptions = false; + }); + }, + child: const Text("Default Halo")), + ElevatedButton( + onPressed: () { + setState(() { + useCustomIndicatorOptions = true; + }); + }, + child: const Text("Custom Halo")), + ], + ), + const SizedBox(height: 20), + Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.width, + ), + child: FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + autoPlayInterval: const Duration(seconds: 3), + disableCenter: true, + viewportFraction: deviceSize.width > 800.0 ? 0.8 : 1.0, + height: deviceSize.height * 0.45, + indicatorMargin: 12.0, + enableInfiniteScroll: true, + slideIndicator: CircularSlideIndicator( + slideIndicatorOptions: useCustomIndicatorOptions + ? customOptions + : defaultOptions, + ), + initialPage: 2, + ), + items: sliders, + ), + ), + const Spacer( + flex: 5, + ) + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/views/manual.dart b/example/lib/views/manual.dart new file mode 100644 index 0000000..fe3dd1b --- /dev/null +++ b/example/lib/views/manual.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class ManuallyControlledCarouselDemo extends StatefulWidget { + const ManuallyControlledCarouselDemo({Key? key}) : super(key: key); + + @override + State createState() { + return _ManuallyControlledCarouselDemoState(); + } +} + +class _ManuallyControlledCarouselDemoState + extends State { + final FlutterCarouselController _controller = FlutterCarouselController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Manually Controlled Carousel Demo')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: FlutterCarousel( + items: sliders, + options: FlutterCarouselOptions( + viewportFraction: 1.0, + autoPlay: false, + floatingIndicator: false, + enableInfiniteScroll: true, + initialPage: 3, + controller: _controller, + slideIndicator: CircularWaveSlideIndicator(), + ), + ), + ), + const SizedBox(height: 16.0), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 16.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: ElevatedButton( + onPressed: _controller.previousPage, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.arrow_back), + ), + ), + ), + Flexible( + child: ElevatedButton( + onPressed: _controller.nextPage, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.arrow_forward), + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/views/multiple_items.dart b/example/lib/views/multiple_items.dart new file mode 100644 index 0000000..bcbe25a --- /dev/null +++ b/example/lib/views/multiple_items.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class MultipleItemsCarouselDemo extends StatelessWidget { + const MultipleItemsCarouselDemo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Multiple Items in One Screen Carousel Demo')), + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800.0), + child: FlutterCarousel.builder( + options: FlutterCarouselOptions( + height: 400, + aspectRatio: 2.0, + enlargeCenterPage: false, + viewportFraction: 1, + showIndicator: true, + enableInfiniteScroll: true, + autoPlayInterval: const Duration(seconds: 2), + autoPlay: true, + slideIndicator: CircularStaticIndicator(), + ), + itemCount: (slides.length / 2).round(), + itemBuilder: (context, index, realIdx) { + final first = index * 2; + final second = first + 1; + return Row( + children: [first, second].map((idx) { + return Expanded( + flex: 1, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + child: Container( + color: slides[idx].color, + height: slides[idx].height, + child: Center( + child: Text(slides[idx].title), + ), + ), + ), + ); + }).toList(), + ); + }, + ), + ), + ), + ); + } +} diff --git a/example/lib/views/page_change_reason.dart b/example/lib/views/page_change_reason.dart new file mode 100644 index 0000000..ac6acec --- /dev/null +++ b/example/lib/views/page_change_reason.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +import '../data/sliders.dart'; + +class PageChangedReasonCarouselDemo extends StatefulWidget { + const PageChangedReasonCarouselDemo({Key? key}) : super(key: key); + + @override + State createState() => + _PageChangedReasonCarouselDemoState(); +} + +class _PageChangedReasonCarouselDemoState + extends State { + FlutterCarouselController? controller = FlutterCarouselController(); + bool autoplay = false; + CarouselPageChangedReason? lastReason; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 10, + title: const Text('Page Changed Reason Carousel Demo'), + actions: [ + IconButton( + onPressed: () => setState(() { + autoplay = !autoplay; + print('autoplay toggled'); + }), + icon: Icon(autoplay ? Icons.pause : Icons.play_arrow), + ), + IconButton( + onPressed: () => setState(() { + controller = + controller == null ? FlutterCarouselController() : null; + print('controller toggled'); + }), + icon: Icon( + controller == null ? Icons.gamepad_outlined : Icons.gamepad, + ), + ) + ], + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Last changed reason:\n${lastReason != null ? lastReason!.toString() : 'None'}'), + Expanded( + child: FlutterCarousel( + items: sliders, + options: FlutterCarouselOptions( + onPageChanged: (_, reason) => setState( + () { + lastReason = reason; + print(reason); + }, + ), + controller: controller, + enableInfiniteScroll: true, + autoPlay: autoplay, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/views/standard.dart b/example/lib/views/standard.dart new file mode 100644 index 0000000..960a236 --- /dev/null +++ b/example/lib/views/standard.dart @@ -0,0 +1,37 @@ +import 'package:example/data/sliders.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; + +class StandardCarouselDemo extends StatelessWidget { + const StandardCarouselDemo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final deviceSize = MediaQuery.of(context).size; + return Scaffold( + appBar: AppBar(title: const Text('Standard Carousel Demo')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + constraints: BoxConstraints(maxHeight: deviceSize.width), + child: FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + autoPlayInterval: const Duration(seconds: 3), + height: deviceSize.height, + viewportFraction: 1.0, + indicatorMargin: 12.0, + enableInfiniteScroll: true, + slideIndicator: CircularSlideIndicator(), + initialPage: 2, + reverse: true, + ), + items: sliders, + ), + ), + ), + ), + ); + } +} diff --git a/lib/flutter_carousel_widget.dart b/lib/flutter_carousel_widget.dart index eb5cf3d..ef1055c 100644 --- a/lib/flutter_carousel_widget.dart +++ b/lib/flutter_carousel_widget.dart @@ -1,13 +1,14 @@ export 'package:flutter_carousel_widget/src/_expandable_carousel_widget.dart'; export 'package:flutter_carousel_widget/src/_flutter_carousel_widget.dart'; +export 'package:flutter_carousel_widget/src/carousel_controller/expandable_carousel_controller.dart'; +export 'package:flutter_carousel_widget/src/carousel_controller/flutter_carousel_controller.dart'; +export 'package:flutter_carousel_widget/src/carousel_options/base_carousel_options.dart'; +export 'package:flutter_carousel_widget/src/carousel_options/expandable_carousel_options.dart'; +export 'package:flutter_carousel_widget/src/carousel_options/flutter_carousel_options.dart'; +export 'package:flutter_carousel_widget/src/carousel_state/expandable_carousel_state.dart'; +export 'package:flutter_carousel_widget/src/carousel_state/flutter_carousel_state.dart'; export 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; export 'package:flutter_carousel_widget/src/enums/center_page_enlarge_strategy.dart'; -export 'package:flutter_carousel_widget/src/helpers/flutter_carousel_controller.dart'; -export 'package:flutter_carousel_widget/src/helpers/flutter_carousel_options.dart'; -export 'package:flutter_carousel_widget/src/helpers/flutter_carousel_state.dart'; -export 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_controller.dart'; -export 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_options.dart'; -export 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_state.dart'; export 'package:flutter_carousel_widget/src/indicators/circular_slide_indicator.dart'; export 'package:flutter_carousel_widget/src/indicators/circular_static_indicator.dart'; export 'package:flutter_carousel_widget/src/indicators/circular_wave_slide_indicator.dart'; diff --git a/lib/src/_expandable_carousel_widget.dart b/lib/src/_expandable_carousel_widget.dart index 8c73b1b..66d0255 100644 --- a/lib/src/_expandable_carousel_widget.dart +++ b/lib/src/_expandable_carousel_widget.dart @@ -3,15 +3,17 @@ library flutter_carousel_widget; import 'dart:async'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/components/overflow_page.dart'; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/enums/center_page_enlarge_strategy.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_controller.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_options.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_state.dart'; -import 'package:flutter_carousel_widget/src/typedefs/widget_builder.dart'; -import 'package:flutter_carousel_widget/src/utils/flutter_carousel_utils.dart'; +import 'package:flutter/material.dart'; + +import 'carousel_controller/expandable_carousel_controller.dart'; +import 'carousel_options/expandable_carousel_options.dart'; +import 'carousel_state/expandable_carousel_state.dart'; +import 'components/overflow_page.dart'; +import 'enums/carousel_page_changed_reason.dart'; +import 'enums/center_page_enlarge_strategy.dart'; +import 'indicators/circular_slide_indicator.dart'; +import 'typedefs/widget_builder.dart'; +import 'utils/flutter_carousel_utils.dart'; /// Main carousel widget /// There are two constructors - one for direct list of items and another for builder pattern (itemBuilder). @@ -59,11 +61,11 @@ class ExpandableCarousel extends StatefulWidget { final ExpandableCarouselOptions options; @override - ExpandableCarouselWidgetState createState() => - ExpandableCarouselWidgetState(); + _ExpandableCarouselWidgetState createState() => + _ExpandableCarouselWidgetState(); } -class ExpandableCarouselWidgetState extends State +class _ExpandableCarouselWidgetState extends State with TickerProviderStateMixin { /// mode is related to why the page is being changed CarouselPageChangedReason changeReasonMode = @@ -96,6 +98,9 @@ class ExpandableCarouselWidgetState extends State /// Timer to manage auto-play functionality Timer? _timer; + /// Default Slide Indicator Key + final _defaultIndicatorKey = const ValueKey('default_indicator'); + /// Retrieve options for the carousel ExpandableCarouselOptions get options => widget.options; @@ -250,13 +255,18 @@ class ExpandableCarouselWidgetState extends State return Timer.periodic(widget.options.autoPlayInterval, (_) { final route = ModalRoute.of(context); if (route?.isCurrent == false) { - return; + return; // Pause auto-play if the route is not active } + // Temporarily store the previous change reason var previousReason = changeReasonMode; + // Set change reason to timed _changeMode(CarouselPageChangedReason.timed); - var nextPage = _carouselState!.pageController!.page!.round() + 1; + // Calculate the next page index for auto-play + var nextPage = widget.options.reverse + ? _carouselState!.pageController!.page!.round() - 1 + : _carouselState!.pageController!.page!.round() + 1; var itemCount = widget.itemCount ?? widget.items?.length ?? 0; // Reset to the first page if at the end of the carousel and infinite scroll is disabled @@ -308,8 +318,11 @@ class ExpandableCarouselWidgetState extends State /// Prepare the sizes for items in the carousel List _prepareSizes() { return isBuilder - ? List.filled(widget.itemCount!, widget.options.estimatedPageSize) - : widget.items!.map((_) => widget.options.estimatedPageSize).toList(); + ? List.filled( + widget.itemCount!, widget.options.estimatedPageSize ?? 0.0) + : widget.items! + .map((_) => widget.options.estimatedPageSize ?? 0.0) + .toList(); } /// Check if sizes need to be reinitialized when the widget is updated @@ -563,7 +576,15 @@ class ExpandableCarouselWidgetState extends State /// to show the current position within the carousel. It uses the [_currentPage] and /// [_pageDelta] values to determine the indicator's position and animation state. Widget _buildSlideIndicator() { - return widget.options.slideIndicator!.build( + if (widget.options.slideIndicator != null) { + return widget.options.slideIndicator!.build( + _currentPage, + _pageDelta, + widget.itemCount!, + ); + } + + return CircularSlideIndicator(key: _defaultIndicatorKey).build( _currentPage, _pageDelta, widget.itemCount!, @@ -576,9 +597,7 @@ class ExpandableCarouselWidgetState extends State /// or below the carousel depending on the [floatingIndicator] option. Widget _buildWidget(BuildContext context) { // If `showIndicator` option is true - if (widget.options.showIndicator && - widget.options.slideIndicator != null && - widget.itemCount! > 1) { + if (widget.options.showIndicator && widget.itemCount! > 1) { // If `floatingIndicator` option is true if (widget.options.floatingIndicator) { return _getGestureWrapper( diff --git a/lib/src/_flutter_carousel_widget.dart b/lib/src/_flutter_carousel_widget.dart index 40a2e4f..bef2c4b 100644 --- a/lib/src/_flutter_carousel_widget.dart +++ b/lib/src/_flutter_carousel_widget.dart @@ -3,14 +3,16 @@ library flutter_carousel_widget; import 'dart:async'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/enums/center_page_enlarge_strategy.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_controller.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_options.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_state.dart'; -import 'package:flutter_carousel_widget/src/typedefs/widget_builder.dart'; -import 'package:flutter_carousel_widget/src/utils/flutter_carousel_utils.dart'; +import 'package:flutter/material.dart'; + +import 'carousel_controller/flutter_carousel_controller.dart'; +import 'carousel_options/flutter_carousel_options.dart'; +import 'carousel_state/flutter_carousel_state.dart'; +import 'enums/carousel_page_changed_reason.dart'; +import 'enums/center_page_enlarge_strategy.dart'; +import 'indicators/circular_slide_indicator.dart'; +import 'typedefs/widget_builder.dart'; +import 'utils/flutter_carousel_utils.dart'; /// Main carousel widget /// There are two constructors - one for direct list of items and another for builder pattern (itemBuilder). @@ -53,22 +55,22 @@ class FlutterCarousel extends StatefulWidget { final List? items; /// Configuration options for the carousel - final CarouselOptions options; + final FlutterCarouselOptions options; @override - FlutterCarouselState createState() => FlutterCarouselState(); + _FlutterCarouselState createState() => _FlutterCarouselState(); } /// State class for the carousel widget /// This class handles the internal state of the carousel, including page transitions, autoplay, gestures, and more. -class FlutterCarouselState extends State +class _FlutterCarouselState extends State with TickerProviderStateMixin { /// Enum to represent why the page is changing (e.g., user input, auto-play, etc.) CarouselPageChangedReason changeReasonMode = CarouselPageChangedReason.controller; /// Carousel state to manage internal state like page controller, item count, etc. - CarouselState? _carouselState; + FlutterCarouselState? _carouselState; /// Page controller to manage page views and transitions PageController? _pageController; @@ -82,14 +84,17 @@ class FlutterCarouselState extends State /// Timer to manage auto-play functionality Timer? _timer; + /// Default Slide Indicator Key + final _defaultIndicatorKey = const ValueKey('default_indicator'); + /// Retrieve the carousel controller, or create a new one if not provided - CarouselControllerImpl get carouselController => + FlutterCarouselControllerImpl get carouselController => widget.options.controller != null - ? widget.options.controller as CarouselControllerImpl - : CarouselController() as CarouselControllerImpl; + ? widget.options.controller as FlutterCarouselControllerImpl + : FlutterCarouselController() as FlutterCarouselControllerImpl; /// Retrieve options for the carousel - CarouselOptions get options => widget.options; + FlutterCarouselOptions get options => widget.options; /// Update the current page index and delta for smooth animations void _changeIndexPageDelta() { @@ -164,7 +169,7 @@ class FlutterCarouselState extends State /// Initialize the carousel state void _initCarouselState() { // Initialize carousel state with options and callbacks for timer handling - _carouselState = CarouselState( + _carouselState = FlutterCarouselState( options, _clearTimer, _resumeTimer, @@ -218,7 +223,9 @@ class FlutterCarouselState extends State _changeMode(CarouselPageChangedReason.timed); // Calculate the next page index for auto-play - var nextPage = _carouselState!.pageController!.page!.round() + 1; + var nextPage = widget.options.reverse + ? _carouselState!.pageController!.page!.round() - 1 + : _carouselState!.pageController!.page!.round() + 1; var itemCount = widget.itemCount ?? widget.items?.length ?? 0; // Reset to the first page if at the end of the carousel and infinite scroll is disabled @@ -494,7 +501,15 @@ class FlutterCarouselState extends State /// to show the current position within the carousel. It uses the [_currentPage] and /// [_pageDelta] values to determine the indicator's position and animation state. Widget _buildSlideIndicator() { - return widget.options.slideIndicator!.build( + if (widget.options.slideIndicator != null) { + return widget.options.slideIndicator!.build( + _currentPage, + _pageDelta, + widget.itemCount!, + ); + } + + return CircularSlideIndicator(key: _defaultIndicatorKey).build( _currentPage, _pageDelta, widget.itemCount!, @@ -507,9 +522,7 @@ class FlutterCarouselState extends State /// or below the carousel depending on the [floatingIndicator] option. Widget _buildWidget(BuildContext context) { // If `showIndicator` option is true - if (widget.options.showIndicator && - widget.options.slideIndicator != null && - widget.itemCount! > 1) { + if (widget.options.showIndicator && widget.itemCount! > 1) { // If `floatingIndicator` option is true if (widget.options.floatingIndicator) { return _getGestureWrapper( diff --git a/lib/src/helpers/flutter_expandable_carousel_controller.dart b/lib/src/carousel_controller/expandable_carousel_controller.dart similarity index 69% rename from lib/src/helpers/flutter_expandable_carousel_controller.dart rename to lib/src/carousel_controller/expandable_carousel_controller.dart index c6fa453..24655c9 100644 --- a/lib/src/helpers/flutter_expandable_carousel_controller.dart +++ b/lib/src/carousel_controller/expandable_carousel_controller.dart @@ -1,9 +1,10 @@ import 'dart:async'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_state.dart'; -import 'package:flutter_carousel_widget/src/utils/flutter_carousel_utils.dart'; +import 'package:flutter/animation.dart'; + +import '../carousel_state/expandable_carousel_state.dart'; +import '../enums/carousel_page_changed_reason.dart'; +import '../utils/flutter_carousel_utils.dart'; abstract class ExpandableCarouselController { factory ExpandableCarouselController() => ExpandableCarouselControllerImpl(); @@ -29,8 +30,7 @@ class ExpandableCarouselControllerImpl implements ExpandableCarouselController { final Completer _readyCompleter = Completer(); ExpandableCarouselState? _state; - /// Animates the controlled [FlutterCarouselWidget] from the current page to the given page. - /// + /// Animates the controlled [ExpandableCarousel] from the current page to the given page. /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. @override @@ -38,37 +38,40 @@ class ExpandableCarouselControllerImpl implements ExpandableCarouselController { {Duration? duration = const Duration(milliseconds: 300), Curve? curve = Curves.linear}) async { final isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { _state!.onResetTimer(); } - final index = getRealIndex(_state!.pageController!.page!.toInt(), + + final index = getRealIndex(_state!.pageController?.page!.toInt() ?? 0, _state!.realPage - _state!.initialPage, _state!.itemCount); _setModeController(); + await _state!.pageController!.animateToPage( - _state!.pageController!.page!.toInt() + page - index, + (_state!.pageController?.page!.toInt() ?? 0) + page - index, duration: duration!, curve: curve!); + if (isNeedResetTimer) { _state!.onResumeTimer(); } } - /// Changes which page is displayed in the controlled [FlutterCarouselWidget]. - /// + /// Changes which page is displayed in the controlled [ExpandableCarousel]. /// Jumps the page position from its current value to the given value, /// without animation, and without checking if the new value is in range. @override void jumpToPage(int page) { - final index = getRealIndex(_state!.pageController!.page!.toInt(), + final index = getRealIndex(_state!.pageController?.page!.toInt() ?? 0, _state!.realPage - _state!.initialPage, _state!.itemCount); _setModeController(); - final pageToJump = _state!.pageController!.page!.toInt() + page - index; - return _state!.pageController!.jumpToPage(pageToJump); + + return _state!.pageController?.jumpToPage( + (_state!.pageController?.page!.toInt() ?? 0) + page - index); } - /// Animates the controlled [FlutterCarouselWidget] to the next page. - /// + /// Animates the controlled [ExpandableCarousel] to the next page. /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. @override @@ -76,11 +79,15 @@ class ExpandableCarouselControllerImpl implements ExpandableCarouselController { {Duration? duration = const Duration(milliseconds: 300), Curve? curve = Curves.linear}) async { final isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { _state!.onResetTimer(); } + _setModeController(); - await _state!.pageController!.nextPage(duration: duration!, curve: curve!); + + await _state!.pageController?.nextPage(duration: duration!, curve: curve!); + if (isNeedResetTimer) { _state!.onResumeTimer(); } @@ -89,8 +96,7 @@ class ExpandableCarouselControllerImpl implements ExpandableCarouselController { @override Future get onReady => _readyCompleter.future; - /// Animates the controlled [FlutterCarouselWidget] to the previous page. - /// + /// Animates the controlled [ExpandableCarousel] to the previous page. /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. @override @@ -98,12 +104,16 @@ class ExpandableCarouselControllerImpl implements ExpandableCarouselController { {Duration? duration = const Duration(milliseconds: 300), Curve? curve = Curves.linear}) async { final isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { _state!.onResetTimer(); } + _setModeController(); - await _state!.pageController! - .previousPage(duration: duration!, curve: curve!); + + await _state!.pageController + ?.previousPage(duration: duration!, curve: curve!); + if (isNeedResetTimer) { _state!.onResumeTimer(); } @@ -112,19 +122,17 @@ class ExpandableCarouselControllerImpl implements ExpandableCarouselController { @override bool get ready => _state != null; - /// Starts the controlled [FlutterCarouselWidget] autoplay. - /// + /// Starts the controlled [ExpandableCarousel] autoplay. /// The carousel will only autoPlay if the [autoPlay] parameter - /// in [CarouselOptions] is true. + /// in [ExpandableCarouselOptions] is true. @override void startAutoPlay() { _state!.onResumeTimer(); } - /// Stops the controlled [FlutterCarouselWidget] from autoplaying. - /// + /// Stops the controlled [ExpandableCarousel] from autoplaying. /// This is a more on-demand way of doing this. Use the [autoPlay] - /// parameter in [CarouselOptions] to specify the autoPlay behaviour of the carousel. + /// parameter in [ExpandableCarouselOptions] to specify the autoPlay behaviour of the carousel. @override void stopAutoPlay() { _state!.onResetTimer(); diff --git a/lib/src/helpers/flutter_carousel_controller.dart b/lib/src/carousel_controller/flutter_carousel_controller.dart similarity index 66% rename from lib/src/helpers/flutter_carousel_controller.dart rename to lib/src/carousel_controller/flutter_carousel_controller.dart index a7928ec..78ca8c8 100644 --- a/lib/src/helpers/flutter_carousel_controller.dart +++ b/lib/src/carousel_controller/flutter_carousel_controller.dart @@ -1,12 +1,13 @@ import 'dart:async'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_state.dart'; -import 'package:flutter_carousel_widget/src/utils/flutter_carousel_utils.dart'; +import 'package:flutter/animation.dart'; -abstract class CarouselController { - factory CarouselController() => CarouselControllerImpl(); +import '../carousel_state/flutter_carousel_state.dart'; +import '../enums/carousel_page_changed_reason.dart'; +import '../utils/flutter_carousel_utils.dart'; + +abstract class FlutterCarouselController { + factory FlutterCarouselController() => FlutterCarouselControllerImpl(); bool get ready; @@ -25,12 +26,11 @@ abstract class CarouselController { void stopAutoPlay(); } -class CarouselControllerImpl implements CarouselController { +class FlutterCarouselControllerImpl implements FlutterCarouselController { final Completer _readyCompleter = Completer(); - CarouselState? _state; + FlutterCarouselState? _state; - /// Animates the controlled [FlutterCarouselWidget] from the current page to the given page. - /// + /// Animates the controlled [FlutterCarousel] from the current page to the given page. /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. @override @@ -38,38 +38,40 @@ class CarouselControllerImpl implements CarouselController { {Duration? duration = const Duration(milliseconds: 300), Curve? curve = Curves.linear}) async { final isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { _state!.onResetTimer(); } - final index = getRealIndex(_state!.pageController!.page!.toInt(), + final index = getRealIndex(_state!.pageController?.page!.toInt() ?? 0, _state!.realPage - _state!.initialPage, _state!.itemCount); + _setModeController(); + await _state!.pageController!.animateToPage( - _state!.pageController!.page!.toInt() + page - index, + (_state!.pageController?.page!.toInt() ?? 0) + page - index, duration: duration!, curve: curve!); + if (isNeedResetTimer) { _state!.onResumeTimer(); } } - /// Changes which page is displayed in the controlled [FlutterCarouselWidget]. - /// + /// Changes which page is displayed in the controlled [FlutterCarousel]. /// Jumps the page position from its current value to the given value, /// without animation, and without checking if the new value is in range. @override - void jumpToPage(int page) { - final index = getRealIndex(_state?.pageController?.page!.toInt() ?? 0, + void jumpToPage(int page) async { + final index = getRealIndex(_state!.pageController?.page!.toInt() ?? 0, _state!.realPage - _state!.initialPage, _state!.itemCount); _setModeController(); - final pageToJump = - _state!.pageController?.page!.toInt() ?? 0 + page - index; - return _state!.pageController?.jumpToPage(pageToJump); + + return _state!.pageController?.jumpToPage( + (_state!.pageController?.page!.toInt() ?? 0) + page - index); } - /// Animates the controlled [FlutterCarouselWidget] to the next page. - /// + /// Animates the controlled [FlutterCarousel] to the next page. /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. @override @@ -77,11 +79,15 @@ class CarouselControllerImpl implements CarouselController { {Duration? duration = const Duration(milliseconds: 300), Curve? curve = Curves.linear}) async { final isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { _state!.onResetTimer(); } + _setModeController(); + await _state!.pageController?.nextPage(duration: duration!, curve: curve!); + if (isNeedResetTimer) { _state!.onResumeTimer(); } @@ -90,8 +96,7 @@ class CarouselControllerImpl implements CarouselController { @override Future get onReady => _readyCompleter.future; - /// Animates the controlled [FlutterCarouselWidget] to the previous page. - /// + /// Animates the controlled [FlutterCarousel] to the previous page. /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. @override @@ -99,12 +104,16 @@ class CarouselControllerImpl implements CarouselController { {Duration? duration = const Duration(milliseconds: 300), Curve? curve = Curves.linear}) async { final isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate; + if (isNeedResetTimer) { _state!.onResetTimer(); } + _setModeController(); + await _state!.pageController! .previousPage(duration: duration!, curve: curve!); + if (isNeedResetTimer) { _state!.onResumeTimer(); } @@ -113,25 +122,23 @@ class CarouselControllerImpl implements CarouselController { @override bool get ready => _state != null; - /// Starts the controlled [FlutterCarouselWidget] autoplay. - /// + /// Starts the controlled [FlutterCarousel] autoplay. /// The carousel will only autoPlay if the [autoPlay] parameter - /// in [CarouselOptions] is true. + /// in [FlutterCarouselOptions] is true. @override void startAutoPlay() { _state!.onResumeTimer(); } - /// Stops the controlled [FlutterCarouselWidget] from autoplaying. - /// + /// Stops the controlled [FlutterCarousel] from autoplaying. /// This is a more on-demand way of doing this. Use the [autoPlay] - /// parameter in [CarouselOptions] to specify the autoPlay behaviour of the carousel. + /// parameter in [FlutterCarouselOptions] to specify the autoPlay behaviour of the carousel. @override void stopAutoPlay() { _state!.onResetTimer(); } - set state(CarouselState? state) { + set state(FlutterCarouselState? state) { _state = state; if (!_readyCompleter.isCompleted) { _readyCompleter.complete(); diff --git a/lib/src/helpers/carousel_options.dart b/lib/src/carousel_options/base_carousel_options.dart similarity index 77% rename from lib/src/helpers/carousel_options.dart rename to lib/src/carousel_options/base_carousel_options.dart index c6bee6e..5cdd36a 100644 --- a/lib/src/helpers/carousel_options.dart +++ b/lib/src/carousel_options/base_carousel_options.dart @@ -1,13 +1,12 @@ import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_controller.dart'; -import 'package:flutter_carousel_widget/src/indicators/circular_slide_indicator.dart'; -import 'package:flutter_carousel_widget/src/indicators/slide_indicator.dart'; - -abstract class CarouselOptions { - CarouselOptions({ - this.height, +import 'package:flutter/widgets.dart'; + +import '../enums/carousel_page_changed_reason.dart'; +import '../enums/center_page_enlarge_strategy.dart'; +import '../indicators/slide_indicator.dart'; + +abstract class BaseCarouselOptions { + BaseCarouselOptions({ this.aspectRatio, this.viewportFraction = 0.8, this.initialPage = 0, @@ -17,7 +16,6 @@ abstract class CarouselOptions { this.autoPlayInterval = const Duration(seconds: 5), this.autoPlayAnimationDuration = const Duration(milliseconds: 300), this.autoPlayCurve = Curves.easeInCubic, - this.controller, this.onPageChanged, this.onScrolled, this.physics = const BouncingScrollPhysics(), @@ -30,7 +28,7 @@ abstract class CarouselOptions { this.showIndicator = true, this.floatingIndicator = true, this.indicatorMargin = 8.0, - this.slideIndicator = const CircularSlideIndicator(), + this.slideIndicator, this.clipBehavior = Clip.antiAlias, this.scrollBehavior, this.pageSnapping = true, @@ -38,72 +36,75 @@ abstract class CarouselOptions { this.dragStartBehavior = DragStartBehavior.start, this.allowImplicitScrolling = false, this.restorationId, - }) : assert(showIndicator == true ? slideIndicator != null : true); - - /// Called whenever the page in the center of the viewport changes. - final Function(int index, CarouselPageChangedReason reason)? onPageChanged; - - /// Controls whether the widget's pages will respond to [RenderObject.showOnScreen], which will allow for implicit accessibility scrolling. - /// - /// Corresponds to Material's PageView's allowImplicitScrolling parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final bool allowImplicitScrolling; + this.enlargeCenterPage = false, + this.enlargeFactor = 0.25, + this.enlargeStrategy = CenterPageEnlargeStrategy.scale, + this.disableCenter = false, + }) : assert(viewportFraction > 0 && viewportFraction <= 1.0, + 'viewportFraction must be between 0.0 and 1.0'), + assert(initialPage >= 0, 'initialPage must be a non-negative integer'), + assert(autoPlayInterval > Duration.zero, + 'autoPlayInterval must be greater than zero'), + assert(autoPlayAnimationDuration > Duration.zero, + 'autoPlayAnimationDuration must be greater than zero'), + assert(indicatorMargin != null && indicatorMargin >= 0, + 'indicatorMargin must be a non-negative value'), + assert( + enlargeCenterPage == false || + (enlargeFactor != null && enlargeFactor > 0.0), + 'enlargeFactor must be greater than 0 when enlargeCenterPage is true'), + assert(enlargeFactor != null && enlargeFactor > 0.0, + 'enlargeFactor must be greater than 0.0'), + super(); /// Aspect ratio is used if no height have been declared. - /// /// Defaults to 1:1 (square) aspect ratio. final double? aspectRatio; + /// The fraction of the viewport that each page should occupy. + /// Defaults to 0.8, which means each page fills 80% of the carousel. + final double viewportFraction; + /// Enables auto play, sliding one page at a time. - /// /// Use [autoPlayInterval] to determent the frequency of slides. /// Defaults to false. final bool autoPlay; /// The animation duration between two transitioning pages while in auto playback. - /// /// Defaults to 500 ms. final Duration autoPlayAnimationDuration; /// Determines the animation curve physics. - /// /// Defaults to [Curves.easeInOut]. final Curve autoPlayCurve; /// Sets Duration to determent the frequency of slides when - /// /// [autoPlay] is set to true. /// Defaults to 5 seconds. final Duration autoPlayInterval; /// The content will be clipped (or not) according to this option. - /// /// Corresponds to Material's PageView's clipBehavior parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html final Clip clipBehavior; - /// A [MapController], used to control the map. - final ExpandableCarouselController? controller; + /// Called whenever the page in the center of the viewport changes. + final Function(int index, CarouselPageChangedReason reason)? onPageChanged; /// Determines the way that drag start behavior is handled. - /// /// Corresponds to Material's PageView's dragStartBehavior parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html final DragStartBehavior dragStartBehavior; ///Determines if carousel should loop infinitely or be limited to item length. - /// ///Defaults to true, i.e. infinite loop. final bool enableInfiniteScroll; /// Whether or not to float `SlideIndicator` over `Carousel`. final bool floatingIndicator; - /// Set carousel height and overrides any existing [aspectRatio]. - final double? height; - /// Indicator margin final double? indicatorMargin; /// The initial page to show when first creating the [CarouselSlider]. - /// /// Defaults to 0. final int initialPage; @@ -123,7 +124,6 @@ abstract class CarouselOptions { final bool padEnds; /// Set to false to disable page snapping, useful for custom scroll behavior. - /// /// Default to `true`. final bool pageSnapping; @@ -150,37 +150,32 @@ abstract class CarouselOptions { final bool pauseAutoPlayOnTouch; /// How the carousel should respond to user input. - /// /// For example, determines how the items continues to animate after the /// user stops dragging the page view. - /// /// The physics are modified to snap to page boundaries using /// [PageScrollPhysics] prior to being used. - /// /// Defaults to matching platform conventions. final ScrollPhysics? physics; + /// Controls whether the widget's pages will respond to [RenderObject.showOnScreen], which will allow for implicit accessibility scrolling. + /// Corresponds to Material's PageView's allowImplicitScrolling parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html + final bool allowImplicitScrolling; + /// Restoration ID to save and restore the scroll offset of the scrollable. - /// /// Corresponds to Material's PageView's restorationId parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html final String? restorationId; /// Reverse the order of items if set to true. - /// /// Defaults to false. final bool reverse; ///A ScrollBehavior that will be applied to this widget individually. - /// /// Defaults to null, wherein the inherited ScrollBehavior is copied and modified to alter the viewport decoration, like Scrollbars. - /// /// ScrollBehaviors also provide ScrollPhysics. If an explicit ScrollPhysics is provided in physics, it will take precedence, followed by scrollBehavior, and then the inherited ancestor ScrollBehavior. - /// /// The ScrollBehavior of the inherited ScrollConfiguration will be modified by default to not apply a Scrollbar. final ScrollBehavior? scrollBehavior; /// The axis along which the page view scrolls. - /// /// Defaults to [Axis.horizontal]. final Axis scrollDirection; @@ -190,8 +185,18 @@ abstract class CarouselOptions { /// Use `slideIndicator` to determine which indicator you want to show for each slide. final SlideIndicator? slideIndicator; - /// The fraction of the viewport that each page should occupy. - /// - /// Defaults to 0.8, which means each page fills 80% of the carousel. - final double viewportFraction; + /// Whether or not to disable the `Center` widget for each slide. + final bool disableCenter; + + /// Determines if current page should be larger then the side images, + /// creating a feeling of depth in the carousel. + /// Defaults to false. + final bool? enlargeCenterPage; + + /// Use `enlargeStrategy` to determine which method to enlarge the center page. + final CenterPageEnlargeStrategy enlargeStrategy; + + /// The fraction of the center page to enlarge. + /// Defaults to 0.25, which means center page will be 25% larger than other pages. + final double? enlargeFactor; } diff --git a/lib/src/carousel_options/expandable_carousel_options.dart b/lib/src/carousel_options/expandable_carousel_options.dart new file mode 100644 index 0000000..17061e4 --- /dev/null +++ b/lib/src/carousel_options/expandable_carousel_options.dart @@ -0,0 +1,171 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; + +import '../carousel_controller/expandable_carousel_controller.dart'; +import '../enums/carousel_page_changed_reason.dart'; +import '../enums/center_page_enlarge_strategy.dart'; +import '../indicators/slide_indicator.dart'; +import 'base_carousel_options.dart'; + +class ExpandableCarouselOptions extends BaseCarouselOptions { + ExpandableCarouselOptions({ + this.controller, + this.estimatedPageSize, + double? aspectRatio, + double viewportFraction = 0.8, + int initialPage = 0, + bool enableInfiniteScroll = false, + bool reverse = false, + bool autoPlay = false, + Duration autoPlayInterval = const Duration(seconds: 5), + Duration autoPlayAnimationDuration = const Duration(milliseconds: 300), + Curve autoPlayCurve = Curves.easeInCubic, + Function(int index, CarouselPageChangedReason reason)? onPageChanged, + ValueChanged? onScrolled, + ScrollPhysics? physics = const BouncingScrollPhysics(), + Axis scrollDirection = Axis.horizontal, + bool pauseAutoPlayOnTouch = true, + bool pauseAutoPlayOnManualNavigate = true, + bool pauseAutoPlayInFiniteScroll = false, + PageStorageKey? pageViewKey, + bool keepPage = true, + bool showIndicator = true, + bool floatingIndicator = true, + double? indicatorMargin = 8.0, + SlideIndicator? slideIndicator, + Clip clipBehavior = Clip.antiAlias, + ScrollBehavior? scrollBehavior, + bool pageSnapping = true, + bool padEnds = true, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + bool allowImplicitScrolling = false, + String? restorationId, + bool? enlargeCenterPage = false, + double? enlargeFactor = 0.25, + CenterPageEnlargeStrategy enlargeStrategy = CenterPageEnlargeStrategy.scale, + bool disableCenter = false, + }) : assert(estimatedPageSize == null || estimatedPageSize > 0.0, + 'estimatedPageSize must be a non-negative double'), + super( + aspectRatio: aspectRatio, + viewportFraction: viewportFraction, + initialPage: initialPage, + enableInfiniteScroll: enableInfiniteScroll, + reverse: reverse, + autoPlay: autoPlay, + autoPlayInterval: autoPlayInterval, + autoPlayAnimationDuration: autoPlayAnimationDuration, + autoPlayCurve: autoPlayCurve, + onPageChanged: onPageChanged, + onScrolled: onScrolled, + physics: physics, + scrollDirection: scrollDirection, + pauseAutoPlayOnTouch: pauseAutoPlayOnTouch, + pauseAutoPlayOnManualNavigate: pauseAutoPlayOnManualNavigate, + pauseAutoPlayInFiniteScroll: pauseAutoPlayInFiniteScroll, + pageViewKey: pageViewKey, + keepPage: keepPage, + showIndicator: showIndicator, + floatingIndicator: floatingIndicator, + indicatorMargin: indicatorMargin, + slideIndicator: slideIndicator, + clipBehavior: clipBehavior, + scrollBehavior: scrollBehavior, + pageSnapping: pageSnapping, + padEnds: padEnds, + dragStartBehavior: dragStartBehavior, + allowImplicitScrolling: allowImplicitScrolling, + restorationId: restorationId, + enlargeCenterPage: enlargeCenterPage, + enlargeFactor: enlargeFactor, + enlargeStrategy: enlargeStrategy, + disableCenter: disableCenter, + ); + + /// Setting it to a value much bigger than most pages' sizes might result in a + /// reversed - "expand and shrink" - effect. + final double? estimatedPageSize; + + /// A [MapController], used to control the map. + final ExpandableCarouselController? controller; + + /// Copy With Constructor + ExpandableCarouselOptions copyWith({ + double? aspectRatio, + double? viewportFraction, + int? initialPage, + bool? enableInfiniteScroll, + bool? reverse, + bool? autoPlay, + Duration? autoPlayInterval, + Duration? autoPlayAnimationDuration, + Curve? autoPlayCurve, + Axis? scrollDirection, + ExpandableCarouselController? carouselController, + Function(int index, CarouselPageChangedReason reason)? onPageChanged, + ValueChanged? onScrolled, + ScrollPhysics? physics, + bool? pageSnapping, + bool? pauseAutoPlayOnTouch, + bool? pauseAutoPlayOnManualNavigate, + bool? pauseAutoPlayInFiniteScroll, + PageStorageKey? pageViewKey, + SlideIndicator? slideIndicator, + bool? showIndicator, + bool? floatingIndicator, + double? indicatorMargin, + bool? keepPage, + bool? padEnds, + Clip? clipBehavior, + DragStartBehavior? dragStartBehavior, + ScrollBehavior? scrollBehavior, + bool? allowImplicitScrolling, + String? restorationId, + bool? enlargeCenterPage, + double? enlargeFactor, + CenterPageEnlargeStrategy? enlargeStrategy, + bool? disableCenter, + double? estimatedPageSize, + }) { + return ExpandableCarouselOptions( + aspectRatio: aspectRatio ?? this.aspectRatio, + viewportFraction: viewportFraction ?? this.viewportFraction, + initialPage: initialPage ?? this.initialPage, + enableInfiniteScroll: enableInfiniteScroll ?? this.enableInfiniteScroll, + reverse: reverse ?? this.reverse, + autoPlay: autoPlay ?? this.autoPlay, + autoPlayInterval: autoPlayInterval ?? this.autoPlayInterval, + autoPlayAnimationDuration: + autoPlayAnimationDuration ?? this.autoPlayAnimationDuration, + autoPlayCurve: autoPlayCurve ?? this.autoPlayCurve, + onPageChanged: onPageChanged ?? this.onPageChanged, + onScrolled: onScrolled ?? this.onScrolled, + physics: physics ?? this.physics, + pageSnapping: pageSnapping ?? this.pageSnapping, + scrollDirection: scrollDirection ?? this.scrollDirection, + pauseAutoPlayOnTouch: pauseAutoPlayOnTouch ?? this.pauseAutoPlayOnTouch, + pauseAutoPlayOnManualNavigate: + pauseAutoPlayOnManualNavigate ?? this.pauseAutoPlayOnManualNavigate, + pauseAutoPlayInFiniteScroll: + pauseAutoPlayInFiniteScroll ?? this.pauseAutoPlayInFiniteScroll, + pageViewKey: pageViewKey ?? this.pageViewKey, + keepPage: keepPage ?? this.keepPage, + showIndicator: showIndicator ?? this.showIndicator, + floatingIndicator: floatingIndicator ?? this.floatingIndicator, + indicatorMargin: indicatorMargin ?? this.indicatorMargin, + slideIndicator: slideIndicator ?? this.slideIndicator, + padEnds: padEnds ?? this.padEnds, + clipBehavior: clipBehavior ?? this.clipBehavior, + dragStartBehavior: dragStartBehavior ?? this.dragStartBehavior, + scrollBehavior: scrollBehavior ?? this.scrollBehavior, + allowImplicitScrolling: + allowImplicitScrolling ?? this.allowImplicitScrolling, + restorationId: restorationId ?? this.restorationId, + enlargeCenterPage: enlargeCenterPage ?? this.enlargeCenterPage, + enlargeFactor: enlargeFactor ?? this.enlargeFactor, + enlargeStrategy: enlargeStrategy ?? this.enlargeStrategy, + disableCenter: disableCenter ?? this.disableCenter, + estimatedPageSize: estimatedPageSize ?? this.estimatedPageSize, + ); + } +} diff --git a/lib/src/carousel_options/flutter_carousel_options.dart b/lib/src/carousel_options/flutter_carousel_options.dart new file mode 100644 index 0000000..db8b76c --- /dev/null +++ b/lib/src/carousel_options/flutter_carousel_options.dart @@ -0,0 +1,170 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; + +import '../carousel_controller/flutter_carousel_controller.dart'; +import '../enums/carousel_page_changed_reason.dart'; +import '../enums/center_page_enlarge_strategy.dart'; +import '../indicators/slide_indicator.dart'; +import 'base_carousel_options.dart'; + +class FlutterCarouselOptions extends BaseCarouselOptions { + FlutterCarouselOptions({ + this.controller, + this.height, + double? aspectRatio, + double viewportFraction = 0.8, + int initialPage = 0, + bool enableInfiniteScroll = false, + bool reverse = false, + bool autoPlay = false, + Duration autoPlayInterval = const Duration(seconds: 5), + Duration autoPlayAnimationDuration = const Duration(milliseconds: 300), + Curve autoPlayCurve = Curves.easeInCubic, + Function(int index, CarouselPageChangedReason reason)? onPageChanged, + ValueChanged? onScrolled, + ScrollPhysics? physics = const BouncingScrollPhysics(), + Axis scrollDirection = Axis.horizontal, + bool pauseAutoPlayOnTouch = true, + bool pauseAutoPlayOnManualNavigate = true, + bool pauseAutoPlayInFiniteScroll = false, + PageStorageKey? pageViewKey, + bool keepPage = true, + bool showIndicator = true, + bool floatingIndicator = true, + double? indicatorMargin = 8.0, + SlideIndicator? slideIndicator, + Clip clipBehavior = Clip.antiAlias, + ScrollBehavior? scrollBehavior, + bool pageSnapping = true, + bool padEnds = true, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + bool allowImplicitScrolling = false, + String? restorationId, + bool? enlargeCenterPage = false, + double? enlargeFactor = 0.25, + CenterPageEnlargeStrategy enlargeStrategy = CenterPageEnlargeStrategy.scale, + bool disableCenter = false, + }) : assert(height == null || height > 0.0, + 'height must be a non-negative double'), + super( + aspectRatio: aspectRatio, + viewportFraction: viewportFraction, + initialPage: initialPage, + enableInfiniteScroll: enableInfiniteScroll, + reverse: reverse, + autoPlay: autoPlay, + autoPlayInterval: autoPlayInterval, + autoPlayAnimationDuration: autoPlayAnimationDuration, + autoPlayCurve: autoPlayCurve, + onPageChanged: onPageChanged, + onScrolled: onScrolled, + physics: physics, + scrollDirection: scrollDirection, + pauseAutoPlayOnTouch: pauseAutoPlayOnTouch, + pauseAutoPlayOnManualNavigate: pauseAutoPlayOnManualNavigate, + pauseAutoPlayInFiniteScroll: pauseAutoPlayInFiniteScroll, + pageViewKey: pageViewKey, + keepPage: keepPage, + showIndicator: showIndicator, + floatingIndicator: floatingIndicator, + indicatorMargin: indicatorMargin, + slideIndicator: slideIndicator, + clipBehavior: clipBehavior, + scrollBehavior: scrollBehavior, + pageSnapping: pageSnapping, + padEnds: padEnds, + dragStartBehavior: dragStartBehavior, + allowImplicitScrolling: allowImplicitScrolling, + restorationId: restorationId, + enlargeCenterPage: enlargeCenterPage, + enlargeFactor: enlargeFactor, + enlargeStrategy: enlargeStrategy, + disableCenter: disableCenter, + ); + + /// Set carousel height and overrides any existing [aspectRatio]. + final double? height; + + /// A [MapController], used to control the map. + final FlutterCarouselController? controller; + + /// Copy With Constructor + FlutterCarouselOptions copyWith({ + double? height, + double? aspectRatio, + double? viewportFraction, + int? initialPage, + bool? enableInfiniteScroll, + bool? reverse, + bool? autoPlay, + Duration? autoPlayInterval, + Duration? autoPlayAnimationDuration, + Curve? autoPlayCurve, + Axis? scrollDirection, + FlutterCarouselController? carouselController, + Function(int index, CarouselPageChangedReason reason)? onPageChanged, + ValueChanged? onScrolled, + ScrollPhysics? physics, + bool? pageSnapping, + bool? pauseAutoPlayOnTouch, + bool? pauseAutoPlayOnManualNavigate, + bool? pauseAutoPlayInFiniteScroll, + PageStorageKey? pageViewKey, + bool? enlargeCenterPage, + double? enlargeFactor, + CenterPageEnlargeStrategy? enlargeStrategy, + bool? disableCenter, + SlideIndicator? slideIndicator, + bool? showIndicator, + bool? floatingIndicator, + double? indicatorMargin, + bool? keepPage, + bool? padEnds, + Clip? clipBehavior, + DragStartBehavior? dragStartBehavior, + ScrollBehavior? scrollBehavior, + bool? allowImplicitScrolling, + String? restorationId, + }) { + return FlutterCarouselOptions( + height: height ?? this.height, + aspectRatio: aspectRatio ?? this.aspectRatio, + viewportFraction: viewportFraction ?? this.viewportFraction, + initialPage: initialPage ?? this.initialPage, + enableInfiniteScroll: enableInfiniteScroll ?? this.enableInfiniteScroll, + reverse: reverse ?? this.reverse, + autoPlay: autoPlay ?? this.autoPlay, + autoPlayInterval: autoPlayInterval ?? this.autoPlayInterval, + autoPlayAnimationDuration: + autoPlayAnimationDuration ?? this.autoPlayAnimationDuration, + autoPlayCurve: autoPlayCurve ?? this.autoPlayCurve, + onPageChanged: onPageChanged ?? this.onPageChanged, + onScrolled: onScrolled ?? this.onScrolled, + physics: physics ?? this.physics, + pageSnapping: pageSnapping ?? this.pageSnapping, + scrollDirection: scrollDirection ?? this.scrollDirection, + pauseAutoPlayOnTouch: pauseAutoPlayOnTouch ?? this.pauseAutoPlayOnTouch, + pauseAutoPlayOnManualNavigate: + pauseAutoPlayOnManualNavigate ?? this.pauseAutoPlayOnManualNavigate, + pauseAutoPlayInFiniteScroll: + pauseAutoPlayInFiniteScroll ?? this.pauseAutoPlayInFiniteScroll, + pageViewKey: pageViewKey ?? this.pageViewKey, + keepPage: keepPage ?? this.keepPage, + enlargeCenterPage: enlargeCenterPage ?? this.enlargeCenterPage, + enlargeFactor: enlargeFactor ?? this.enlargeFactor, + enlargeStrategy: enlargeStrategy ?? this.enlargeStrategy, + disableCenter: disableCenter ?? this.disableCenter, + showIndicator: showIndicator ?? this.showIndicator, + floatingIndicator: floatingIndicator ?? this.floatingIndicator, + indicatorMargin: indicatorMargin ?? this.indicatorMargin, + slideIndicator: slideIndicator ?? this.slideIndicator, + padEnds: padEnds ?? this.padEnds, + clipBehavior: clipBehavior ?? this.clipBehavior, + dragStartBehavior: dragStartBehavior ?? this.dragStartBehavior, + scrollBehavior: scrollBehavior ?? this.scrollBehavior, + allowImplicitScrolling: + allowImplicitScrolling ?? this.allowImplicitScrolling, + restorationId: restorationId ?? this.restorationId, + ); + } +} diff --git a/lib/src/helpers/flutter_expandable_carousel_state.dart b/lib/src/carousel_state/expandable_carousel_state.dart similarity index 83% rename from lib/src/helpers/flutter_expandable_carousel_state.dart rename to lib/src/carousel_state/expandable_carousel_state.dart index aa694a0..d5bb359 100644 --- a/lib/src/helpers/flutter_expandable_carousel_state.dart +++ b/lib/src/carousel_state/expandable_carousel_state.dart @@ -1,6 +1,7 @@ -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_options.dart'; +import 'package:flutter/widgets.dart'; + +import '../carousel_options/expandable_carousel_options.dart'; +import '../enums/carousel_page_changed_reason.dart'; class ExpandableCarouselState { ExpandableCarouselState( @@ -11,7 +12,6 @@ class ExpandableCarouselState { ); /// The initial index of the [PageView] on [FlutterCarouselWidget] init. - /// int initialPage = 0; /// The widgets count that should be shown at carousel @@ -35,7 +35,6 @@ class ExpandableCarouselState { PageController? pageController; /// The actual index of the [PageView]. - /// /// This value can be ignored unless you know the carousel will be scrolled /// backwards more then 10000 pages. /// Defaults to 10000 to simulate infinite backwards scrolling. diff --git a/lib/src/helpers/flutter_carousel_state.dart b/lib/src/carousel_state/flutter_carousel_state.dart similarity index 77% rename from lib/src/helpers/flutter_carousel_state.dart rename to lib/src/carousel_state/flutter_carousel_state.dart index 9e8d75c..345f5f2 100644 --- a/lib/src/helpers/flutter_carousel_state.dart +++ b/lib/src/carousel_state/flutter_carousel_state.dart @@ -1,9 +1,10 @@ -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_options.dart'; +import 'package:flutter/widgets.dart'; -class CarouselState { - CarouselState( +import '../carousel_options/flutter_carousel_options.dart'; +import '../enums/carousel_page_changed_reason.dart'; + +class FlutterCarouselState { + FlutterCarouselState( this.options, this.onResetTimer, this.onResumeTimer, @@ -11,7 +12,6 @@ class CarouselState { ); /// The initial index of the [PageView] on [FlutterCarouselWidget] init. - /// int initialPage = 0; /// The widgets count that should be shown at carousel @@ -28,14 +28,13 @@ class CarouselState { Function onResumeTimer; /// The [CarouselOptions] to create this state - CarouselOptions options; + FlutterCarouselOptions options; /// [pageController] is created using the properties passed to the constructor /// and can be used to control the [PageView] it is passed to. PageController? pageController; /// The actual index of the [PageView]. - /// /// This value can be ignored unless you know the carousel will be scrolled /// backwards more then 10000 pages. /// Defaults to 10000 to simulate infinite backwards scrolling. diff --git a/lib/src/helpers/flutter_carousel_options.dart b/lib/src/helpers/flutter_carousel_options.dart deleted file mode 100644 index 5b2eb15..0000000 --- a/lib/src/helpers/flutter_carousel_options.dart +++ /dev/null @@ -1,299 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/enums/center_page_enlarge_strategy.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_controller.dart'; -import 'package:flutter_carousel_widget/src/indicators/circular_slide_indicator.dart'; -import 'package:flutter_carousel_widget/src/indicators/slide_indicator.dart'; - -class CarouselOptions { - CarouselOptions({ - this.height, - this.aspectRatio, - this.viewportFraction = 0.8, - this.initialPage = 0, - this.enableInfiniteScroll = false, - this.reverse = false, - this.autoPlay = false, - this.autoPlayInterval = const Duration(seconds: 5), - this.autoPlayAnimationDuration = const Duration(milliseconds: 300), - this.autoPlayCurve = Curves.easeInCubic, - this.enlargeCenterPage = false, - this.enlargeFactor = 0.25, - this.controller, - this.onPageChanged, - this.onScrolled, - this.physics = const BouncingScrollPhysics(), - this.scrollDirection = Axis.horizontal, - this.pauseAutoPlayOnTouch = true, - this.pauseAutoPlayOnManualNavigate = true, - this.pauseAutoPlayInFiniteScroll = false, - this.pageViewKey, - this.keepPage = true, - this.enlargeStrategy = CenterPageEnlargeStrategy.scale, - this.disableCenter = false, - this.showIndicator = true, - this.floatingIndicator = true, - this.indicatorMargin = 8.0, - this.slideIndicator = const CircularSlideIndicator(), - this.clipBehavior = Clip.antiAlias, - this.scrollBehavior, - this.pageSnapping = true, - this.padEnds = true, - this.dragStartBehavior = DragStartBehavior.start, - this.allowImplicitScrolling = false, - this.restorationId, - }) : assert(showIndicator == true ? slideIndicator != null : true); - - /// Called whenever the page in the center of the viewport changes. - final Function(int index, CarouselPageChangedReason reason)? onPageChanged; - - /// Controls whether the widget's pages will respond to [RenderObject.showOnScreen], which will allow for implicit accessibility scrolling. - /// - /// Corresponds to Material's PageView's allowImplicitScrolling parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final bool allowImplicitScrolling; - - /// Aspect ratio is used if no height have been declared. - /// - /// Defaults to 1:1 (square) aspect ratio. - final double? aspectRatio; - - /// Enables auto play, sliding one page at a time. - /// - /// Use [autoPlayInterval] to determent the frequency of slides. - /// Defaults to false. - final bool autoPlay; - - /// The animation duration between two transitioning pages while in auto playback. - /// - /// Defaults to 500 ms. - final Duration autoPlayAnimationDuration; - - /// Determines the animation curve physics. - /// - /// Defaults to [Curves.easeInOut]. - final Curve autoPlayCurve; - - /// Sets Duration to determent the frequency of slides when - /// - /// [autoPlay] is set to true. - /// Defaults to 5 seconds. - final Duration autoPlayInterval; - - /// The content will be clipped (or not) according to this option. - /// - /// Corresponds to Material's PageView's clipBehavior parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final Clip clipBehavior; - - /// A [MapController], used to control the map. - final CarouselController? controller; - - /// Determines the way that drag start behavior is handled. - /// - /// Corresponds to Material's PageView's dragStartBehavior parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final DragStartBehavior dragStartBehavior; - - ///Determines if carousel should loop infinitely or be limited to item length. - /// - ///Defaults to true, i.e. infinite loop. - final bool enableInfiniteScroll; - - /// Determines if current page should be larger then the side images, - /// creating a feeling of depth in the carousel. - /// - /// Defaults to false. - final bool? enlargeCenterPage; - - /// Use `enlargeStrategy` to determine which method to enlarge the center page. - final CenterPageEnlargeStrategy enlargeStrategy; - - /// Whether or not to disable the `Center` widget for each slide. - final bool disableCenter; - - /// The fraction of the center page to enlarge. - /// - /// Defaults to 0.25, which means center page will be 25% larger than other pages. - final double? enlargeFactor; - - /// Whether or not to float `SlideIndicator` over `Carousel`. - final bool floatingIndicator; - - /// Set carousel height and overrides any existing [aspectRatio]. - final double? height; - - /// Indicator margin - final double? indicatorMargin; - - /// The initial page to show when first creating the [CarouselSlider]. - /// - /// Defaults to 0. - final int initialPage; - - /// Whether or not to keep pages in PageView - final bool keepPage; - - /// Called whenever the carousel is scrolled - final ValueChanged? onScrolled; - - /// Whether to add padding to both ends of the list. - /// If this is set to true and [viewportFraction] < 1.0, padding will be - /// added such that the first and last child slivers will be in the center - /// of the viewport when scrolled all the way to the start or end, - /// respectively. - /// If [viewportFraction] >= 1.0, this property has no effect. - /// This property defaults to true and must not be null. - final bool padEnds; - - /// Set to false to disable page snapping, useful for custom scroll behavior. - /// - /// Default to `true`. - final bool pageSnapping; - - /// Pass a `PageStorageKey` if you want to keep the PageView's position when - /// it was recreated. - final PageStorageKey? pageViewKey; - - /// If `enableInfiniteScroll` is `false`, and `autoPlay` is `true`, this option - /// decide the carousel should go to the first item when it reach the last item or not. - /// If set to `true`, the auto play will be paused when it reach the last item. - /// If set to `false`, the auto play function will animate to the first item when it was - /// in the last item. - final bool pauseAutoPlayInFiniteScroll; - - /// If `true`, the auto play function will be paused when user is calling - /// pageController's `nextPage` or `previousPage` or `animateToPage` method. - /// And after the animation complete, the auto play will be resumed. - /// Default to `true`. - final bool pauseAutoPlayOnManualNavigate; - - /// If `true`, the auto play function will be paused when user is interacting with - /// the carousel, and will be resumed when user finish interacting. - /// Default to `true`. - final bool pauseAutoPlayOnTouch; - - /// How the carousel should respond to user input. - /// - /// For example, determines how the items continues to animate after the - /// user stops dragging the page view. - /// - /// The physics are modified to snap to page boundaries using - /// [PageScrollPhysics] prior to being used. - /// - /// Defaults to matching platform conventions. - final ScrollPhysics? physics; - - /// Restoration ID to save and restore the scroll offset of the scrollable. - /// - /// Corresponds to Material's PageView's restorationId parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final String? restorationId; - - /// Reverse the order of items if set to true. - /// - /// Defaults to false. - final bool reverse; - - ///A ScrollBehavior that will be applied to this widget individually. - /// - /// Defaults to null, wherein the inherited ScrollBehavior is copied and modified to alter the viewport decoration, like Scrollbars. - /// - /// ScrollBehaviors also provide ScrollPhysics. If an explicit ScrollPhysics is provided in physics, it will take precedence, followed by scrollBehavior, and then the inherited ancestor ScrollBehavior. - /// - /// The ScrollBehavior of the inherited ScrollConfiguration will be modified by default to not apply a Scrollbar. - final ScrollBehavior? scrollBehavior; - - /// The axis along which the page view scrolls. - /// - /// Defaults to [Axis.horizontal]. - final Axis scrollDirection; - - /// Whether or not to show the `SlideIndicator` for each slide. - final bool showIndicator; - - /// Use `slideIndicator` to determine which indicator you want to show for each slide. - final SlideIndicator? slideIndicator; - - /// The fraction of the viewport that each page should occupy. - /// - /// Defaults to 0.8, which means each page fills 80% of the carousel. - final double viewportFraction; - - /// Copy With Constructor - CarouselOptions copyWith({ - double? height, - double? aspectRatio, - double? viewportFraction, - int? initialPage, - bool? enableInfiniteScroll, - bool? reverse, - bool? autoPlay, - Duration? autoPlayInterval, - Duration? autoPlayAnimationDuration, - Curve? autoPlayCurve, - Axis? scrollDirection, - CarouselController? carouselController, - Function(int index, CarouselPageChangedReason reason)? onPageChanged, - ValueChanged? onScrolled, - ScrollPhysics? physics, - bool? pageSnapping, - bool? pauseAutoPlayOnTouch, - bool? pauseAutoPlayOnManualNavigate, - bool? pauseAutoPlayInFiniteScroll, - PageStorageKey? pageViewKey, - bool? enlargeCenterPage, - double? enlargeFactor, - CenterPageEnlargeStrategy? enlargeStrategy, - bool? disableCenter, - SlideIndicator? slideIndicator, - bool? showIndicator, - bool? floatingIndicator, - double? indicatorMargin, - bool? keepPage, - bool? padEnds, - Clip? clipBehavior, - DragStartBehavior? dragStartBehavior, - ScrollBehavior? scrollBehavior, - bool? allowImplicitScrolling, - String? restorationId, - }) { - return CarouselOptions( - height: height ?? this.height, - aspectRatio: aspectRatio ?? this.aspectRatio, - viewportFraction: viewportFraction ?? this.viewportFraction, - initialPage: initialPage ?? this.initialPage, - enableInfiniteScroll: enableInfiniteScroll ?? this.enableInfiniteScroll, - reverse: reverse ?? this.reverse, - autoPlay: autoPlay ?? this.autoPlay, - autoPlayInterval: autoPlayInterval ?? this.autoPlayInterval, - autoPlayAnimationDuration: - autoPlayAnimationDuration ?? this.autoPlayAnimationDuration, - autoPlayCurve: autoPlayCurve ?? this.autoPlayCurve, - onPageChanged: onPageChanged ?? this.onPageChanged, - onScrolled: onScrolled ?? this.onScrolled, - physics: physics ?? this.physics, - pageSnapping: pageSnapping ?? this.pageSnapping, - scrollDirection: scrollDirection ?? this.scrollDirection, - pauseAutoPlayOnTouch: pauseAutoPlayOnTouch ?? this.pauseAutoPlayOnTouch, - pauseAutoPlayOnManualNavigate: - pauseAutoPlayOnManualNavigate ?? this.pauseAutoPlayOnManualNavigate, - pauseAutoPlayInFiniteScroll: - pauseAutoPlayInFiniteScroll ?? this.pauseAutoPlayInFiniteScroll, - pageViewKey: pageViewKey ?? this.pageViewKey, - keepPage: keepPage ?? this.keepPage, - enlargeCenterPage: enlargeCenterPage ?? this.enlargeCenterPage, - enlargeFactor: enlargeFactor ?? this.enlargeFactor, - enlargeStrategy: enlargeStrategy ?? this.enlargeStrategy, - disableCenter: disableCenter ?? this.disableCenter, - showIndicator: showIndicator ?? this.showIndicator, - floatingIndicator: floatingIndicator ?? this.floatingIndicator, - indicatorMargin: indicatorMargin ?? this.indicatorMargin, - slideIndicator: slideIndicator ?? this.slideIndicator, - padEnds: padEnds ?? this.padEnds, - clipBehavior: clipBehavior ?? this.clipBehavior, - dragStartBehavior: dragStartBehavior ?? this.dragStartBehavior, - scrollBehavior: scrollBehavior ?? this.scrollBehavior, - allowImplicitScrolling: - allowImplicitScrolling ?? this.allowImplicitScrolling, - restorationId: restorationId ?? this.restorationId, - ); - } -} diff --git a/lib/src/helpers/flutter_expandable_carousel_options.dart b/lib/src/helpers/flutter_expandable_carousel_options.dart deleted file mode 100644 index 35014e9..0000000 --- a/lib/src/helpers/flutter_expandable_carousel_options.dart +++ /dev/null @@ -1,300 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/carousel_page_changed_reason.dart'; -import 'package:flutter_carousel_widget/src/enums/center_page_enlarge_strategy.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_expandable_carousel_controller.dart'; -import 'package:flutter_carousel_widget/src/indicators/circular_slide_indicator.dart'; -import 'package:flutter_carousel_widget/src/indicators/slide_indicator.dart'; - -class ExpandableCarouselOptions { - ExpandableCarouselOptions({ - this.aspectRatio, - this.viewportFraction = 0.8, - this.initialPage = 0, - this.enableInfiniteScroll = false, - this.reverse = false, - this.autoPlay = false, - this.autoPlayInterval = const Duration(seconds: 5), - this.autoPlayAnimationDuration = const Duration(milliseconds: 300), - this.autoPlayCurve = Curves.easeInCubic, - this.controller, - this.onPageChanged, - this.onScrolled, - this.physics = const BouncingScrollPhysics(), - this.scrollDirection = Axis.horizontal, - this.pauseAutoPlayOnTouch = true, - this.pauseAutoPlayOnManualNavigate = true, - this.pauseAutoPlayInFiniteScroll = false, - this.pageViewKey, - this.keepPage = true, - this.showIndicator = true, - this.floatingIndicator = true, - this.indicatorMargin = 8.0, - this.slideIndicator = const CircularSlideIndicator(), - this.clipBehavior = Clip.antiAlias, - this.scrollBehavior, - this.pageSnapping = true, - this.padEnds = true, - this.dragStartBehavior = DragStartBehavior.start, - this.allowImplicitScrolling = false, - this.restorationId, - this.enlargeCenterPage = false, - this.enlargeFactor = 0.25, - this.enlargeStrategy = CenterPageEnlargeStrategy.scale, - this.disableCenter = false, - this.estimatedPageSize = 0.0, - }) : assert(showIndicator == true ? slideIndicator != null : true); - - /// Called whenever the page in the center of the viewport changes. - final Function(int index, CarouselPageChangedReason reason)? onPageChanged; - - /// Controls whether the widget's pages will respond to [RenderObject.showOnScreen], which will allow for implicit accessibility scrolling. - /// - /// Corresponds to Material's PageView's allowImplicitScrolling parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final bool allowImplicitScrolling; - - /// Aspect ratio is used if no height have been declared. - /// - /// Defaults to 1:1 (square) aspect ratio. - final double? aspectRatio; - - /// Enables auto play, sliding one page at a time. - /// - /// Use [autoPlayInterval] to determent the frequency of slides. - /// Defaults to false. - final bool autoPlay; - - /// The animation duration between two transitioning pages while in auto playback. - /// - /// Defaults to 500 ms. - final Duration autoPlayAnimationDuration; - - /// Determines the animation curve physics. - /// - /// Defaults to [Curves.easeInOut]. - final Curve autoPlayCurve; - - /// Sets Duration to determent the frequency of slides when - /// - /// [autoPlay] is set to true. - /// Defaults to 5 seconds. - final Duration autoPlayInterval; - - /// The content will be clipped (or not) according to this option. - /// - /// Corresponds to Material's PageView's clipBehavior parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final Clip clipBehavior; - - /// A [MapController], used to control the map. - final ExpandableCarouselController? controller; - - /// Determines the way that drag start behavior is handled. - /// - /// Corresponds to Material's PageView's dragStartBehavior parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final DragStartBehavior dragStartBehavior; - - ///Determines if carousel should loop infinitely or be limited to item length. - /// - ///Defaults to true, i.e. infinite loop. - final bool enableInfiniteScroll; - - /// Whether or not to float `SlideIndicator` over `Carousel`. - final bool floatingIndicator; - - /// Indicator margin - final double? indicatorMargin; - - /// The initial page to show when first creating the [CarouselSlider]. - /// - /// Defaults to 0. - final int initialPage; - - /// Whether or not to keep pages in PageView - final bool keepPage; - - /// Called whenever the carousel is scrolled - final ValueChanged? onScrolled; - - /// Whether to add padding to both ends of the list. - /// If this is set to true and [viewportFraction] < 1.0, padding will be - /// added such that the first and last child slivers will be in the center - /// of the viewport when scrolled all the way to the start or end, - /// respectively. - /// If [viewportFraction] >= 1.0, this property has no effect. - /// This property defaults to true and must not be null. - final bool padEnds; - - /// Set to false to disable page snapping, useful for custom scroll behavior. - /// - /// Default to `true`. - final bool pageSnapping; - - /// Pass a `PageStorageKey` if you want to keep the PageView's position when - /// it was recreated. - final PageStorageKey? pageViewKey; - - /// If `enableInfiniteScroll` is `false`, and `autoPlay` is `true`, this option - /// decide the carousel should go to the first item when it reach the last item or not. - /// If set to `true`, the auto play will be paused when it reach the last item. - /// If set to `false`, the auto play function will animate to the first item when it was - /// in the last item. - final bool pauseAutoPlayInFiniteScroll; - - /// If `true`, the auto play function will be paused when user is calling - /// pageController's `nextPage` or `previousPage` or `animateToPage` method. - /// And after the animation complete, the auto play will be resumed. - /// Default to `true`. - final bool pauseAutoPlayOnManualNavigate; - - /// If `true`, the auto play function will be paused when user is interacting with - /// the carousel, and will be resumed when user finish interacting. - /// Default to `true`. - final bool pauseAutoPlayOnTouch; - - /// How the carousel should respond to user input. - /// - /// For example, determines how the items continues to animate after the - /// user stops dragging the page view. - /// - /// The physics are modified to snap to page boundaries using - /// [PageScrollPhysics] prior to being used. - /// - /// Defaults to matching platform conventions. - final ScrollPhysics? physics; - - /// Restoration ID to save and restore the scroll offset of the scrollable. - /// - /// Corresponds to Material's PageView's restorationId parameter: https://api.flutter.dev/flutter/widgets/PageView-class.html - final String? restorationId; - - /// Reverse the order of items if set to true. - /// - /// Defaults to false. - final bool reverse; - - ///A ScrollBehavior that will be applied to this widget individually. - /// - /// Defaults to null, wherein the inherited ScrollBehavior is copied and modified to alter the viewport decoration, like Scrollbars. - /// - /// ScrollBehaviors also provide ScrollPhysics. If an explicit ScrollPhysics is provided in physics, it will take precedence, followed by scrollBehavior, and then the inherited ancestor ScrollBehavior. - /// - /// The ScrollBehavior of the inherited ScrollConfiguration will be modified by default to not apply a Scrollbar. - final ScrollBehavior? scrollBehavior; - - /// The axis along which the page view scrolls. - /// - /// Defaults to [Axis.horizontal]. - final Axis scrollDirection; - - /// Whether or not to show the `SlideIndicator` for each slide. - final bool showIndicator; - - /// Use `slideIndicator` to determine which indicator you want to show for each slide. - final SlideIndicator? slideIndicator; - - /// The fraction of the viewport that each page should occupy. - /// - /// Defaults to 0.8, which means each page fills 80% of the carousel. - final double viewportFraction; - - /// Whether or not to disable the `Center` widget for each slide. - final bool disableCenter; - - /// Determines if current page should be larger then the side images, - /// creating a feeling of depth in the carousel. - /// - /// Defaults to false. - final bool? enlargeCenterPage; - - /// Use `enlargeStrategy` to determine which method to enlarge the center page. - final CenterPageEnlargeStrategy enlargeStrategy; - - /// The fraction of the center page to enlarge. - /// - /// Defaults to 0.25, which means center page will be 25% larger than other pages. - final double? enlargeFactor; - - /// Setting it to a value much bigger than most pages' sizes might result in a - /// reversed - "expand and shrink" - effect. - final double estimatedPageSize; - - /// Copy With Constructor - ExpandableCarouselOptions copyWith({ - double? aspectRatio, - double? viewportFraction, - int? initialPage, - bool? enableInfiniteScroll, - bool? reverse, - bool? autoPlay, - Duration? autoPlayInterval, - Duration? autoPlayAnimationDuration, - Curve? autoPlayCurve, - Axis? scrollDirection, - ExpandableCarouselController? carouselController, - Function(int index, CarouselPageChangedReason reason)? onPageChanged, - ValueChanged? onScrolled, - ScrollPhysics? physics, - bool? pageSnapping, - bool? pauseAutoPlayOnTouch, - bool? pauseAutoPlayOnManualNavigate, - bool? pauseAutoPlayInFiniteScroll, - PageStorageKey? pageViewKey, - SlideIndicator? slideIndicator, - bool? showIndicator, - bool? floatingIndicator, - double? indicatorMargin, - bool? keepPage, - bool? padEnds, - Clip? clipBehavior, - DragStartBehavior? dragStartBehavior, - ScrollBehavior? scrollBehavior, - bool? allowImplicitScrolling, - String? restorationId, - bool? enlargeCenterPage, - double? enlargeFactor, - CenterPageEnlargeStrategy? enlargeStrategy, - bool? disableCenter, - double? estimatedPageSize, - }) { - return ExpandableCarouselOptions( - aspectRatio: aspectRatio ?? this.aspectRatio, - viewportFraction: viewportFraction ?? this.viewportFraction, - initialPage: initialPage ?? this.initialPage, - enableInfiniteScroll: enableInfiniteScroll ?? this.enableInfiniteScroll, - reverse: reverse ?? this.reverse, - autoPlay: autoPlay ?? this.autoPlay, - autoPlayInterval: autoPlayInterval ?? this.autoPlayInterval, - autoPlayAnimationDuration: - autoPlayAnimationDuration ?? this.autoPlayAnimationDuration, - autoPlayCurve: autoPlayCurve ?? this.autoPlayCurve, - onPageChanged: onPageChanged ?? this.onPageChanged, - onScrolled: onScrolled ?? this.onScrolled, - physics: physics ?? this.physics, - pageSnapping: pageSnapping ?? this.pageSnapping, - scrollDirection: scrollDirection ?? this.scrollDirection, - pauseAutoPlayOnTouch: pauseAutoPlayOnTouch ?? this.pauseAutoPlayOnTouch, - pauseAutoPlayOnManualNavigate: - pauseAutoPlayOnManualNavigate ?? this.pauseAutoPlayOnManualNavigate, - pauseAutoPlayInFiniteScroll: - pauseAutoPlayInFiniteScroll ?? this.pauseAutoPlayInFiniteScroll, - pageViewKey: pageViewKey ?? this.pageViewKey, - keepPage: keepPage ?? this.keepPage, - showIndicator: showIndicator ?? this.showIndicator, - floatingIndicator: floatingIndicator ?? this.floatingIndicator, - indicatorMargin: indicatorMargin ?? this.indicatorMargin, - slideIndicator: slideIndicator ?? this.slideIndicator, - padEnds: padEnds ?? this.padEnds, - clipBehavior: clipBehavior ?? this.clipBehavior, - dragStartBehavior: dragStartBehavior ?? this.dragStartBehavior, - scrollBehavior: scrollBehavior ?? this.scrollBehavior, - allowImplicitScrolling: - allowImplicitScrolling ?? this.allowImplicitScrolling, - restorationId: restorationId ?? this.restorationId, - enlargeCenterPage: enlargeCenterPage ?? this.enlargeCenterPage, - enlargeFactor: enlargeFactor ?? this.enlargeFactor, - enlargeStrategy: enlargeStrategy ?? this.enlargeStrategy, - disableCenter: disableCenter ?? this.disableCenter, - estimatedPageSize: estimatedPageSize ?? this.estimatedPageSize, - ); - } -} diff --git a/lib/src/indicators/circular_slide_indicator.dart b/lib/src/indicators/circular_slide_indicator.dart index 9013692..0387bfe 100644 --- a/lib/src/indicators/circular_slide_indicator.dart +++ b/lib/src/indicators/circular_slide_indicator.dart @@ -2,15 +2,19 @@ import 'package:flutter/material.dart' hide CarouselController; import 'package:flutter_carousel_widget/src/indicators/slide_indicator.dart'; import 'package:flutter_carousel_widget/src/indicators/slide_indicator_options.dart'; -class CircularSlideIndicator implements SlideIndicator { - const CircularSlideIndicator( - {this.slideIndicatorOptions = const SlideIndicatorOptions()}); +class CircularSlideIndicator extends SlideIndicator { + CircularSlideIndicator({ + this.slideIndicatorOptions = const SlideIndicatorOptions(), + this.key, + }); final SlideIndicatorOptions slideIndicatorOptions; + final Key? key; @override Widget build(int currentPage, double pageDelta, int itemCount) { return Container( + key: key, alignment: slideIndicatorOptions.alignment, padding: slideIndicatorOptions.padding, color: Colors.transparent, diff --git a/lib/src/indicators/circular_static_indicator.dart b/lib/src/indicators/circular_static_indicator.dart index 260aed0..50471da 100644 --- a/lib/src/indicators/circular_static_indicator.dart +++ b/lib/src/indicators/circular_static_indicator.dart @@ -5,13 +5,16 @@ import 'package:flutter_carousel_widget/src/indicators/slide_indicator_options.d class CircularStaticIndicator extends SlideIndicator { CircularStaticIndicator({ this.slideIndicatorOptions = const SlideIndicatorOptions(), + this.key, }); final SlideIndicatorOptions slideIndicatorOptions; + final Key? key; @override Widget build(int currentPage, double pageDelta, int itemCount) { return Container( + key: key, alignment: slideIndicatorOptions.alignment, padding: slideIndicatorOptions.padding, color: Colors.transparent, diff --git a/lib/src/indicators/circular_wave_slide_indicator.dart b/lib/src/indicators/circular_wave_slide_indicator.dart index d497fca..998a2ef 100644 --- a/lib/src/indicators/circular_wave_slide_indicator.dart +++ b/lib/src/indicators/circular_wave_slide_indicator.dart @@ -2,16 +2,19 @@ import 'package:flutter/material.dart' hide CarouselController; import 'package:flutter_carousel_widget/src/indicators/slide_indicator.dart'; import 'package:flutter_carousel_widget/src/indicators/slide_indicator_options.dart'; -class CircularWaveSlideIndicator implements SlideIndicator { +class CircularWaveSlideIndicator extends SlideIndicator { CircularWaveSlideIndicator({ this.slideIndicatorOptions = const SlideIndicatorOptions(), + this.key, }); final SlideIndicatorOptions slideIndicatorOptions; + final Key? key; @override Widget build(int currentPage, double pageDelta, int itemCount) { return Container( + key: key, alignment: slideIndicatorOptions.alignment, padding: slideIndicatorOptions.padding, color: Colors.transparent, diff --git a/lib/src/indicators/sequential_fill_indicator.dart b/lib/src/indicators/sequential_fill_indicator.dart index a7dd37d..2c87ea0 100644 --- a/lib/src/indicators/sequential_fill_indicator.dart +++ b/lib/src/indicators/sequential_fill_indicator.dart @@ -5,13 +5,16 @@ import 'package:flutter_carousel_widget/src/indicators/slide_indicator_options.d class SequentialFillIndicator extends SlideIndicator { SequentialFillIndicator({ this.slideIndicatorOptions = const SlideIndicatorOptions(), + this.key, }); final SlideIndicatorOptions slideIndicatorOptions; + final Key? key; @override Widget build(int currentPage, double pageDelta, int itemCount) { return Container( + key: key, alignment: slideIndicatorOptions.alignment, padding: slideIndicatorOptions.padding, color: Colors.transparent, diff --git a/lib/src/indicators/slide_indicator.dart b/lib/src/indicators/slide_indicator.dart index 8e19d9d..a13703a 100644 --- a/lib/src/indicators/slide_indicator.dart +++ b/lib/src/indicators/slide_indicator.dart @@ -10,7 +10,6 @@ import 'package:flutter/material.dart' hide CarouselController; /// * [SequentialFillIndicator] abstract class SlideIndicator { /// Builder method returning the slide indicator widget. - /// /// The method accepts the [currentPage] on which the carousel currently /// resides, the [pageDelta] or the difference between the current page and /// its resting position and [itemCount] which is the total number of items. diff --git a/pubspec.yaml b/pubspec.yaml index 9f1c14d..9e89301 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,3 +20,15 @@ dev_dependencies: sdk: flutter lints: ^4.0.0 + +topics: + - carousel + - flutter-carousel + - flutter-carousel-widget + - carousel-slider + - carousel-widget + +funding: + - https://ko-fi.com/nixrajput + - https://www.buymeacoffee.com/nixrajput + - https://github.com/sponsors/nixrajput \ No newline at end of file diff --git a/test/flutter_carousel_widget/widget_test.dart b/test/flutter_carousel_widget/widget_test.dart deleted file mode 100644 index 3be9769..0000000 --- a/test/flutter_carousel_widget/widget_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('FlutterCarousel Widget Tests', () { - testWidgets('Carousel should render correctly with default options', - (WidgetTester tester) async { - // Build the widget - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: FlutterCarousel( - options: CarouselOptions( - scrollDirection: Axis.vertical, - height: 300, - initialPage: 0, // Ensure initialPage is set correctly - ), - items: [const Text('Page 1'), const Text('Page 2')], - ), - ), - ), - ); - - // Debug print statement - print('Initial widget state: ${find.byType(FlutterCarousel)}'); - - // Check initial state - expect(find.text('Page 1'), findsOneWidget); - expect(find.text('Page 2'), findsNothing); - - // Perform the drag to change the page - await tester.drag(find.byType(FlutterCarousel), const Offset(0, -300)); - await tester.pumpAndSettle(); - - // Debug print statement - print('After dragging: ${find.byType(FlutterCarousel)}'); - - // Verify the page has changed - expect(find.text('Page 1'), findsNothing); - expect(find.text('Page 2'), findsOneWidget); - }); - - testWidgets('Carousel should show or hide indicator based on options', - (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: FlutterCarousel( - options: CarouselOptions( - height: 300, - showIndicator: true, - ), - items: [const Text('Page 1'), const Text('Page 2')], - ), - ), - ), - ); - - expect(find.byType(SlideIndicator), findsOneWidget); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: FlutterCarousel( - options: CarouselOptions( - height: 300, - showIndicator: false, - ), - items: [const Text('Page 1'), const Text('Page 2')], - ), - ), - ), - ); - - expect(find.byType(SlideIndicator), findsNothing); - }); - - testWidgets('Carousel should render correctly with initial page option', - (WidgetTester tester) async { - // Arrange - const initialPage = 2; - await tester.pumpWidget( - MaterialApp( - home: FlutterCarousel( - items: List.generate(5, (index) => Text('Item $index')), - options: CarouselOptions(initialPage: initialPage), - ), - ), - ); - - await tester.pumpAndSettle(); - - expect(find.text('Item $initialPage'), findsOneWidget); - }); - }); -} diff --git a/test/flutter_carousel_widget/integration_test.dart b/test/integration_test.dart similarity index 70% rename from test/flutter_carousel_widget/integration_test.dart rename to test/integration_test.dart index 06d12a3..8d16133 100644 --- a/test/flutter_carousel_widget/integration_test.dart +++ b/test/integration_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart' hide CarouselController; +import 'package:flutter/material.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -6,12 +6,12 @@ void main() { group('FlutterCarousel Integration Tests', () { testWidgets('Carousel should update based on CarouselController', (WidgetTester tester) async { - final controller = CarouselController(); + final controller = FlutterCarouselController(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: FlutterCarousel( - options: CarouselOptions( + options: FlutterCarouselOptions( controller: controller, height: 300, autoPlay: true, @@ -34,7 +34,7 @@ void main() { MaterialApp( home: Scaffold( body: FlutterCarousel( - options: CarouselOptions( + options: FlutterCarouselOptions( height: 300, autoPlay: true, autoPlayInterval: const Duration(seconds: 1), @@ -58,7 +58,7 @@ void main() { builder: (context, constraints) { return FlutterCarousel( items: List.generate(5, (index) => Text('Item $index')), - options: CarouselOptions( + options: FlutterCarouselOptions( viewportFraction: constraints.maxWidth < 600 ? 0.8 : 0.5, ), ); @@ -86,7 +86,7 @@ void main() { MaterialApp( home: FlutterCarousel( items: List.generate(5, (index) => Text('Item $index')), - options: CarouselOptions(viewportFraction: 0.8), + options: FlutterCarouselOptions(viewportFraction: 0.8), ), ), ); @@ -107,7 +107,7 @@ void main() { MaterialApp( home: FlutterCarousel( items: List.generate(5, (index) => Text('Item $index')), - options: CarouselOptions( + options: FlutterCarouselOptions( onPageChanged: (index, reason) { currentPage = index; }, @@ -124,9 +124,9 @@ void main() { }); test('CarouselState and PageController integration', () { - final options = CarouselOptions(); + final options = FlutterCarouselOptions(); final pageController = PageController(); - final carouselState = CarouselState( + final carouselState = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer @@ -140,8 +140,8 @@ void main() { test('CarouselState mode change integration', () { CarouselPageChangedReason? modeChangedReason; - final carouselState = CarouselState( - CarouselOptions(), + final carouselState = FlutterCarouselState( + FlutterCarouselOptions(), () {}, // onResetTimer () {}, // onResumeTimer (reason) { @@ -155,9 +155,9 @@ void main() { testWidgets('CarouselController and CarouselOptions integration', (WidgetTester tester) async { - final options = CarouselOptions(autoPlay: true); - final controller = CarouselControllerImpl(); - final state = CarouselState( + final options = FlutterCarouselOptions(autoPlay: true); + final controller = FlutterCarouselControllerImpl(); + final state = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer @@ -196,9 +196,9 @@ void main() { testWidgets('CarouselController auto play functionality', (WidgetTester tester) async { - final options = CarouselOptions(autoPlay: true); - final controller = CarouselControllerImpl(); - final state = CarouselState( + final options = FlutterCarouselOptions(autoPlay: true); + final controller = FlutterCarouselControllerImpl(); + final state = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer @@ -248,4 +248,80 @@ void main() { expect(isAutoPlayStopped, true); }); }); + + group("CarouselOptions Assertion Tests", () { + testWidgets('viewportFraction should be between 0.0 and 1.0', + (WidgetTester tester) async { + expect( + () => FlutterCarouselOptions(viewportFraction: 1.2), + throwsAssertionError, + ); + + expect( + () => FlutterCarouselOptions(viewportFraction: -0.1), + throwsAssertionError, + ); + }); + + testWidgets('initialPage should be a non-negative integer', + (WidgetTester tester) async { + expect( + () => FlutterCarouselOptions(initialPage: -1), + throwsAssertionError, + ); + }); + + testWidgets('autoPlayInterval should be greater than zero', + (WidgetTester tester) async { + expect( + () => FlutterCarouselOptions(autoPlayInterval: Duration.zero), + throwsAssertionError, + ); + }); + + testWidgets('autoPlayAnimationDuration should be greater than zero', + (WidgetTester tester) async { + expect( + () => FlutterCarouselOptions(autoPlayAnimationDuration: Duration.zero), + throwsAssertionError, + ); + }); + + testWidgets('indicatorMargin should be a non-negative value', + (WidgetTester tester) async { + expect( + () => FlutterCarouselOptions(indicatorMargin: -1.0), + throwsAssertionError, + ); + }); + + testWidgets( + 'enlargeFactor should be greater than 0 when enlargeCenterPage is true', + (WidgetTester tester) async { + expect( + () => + FlutterCarouselOptions(enlargeCenterPage: true, enlargeFactor: 0.0), + throwsAssertionError, + ); + + expect( + () => FlutterCarouselOptions( + enlargeCenterPage: true, enlargeFactor: null), + throwsAssertionError, + ); + }); + + testWidgets('enlargeFactor should be greater than 0.0', + (WidgetTester tester) async { + expect( + () => FlutterCarouselOptions(enlargeFactor: 0.0), + throwsAssertionError, + ); + + expect( + () => FlutterCarouselOptions(enlargeFactor: -0.1), + throwsAssertionError, + ); + }); + }); } diff --git a/test/flutter_carousel_widget/unit_test.dart b/test/unit_test.dart similarity index 85% rename from test/flutter_carousel_widget/unit_test.dart rename to test/unit_test.dart index 45a6571..d5e3660 100644 --- a/test/flutter_carousel_widget/unit_test.dart +++ b/test/unit_test.dart @@ -1,17 +1,13 @@ import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide CarouselController; -import 'package:flutter_carousel_widget/src/enums/center_page_enlarge_strategy.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_controller.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_options.dart'; -import 'package:flutter_carousel_widget/src/helpers/flutter_carousel_state.dart'; -import 'package:flutter_carousel_widget/src/indicators/circular_slide_indicator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_carousel_widget/src/utils/flutter_carousel_utils.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('FlutterCarousel Unit Tests', () { test('Default values of CarouselOptions should be as expected', () { - final options = CarouselOptions(); + final options = FlutterCarouselOptions(); expect(options.height, null); expect(options.aspectRatio, null); @@ -41,7 +37,7 @@ void main() { expect(options.showIndicator, true); expect(options.floatingIndicator, true); expect(options.indicatorMargin, 8.0); - expect(options.slideIndicator, isA()); + expect(options.slideIndicator, isNull); expect(options.clipBehavior, Clip.antiAlias); expect(options.scrollBehavior, null); expect(options.pageSnapping, true); @@ -52,7 +48,7 @@ void main() { }); test('CarouselOptions should accept and return custom values', () { - final options = CarouselOptions( + final options = FlutterCarouselOptions( height: 300, aspectRatio: 2.0, viewportFraction: 0.9, @@ -95,7 +91,7 @@ void main() { test( 'CarouselOptions copyWith should return new instance with updated values', () { - final options = CarouselOptions(); + final options = FlutterCarouselOptions(); final newOptions = options.copyWith( height: 400, @@ -115,8 +111,8 @@ void main() { }); test('CarouselState initialization', () { - final options = CarouselOptions(); - final carouselState = CarouselState( + final options = FlutterCarouselOptions(); + final carouselState = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer @@ -131,8 +127,8 @@ void main() { }); test('CarouselState item count', () { - final options = CarouselOptions(); - final carouselState = CarouselState( + final options = FlutterCarouselOptions(); + final carouselState = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer @@ -146,14 +142,14 @@ void main() { test('CarouselControllerImpl initialization, set state and ready completer', () async { // Create the controller - final controller = CarouselControllerImpl(); + final controller = FlutterCarouselControllerImpl(); // Verify initial state expect(controller.ready, false); // Create a mock CarouselState and assign it to the controller's state - final options = CarouselOptions(); - final state = CarouselState( + final options = FlutterCarouselOptions(); + final state = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer @@ -170,9 +166,9 @@ void main() { }); test('CarouselControllerImpl jumpToPage', () { - final options = CarouselOptions(); - final controller = CarouselControllerImpl(); - final state = CarouselState( + final options = FlutterCarouselOptions(); + final controller = FlutterCarouselControllerImpl(); + final state = FlutterCarouselState( options, () {}, // onResetTimer () {}, // onResumeTimer diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..063e7a2 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,391 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('FlutterCarousel Widget Tests', () { + testWidgets( + 'Carousel should render correctly with options and page changes on drag', + (WidgetTester tester) async { + // Build the widget + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + viewportFraction: 1.0, + height: 400, + initialPage: 0, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')) + ], + ), + ), + ), + ); + + // Check initial state + expect(find.text('Page 1'), findsOneWidget); + expect(find.text('Page 2'), findsNothing); + + // Perform the horizontal drag to change the page + final screenWidth = tester.view.physicalSize.width; + await tester.drag(find.byType(FlutterCarousel), Offset(-screenWidth, 0)); + await tester.pumpAndSettle(); + + // Verify the page has changed + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), findsOneWidget); + }); + + testWidgets('Carousel should show or hide indicator based on options', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + height: 400, + showIndicator: true, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')) + ], + ), + ), + ), + ); + + expect(find.byKey(const Key('default_indicator')), findsOneWidget); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + height: 400, + showIndicator: false, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')) + ], + ), + ), + ), + ); + + expect(find.byKey(const Key('default_indicator')), findsNothing); + }); + + testWidgets('Carousel should render correctly with initial page option', + (WidgetTester tester) async { + // Arrange + const initialPage = 2; + await tester.pumpWidget( + MaterialApp( + home: FlutterCarousel( + items: List.generate( + 5, (index) => SizedBox(child: Text('Item $index'))), + options: FlutterCarouselOptions(initialPage: initialPage), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('Item $initialPage'), findsOneWidget); + }); + + testWidgets('Carousel should auto-play and change pages', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + autoPlayInterval: const Duration(seconds: 1), + viewportFraction: 1.0, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + ], + ), + ), + ), + ); + + await tester.pump(const Duration(seconds: 1)); + await tester.pumpAndSettle(); + expect(find.text('Page 2'), findsOneWidget); + + await tester.pump(const Duration(seconds: 1)); + await tester.pumpAndSettle(); + expect(find.text('Page 3'), findsOneWidget); + }); + + testWidgets('Carousel should pause auto-play on user interaction', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + autoPlayInterval: const Duration(seconds: 2), + pauseAutoPlayOnTouch: true, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + ], + ), + ), + ), + ); + + await tester.pump(const Duration(seconds: 1)); + await tester.tap(find.byType(FlutterCarousel)); + await tester.pump(const Duration(seconds: 2)); + + expect(find.text('Page 2'), + findsOneWidget); // Shouldn't move to the next page + }); + + testWidgets('Carousel should support reverse scrolling', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + viewportFraction: 1.0, + reverse: true, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + const SizedBox(child: Text('Page 4')), + ], + ), + ), + ), + ); + + final screenWidth = tester.view.physicalSize.width; + await tester.drag(find.byType(FlutterCarousel), Offset(screenWidth, 0)); + await tester.pumpAndSettle(const Duration(seconds: 2)); + + expect(find.text('Page 4'), findsOneWidget); + }); + + testWidgets('Carousel should maintain page position when keepPage is true', + (WidgetTester tester) async { + final controller = FlutterCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + viewportFraction: 1.0, + keepPage: true, + controller: controller, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + ], + ), + ), + ), + ); + + controller.nextPage(); + await tester.pumpAndSettle(); + expect(find.text('Page 2'), findsOneWidget); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + viewportFraction: 1.0, + keepPage: true, + controller: controller, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + ], + ), + ), + ), + ); + + await tester.pumpAndSettle(); + expect(find.text('Page 2'), findsOneWidget); + }); + + testWidgets('CarouselController should control page navigation', + (WidgetTester tester) async { + final controller = FlutterCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + viewportFraction: 1.0, + height: 400, + initialPage: 0, + controller: controller, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + const SizedBox(child: Text('Page 4')), + const SizedBox(child: Text('Page 5')), + ], + ), + ), + ), + ); + + controller.animateToPage(2); + await tester.pumpAndSettle(); + expect(find.text('Page 3'), findsOneWidget); + + controller.previousPage(); + await tester.pumpAndSettle(); + expect(find.text('Page 2'), findsOneWidget); + + controller.nextPage(); + await tester.pumpAndSettle(); + expect(find.text('Page 3'), findsOneWidget); + }); + + testWidgets('Carousel should support vertical scrolling', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + options: FlutterCarouselOptions( + scrollDirection: Axis.vertical, + viewportFraction: 1.0, + ), + items: [ + const Text('Page 1'), + const Text('Page 2'), + const Text('Page 3'), + ], + ), + ), + ), + ); + + // Ensure widgets are rendered before performing any action + await tester.pumpAndSettle(); + + final screenHeight = tester.view.physicalSize.height; + await tester.drag( + find.byType(FlutterCarousel), + Offset(0, -screenHeight * 0.5), + ); + await tester.pumpAndSettle(); + + // Assert Page 2 is displayed after the drag + expect(find.text('Page 2'), findsOneWidget); + }); + + testWidgets( + 'Carousel should support snapping when pageSnapping is true and false', + (WidgetTester tester) async { + final screenWidth = tester.view.physicalSize.width; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + key: const ValueKey('page_snapping_true_carousel'), + options: FlutterCarouselOptions( + viewportFraction: 1.0, + pageSnapping: true, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + ], + ), + ), + ), + ); + + // Ensure widgets are rendered before performing any action + await tester.pumpAndSettle(); + + await tester.drag( + find.byKey(const ValueKey('page_snapping_true_carousel')), + Offset(-screenWidth * 0.25, 0), + ); + + // Ensure all animations settle + await tester.pumpAndSettle(); + + // Debug output to check if Page 2 is found + final page2Finder = find.text('Page 2'); + print('Page 2 found: ${page2Finder.evaluate().isNotEmpty}'); + + // Assert Page 2 is displayed after the drag + expect(find.text('Page 2'), findsOneWidget); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FlutterCarousel( + key: const ValueKey('page_snapping_false_carousel'), + options: FlutterCarouselOptions( + viewportFraction: 1.0, + pageSnapping: false, + ), + items: [ + const SizedBox(child: Text('Page 1')), + const SizedBox(child: Text('Page 2')), + const SizedBox(child: Text('Page 3')), + ], + ), + ), + ), + ); + + // Ensure widgets are rendered before performing any action + await tester.pumpAndSettle(); + + await tester.drag( + find.byKey(const ValueKey('page_snapping_false_carousel')), + Offset(-screenWidth * 0.25, 0), + ); + + // Ensure all animations settle + await tester.pumpAndSettle(); + + // Shouldn't snap to the next page, so Page 1 + // should still be partially visible + expect(find.text('Page 1'), findsOneWidget); + expect(find.text('Page 2'), findsOneWidget); + }); + }); +}