diff --git a/.gitignore b/.gitignore index 3b67647..bcae468 100644 --- a/.gitignore +++ b/.gitignore @@ -10,12 +10,6 @@ .svn/ migrate_working_dir/ -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - # Flutter/Dart/Pub related /pubspec.lock **/doc/api/ @@ -24,4 +18,70 @@ migrate_working_dir/ build/ # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +# IntelliJ related +# Source: https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..6bbe2ae --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..af74dbf --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,318 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..4bec4ea --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,117 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5c94cb2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..04db2f0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..931b96c --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/AI_Playlist_Generator.xml b/.idea/runConfigurations/AI_Playlist_Generator.xml new file mode 100644 index 0000000..d66593c --- /dev/null +++ b/.idea/runConfigurations/AI_Playlist_Generator.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/AirBnb.xml b/.idea/runConfigurations/AirBnb.xml new file mode 100644 index 0000000..b0fc46d --- /dev/null +++ b/.idea/runConfigurations/AirBnb.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Bottom_Bar_Visibility.xml b/.idea/runConfigurations/Bottom_Bar_Visibility.xml new file mode 100644 index 0000000..6f47469 --- /dev/null +++ b/.idea/runConfigurations/Bottom_Bar_Visibility.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Cupertino_Modal_Sheet.xml b/.idea/runConfigurations/Cupertino_Modal_Sheet.xml new file mode 100644 index 0000000..868fa85 --- /dev/null +++ b/.idea/runConfigurations/Cupertino_Modal_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Declarative_Modal_Sheet.xml b/.idea/runConfigurations/Declarative_Modal_Sheet.xml new file mode 100644 index 0000000..362e4a0 --- /dev/null +++ b/.idea/runConfigurations/Declarative_Modal_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Declarative_Navigation_Sheet.xml b/.idea/runConfigurations/Declarative_Navigation_Sheet.xml new file mode 100644 index 0000000..3a9449a --- /dev/null +++ b/.idea/runConfigurations/Declarative_Navigation_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Draggable_Sheet.xml b/.idea/runConfigurations/Draggable_Sheet.xml new file mode 100644 index 0000000..e06b0fd --- /dev/null +++ b/.idea/runConfigurations/Draggable_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Imperative_Modal_Sheet.xml b/.idea/runConfigurations/Imperative_Modal_Sheet.xml new file mode 100644 index 0000000..7c4c63c --- /dev/null +++ b/.idea/runConfigurations/Imperative_Modal_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Imperative_Navigation_Sheet.xml b/.idea/runConfigurations/Imperative_Navigation_Sheet.xml new file mode 100644 index 0000000..f1b68a0 --- /dev/null +++ b/.idea/runConfigurations/Imperative_Navigation_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Keyboard_Dismiss_Behavior.xml b/.idea/runConfigurations/Keyboard_Dismiss_Behavior.xml new file mode 100644 index 0000000..5d09cc4 --- /dev/null +++ b/.idea/runConfigurations/Keyboard_Dismiss_Behavior.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Navigation_Sheet_And_Keyboard.xml b/.idea/runConfigurations/Navigation_Sheet_And_Keyboard.xml new file mode 100644 index 0000000..516df85 --- /dev/null +++ b/.idea/runConfigurations/Navigation_Sheet_And_Keyboard.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Safari.xml b/.idea/runConfigurations/Safari.xml new file mode 100644 index 0000000..72f737e --- /dev/null +++ b/.idea/runConfigurations/Safari.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/ScrollableSheet_And_PageView.xml b/.idea/runConfigurations/ScrollableSheet_And_PageView.xml new file mode 100644 index 0000000..ddb51e9 --- /dev/null +++ b/.idea/runConfigurations/ScrollableSheet_And_PageView.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Scrollable_Sheet.xml b/.idea/runConfigurations/Scrollable_Sheet.xml new file mode 100644 index 0000000..8ff54dd --- /dev/null +++ b/.idea/runConfigurations/Scrollable_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Sheet_Content_Scaffold.xml b/.idea/runConfigurations/Sheet_Content_Scaffold.xml new file mode 100644 index 0000000..0cef1ef --- /dev/null +++ b/.idea/runConfigurations/Sheet_Content_Scaffold.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Sheet_Controller.xml b/.idea/runConfigurations/Sheet_Controller.xml new file mode 100644 index 0000000..ec367de --- /dev/null +++ b/.idea/runConfigurations/Sheet_Controller.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Sheet_Draggable.xml b/.idea/runConfigurations/Sheet_Draggable.xml new file mode 100644 index 0000000..689adf6 --- /dev/null +++ b/.idea/runConfigurations/Sheet_Draggable.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Sheet_Physics.xml b/.idea/runConfigurations/Sheet_Physics.xml new file mode 100644 index 0000000..528edd5 --- /dev/null +++ b/.idea/runConfigurations/Sheet_Physics.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Sheet_Position_Driven_Animation.xml b/.idea/runConfigurations/Sheet_Position_Driven_Animation.xml new file mode 100644 index 0000000..ff8ef49 --- /dev/null +++ b/.idea/runConfigurations/Sheet_Position_Driven_Animation.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/TextField_With_Multiple_Stops.xml b/.idea/runConfigurations/TextField_With_Multiple_Stops.xml new file mode 100644 index 0000000..75822d5 --- /dev/null +++ b/.idea/runConfigurations/TextField_With_Multiple_Stops.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Todo_List.xml b/.idea/runConfigurations/Todo_List.xml new file mode 100644 index 0000000..40962ca --- /dev/null +++ b/.idea/runConfigurations/Todo_List.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/iOS_Style_Modal_Navigation_Sheet.xml b/.idea/runConfigurations/iOS_Style_Modal_Navigation_Sheet.xml new file mode 100644 index 0000000..8e489c0 --- /dev/null +++ b/.idea/runConfigurations/iOS_Style_Modal_Navigation_Sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/smooth_sheets.iml b/.idea/smooth_sheets.iml new file mode 100644 index 0000000..3144690 --- /dev/null +++ b/.idea/smooth_sheets.iml @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 2176703..4fffd1f 100644 --- a/README.md +++ b/README.md @@ -5,24 +5,16 @@ **smooth_sheets** offers modal and persistent sheet widgets for Flutter apps. The key features are: - **Smooth motion**: The sheets respond to user interaction with smooth, graceful motion. -- **Highly flexible**: Not restricted to a specific design. Both modal and persistent styles are - supp/orted, as well as scrollable and non-scrollable widgets. -- **Supports nested navigation**: A sheet is able to have multiple pages and to navigate between the - pages with motion animation for transitions. -- **Works with imperative & declarative Navigator API**: No special navigation mechanism is - required. The traditional ways such as `Navigator.push` is supported and it works with Navigator - 2.0 packages like go_route as well. +- **Highly flexible**: Not restricted to a specific design. Both modal and persistent styles are supported, as well as scrollable and non-scrollable widgets. +- **Supports nested navigation**: A sheet is able to have multiple pages and to navigate between the pages with motion animation for transitions. +- **Works with imperative & declarative Navigator API**: No special navigation mechanism is required. The traditional ways such as `Navigator.push` is supported and it works with Navigator 2.0 packages like go_route as well. - **iOS flavor**: The modal sheets in the style of iOS 15 are supported.
## For developers using Flutter 3.24+ -If your project uses Flutter 3.24.0 or later, we recommend using the pre-release versions -named `1.0.0-f324.x.x.x`. While you can still use the non-pre-release versions (e.g., `0.9.4`) with -Flutter 3.24+, you may encounter issues related to the `PopScope` widget due to a breaking change in -Flutter 3.24. There are no functional or API differences between the pre-release and non-pre-release -versions, except that the pre-release versions require Flutter 3.24.0 or later. +If your project uses Flutter 3.24.0 or later, we recommend using the pre-release versions named `1.0.0-f324.x.x.x`. While you can still use the non-pre-release versions (e.g., `0.9.4`) with Flutter 3.24+, you may encounter issues related to the `PopScope` widget due to a breaking change in Flutter 3.24. There are no functional or API differences between the pre-release and non-pre-release versions, except that the pre-release versions require Flutter 3.24.0 or later. ```yaml dependencies: @@ -35,31 +27,19 @@ dependencies:
Background -This package previously used the `Route.onPopInvoked` method to invoke `PopScope.onPopInvoked` -callbacks when users performed a swipe-to-dismiss gesture. However, these methods were deprecated in -Flutter 3.24.0 as part of -a [breaking change](https://docs.flutter.dev/release/breaking-changes/popscope-with-result) related -to the `PopScope` widget. The problem is that `ModalRoute.onPopInvoked`, which was an override -of `Route.onPopInvoked` and where `PopScope.onPopInvoked` callbacks were actually invoked, was -removed. As a result, the `PopScope.onPopInvoked` callback is no longer invoked in Flutter 3.24+. -These changes led to issues such as [#233](https://github.com/fujidaiti/smooth_sheets/issues/233). - -The only possible solution was to replace `Route.onPopInvoked` with `Route.onPopInvokedWithResult`, -which was introduced in Flutter 3.24.0. However, migrating to the new API would require increasing -the lower bound of the SDK constraint to 3.24.0. For those using an SDK version lower than 3.24, -this change would be a significant breaking change. Ultimately, we decided to publish different -versions for different SDK constraints to maintain backward compatibility. +This package previously used the `Route.onPopInvoked` method to invoke `PopScope.onPopInvoked` callbacks when users performed a swipe-to-dismiss gesture. However, these methods were deprecated in Flutter 3.24.0 as part of a [breaking change](https://docs.flutter.dev/release/breaking-changes/popscope-with-result) related to the `PopScope` widget. The problem is that `ModalRoute.onPopInvoked`, which was an override of `Route.onPopInvoked` and where `PopScope.onPopInvoked` callbacks were actually invoked, was removed. As a result, the `PopScope.onPopInvoked` callback is no longer invoked in Flutter 3.24+. These changes led to issues such as [#233](https://github.com/fujidaiti/smooth_sheets/issues/233). + +The only possible solution was to replace `Route.onPopInvoked` with `Route.onPopInvokedWithResult`, which was introduced in Flutter 3.24.0. However, migrating to the new API would require increasing the lower bound of the SDK constraint to 3.24.0. For those using an SDK version lower than 3.24, this change would be a significant breaking change. Ultimately, we decided to publish different versions for different SDK constraints to maintain backward compatibility.

## Migration guide -- [0.8.x to 0.9.x](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.9.x.md) - 🆕 -- [0.7.x to 0.8.x](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.8.x.md) +- [0.9.x to 0.10.x](https://github.com/fujidaiti/smooth_sheets/blob/main/migrations/migration-guide-0.10.x.md) 🆕 +- [0.8.x to 0.9.x](https://github.com/fujidaiti/smooth_sheets/blob/main/migrations/migration-guide-0.9.x.md) -See [here](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/) for older versions. +See [here](https://github.com/fujidaiti/smooth_sheets/tree/main/migrations) for older versions.
@@ -131,11 +111,7 @@ See [here](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/) for older ## Why use this? -There are few packages on pub.dev that supports nested navigation with motion animation for page -transitions. One of the great choices for this usecase -is [wolt_modal_sheet](https://github.com/woltapp/wolt_modal_sheet), which this package is inspired -by. Although smooth_sheet has similar features with wolt_modal_sheet, it does not intended to be a -replacement of that package. Here is some differences between those 2 packages: +There are few packages on pub.dev that supports nested navigation with motion animation for page transitions. One of the great choices for this usecase is [wolt_modal_sheet](https://github.com/woltapp/wolt_modal_sheet), which this package is inspired by. Although smooth_sheet has similar features with wolt_modal_sheet, it does not intended to be a replacement of that package. Here is some differences between those 2 packages: | | wolt_modal_sheet | smooth_sheets | |:----------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------:| @@ -151,27 +127,21 @@ replacement of that package. Here is some differences between those 2 packages: Several resources are available for learning the functionalities of this package. -- Tutorials: - See [example/lib/tutorial/](https://github.com/fujidaiti/smooth_sheets/tree/main/example/lib/tutorial) - to learn the basic usage of the core components. -- Showcases: More practical examples are available - in [example/lib/showcase/](https://github.com/fujidaiti/smooth_sheets/tree/main/example/lib/showcase). +- Tutorials: See [example/lib/tutorial/](https://github.com/fujidaiti/smooth_sheets/tree/main/example/lib/tutorial) to learn the basic usage of the core components. +- Showcases: More practical examples are available in [example/lib/showcase/](https://github.com/fujidaiti/smooth_sheets/tree/main/example/lib/showcase). - Documentation: WORK IN PROGRESS! Please see the source code for a while.
## Ingredients -This section provides descriptions for each core component and links to related resources for -further learning. +This section provides descriptions for each core component and links to related resources for further learning.
### SheetAnchor -SheetAnchor represents the visible height of the sheet. It is used in a variety of situations, for -example, to specify how much area of a sheet is initially visible at first build, or to limit the -range of sheet dragging. +SheetAnchor represents the visible height of the sheet. It is used in a variety of situations, for example, to specify how much area of a sheet is initially visible at first build, or to limit the range of sheet dragging.
@@ -182,15 +152,11 @@ range of sheet dragging. -A sheet that can be dragged. The height will be equal to the content. The behavior of the sheet when -over-dragged or under-dragged is determined by [SheetPhysics](#sheetphysics). Note that this widget -does not work with scrollable widgets. Instead, use [ScrollableSheet](#scrollablesheet) for this -usecase. +A sheet that can be dragged. The height will be equal to the content. The behavior of the sheet when over-dragged or under-dragged is determined by [SheetPhysics](#sheetphysics). Note that this widget does not work with scrollable widgets. Instead, use [ScrollableSheet](#scrollablesheet) for this usecase. See also: -- [draggable_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/draggable_sheet.dart) - for basic usage. +- [draggable_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/draggable_sheet.dart) for basic usage.
@@ -201,14 +167,11 @@ See also: -A sheet that is similar to [DraggableSheet](#draggablesheet), but specifically designed to be -integrated with scrollable widgets. It will begin to be dragged when the content is over-scrolled or -under-scrolled. +A sheet that is similar to [DraggableSheet](#draggablesheet), but specifically designed to be integrated with scrollable widgets. It will begin to be dragged when the content is over-scrolled or under-scrolled. See also: -- [scrollable_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/scrollable_sheet.dart) - for basic usage. +- [scrollable_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/scrollable_sheet.dart) for basic usage.
@@ -219,17 +182,12 @@ See also: -A sheet that is able to have multiple pages and performs graceful motion animation when page -transitions. It supports both of imperative Navigator API such as `Navigator.push`, and declarative -API (Navigator 2.0). +A sheet that is able to have multiple pages and performs graceful motion animation when page transitions. It supports both of imperative Navigator API such as `Navigator.push`, and declarative API (Navigator 2.0). See also: -- [declarative_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/declarative_navigation_sheet.dart), - tutorial of integration with Navigator 2.0 using [go_router](https://pub.dev/packages/go_router) - package. -- [imperative_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/imperative_navigation_sheet.dart), - a tutorial of integration with imperative Navigator API. +- [declarative_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/declarative_navigation_sheet.dart), tutorial of integration with Navigator 2.0 using [go_router](https://pub.dev/packages/go_router) package. +- [imperative_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/imperative_navigation_sheet.dart), a tutorial of integration with imperative Navigator API.
@@ -239,37 +197,22 @@ See also: - - - -A sheet can be displayed as a modal sheet using ModalSheetRoute for imperative navigation, or -ModalSheetPage for declarative navigation. To enable the *swipe-to-dismiss* action, which allows the -user to dismiss the sheet by a swiping-down gesture, set `swipeDismissible` to true. +A sheet can be displayed as a modal sheet using ModalSheetRoute for imperative navigation, or ModalSheetPage for declarative navigation. To enable the *swipe-to-dismiss* action, which allows the user to dismiss the sheet by a swiping-down gesture, set `swipeDismissible` to true.
-Furthermore, [the modal sheets in the style of iOS 15](https://medium.com/surf-dev/bottomsheet-in-ios-15-uisheetpresentationcontroller-and-its-capabilities-5e913661c9f) -are also supported. For imperative navigation, use CupertinoModalSheetRoute, and for declarative -navigation, use CupertinoModalSheetPage, respectively. +Furthermore, [the modal sheets in the style of iOS 15](https://medium.com/surf-dev/bottomsheet-in-ios-15-uisheetpresentationcontroller-and-its-capabilities-5e913661c9f) are also supported. For imperative navigation, use CupertinoModalSheetRoute, and for declarative navigation, use CupertinoModalSheetPage, respectively. See also: -- [SwipeDismissSensitivity](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SwipeDismissSensitivity-class.html), - which can be used to tweak the sensitivity of the swipe-to-dismiss action. -- [declarative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/declarative_modal_sheet.dart), - a tutorial of integration with declarative navigation - using [go_router](https://pub.dev/packages/go_router) package. -- [imperative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/imperative_modal_sheet.dart), - a tutorial of integration with imperative Navigator API. -- [cupertino_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/cupertino_modal_sheet.dart), - a tutorial of iOS style modal sheets. -- [ios_style_declarative_modal_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart), - an example of iOS-style modal NavigationSheet with go_router. -- [showcase/todo_list](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/showcase/todo_list), - which uses SheetDismissible to show a confirmation dialog when the user tries to discard the todo - editing sheet without saving the content. +- [SwipeDismissSensitivity](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SwipeDismissSensitivity-class.html), which can be used to tweak the sensitivity of the swipe-to-dismiss action. +- [declarative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/declarative_modal_sheet.dart), a tutorial of integration with declarative navigation using [go_router](https://pub.dev/packages/go_router) package. +- [imperative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/imperative_modal_sheet.dart), a tutorial of integration with imperative Navigator API. +- [cupertino_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/cupertino_modal_sheet.dart), a tutorial of iOS style modal sheets. +- [ios_style_declarative_modal_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart), an example of iOS-style modal NavigationSheet with go_router. +- [showcase/todo_list](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/showcase/todo_list), which uses SheetDismissible to show a confirmation dialog when the user tries to discard the todo editing sheet without saving the content.
@@ -280,26 +223,18 @@ See also: -A physics determines how the sheet will behave when over-dragged or under-dragged, or when the user -stops dragging. There are 3 predefined physics: +A physics determines how the sheet will behave when over-dragged or under-dragged, or when the user stops dragging. There are 3 predefined physics: - ClampingSheetPhysics: Prevents the sheet from reaching beyond the content bounds. -- BouncingSheetPhysics: Allows the sheet to go beyond the content bounds, but then bounce the sheet - back to the edge of those bounds. - Use [BouncingBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/BouncingBehavior-class.html) - and its subclasses to tweak the bouncing behavior. -- SnappingSheetPhysics: Automatically snaps the sheet to a certain extent when the user stops - dragging. +- BouncingSheetPhysics: Allows the sheet to go beyond the content bounds, but then bounce the sheet back to the edge of those bounds. Use [BouncingBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/BouncingBehavior-class.html) and its subclasses to tweak the bouncing behavior. +- SnappingSheetPhysics: Automatically snaps the sheet to a certain extent when the user stops dragging. -These physics can be combined to create more complex behavior (e.g. bouncing behavior + snapping -behavior). +These physics can be combined to create more complex behavior (e.g. bouncing behavior + snapping behavior). See also: -- [sheet_physics.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_physics.dart) - for basic usage. -- [bouncing_behaviors.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/bouncing_behaviors.dart), - which shows how to tweak the bouncing behavior of BouncingSheetPhysics. +- [sheet_physics.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_physics.dart) for basic usage. +- [bouncing_behaviors.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/bouncing_behaviors.dart), which shows how to tweak the bouncing behavior of BouncingSheetPhysics.
@@ -310,13 +245,11 @@ See also: -Like [ScrollController](https://api.flutter.dev/flutter/widgets/ScrollController-class.html) for -scrollable widget, the SheetController can be used to animate or observe the extent of a sheet. +Like [ScrollController](https://api.flutter.dev/flutter/widgets/ScrollController-class.html) for scrollable widget, the SheetController can be used to animate or observe the extent of a sheet. See also: -- [sheet_controller.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_controller.dart) - for basic usage. +- [sheet_controller.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_controller.dart) for basic usage.
@@ -327,20 +260,14 @@ See also: -A special kind of [Scaffold](https://api.flutter.dev/flutter/material/Scaffold-class.html) designed -for use in a sheet. It has slots for an app bar and a bottom bar, similar to Scaffold. However, it -differs in that its height reduces to fit the content widget. +A special kind of [Scaffold](https://api.flutter.dev/flutter/material/Scaffold-class.html) designed for use in a sheet. It has slots for an app bar and a bottom bar, similar to Scaffold. However, it differs in that its height reduces to fit the content widget. See also: -- [SheetContentScaffold](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetContentScaffold-class.html), - the API documentation. -- [BottomBarVisibility](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/BottomBarVisibility-class.html), - which can be used to control the visibility of the bottom bar based on the sheet position. -- [tutorial/sheet_content_scaffold.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_content_scaffold.dart), - which shows the basic usage of SheetContentScaffold. -- [tutorial/bottom_bar_visibility.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/bottom_bar_visibility.dart), - which shows the basic usage of BottomBarVisibility widgets. +- [SheetContentScaffold](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetContentScaffold-class.html), the API documentation. +- [BottomBarVisibility](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/BottomBarVisibility-class.html), which can be used to control the visibility of the bottom bar based on the sheet position. +- [tutorial/sheet_content_scaffold.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_content_scaffold.dart), which shows the basic usage of SheetContentScaffold. +- [tutorial/bottom_bar_visibility.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/bottom_bar_visibility.dart), which shows the basic usage of BottomBarVisibility widgets.
@@ -350,18 +277,11 @@ See also: -SheetDraggable enables its child widget to act as a drag handle for the sheet. Typically, you will -want to use this widget when placing non-scrollable widget(s) in -a [ScrollableSheet](#scrollablesheet), since it only works with scrollable widgets, so you can't -drag the sheet by touching a non-scrollable area. Try removing SheetDraggable and you will see that -the drag handle doesn't work as it should. -Note that SheetDraggable is not needed when using DraggableSheet since it implicitly wraps the child -widget with SheetDraggable. +SheetDraggable enables its child widget to act as a drag handle for the sheet. Typically, you will want to use this widget when placing non-scrollable widget(s) in a [ScrollableSheet](#scrollablesheet), since it only works with scrollable widgets, so you can't drag the sheet by touching a non-scrollable area. Try removing SheetDraggable and you will see that the drag handle doesn't work as it should. Note that SheetDraggable is not needed when using DraggableSheet since it implicitly wraps the child widget with SheetDraggable. See also: -- [sheet_draggable.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_draggable.dart) - for basic usage. +- [sheet_draggable.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_draggable.dart) for basic usage.
@@ -371,51 +291,37 @@ See also: -It is easy to create sheet extent driven animations by using SheetPositionDrivenAnimation, a special -kind of [Animation](https://api.flutter.dev/flutter/animation/Animation-class.html) whose value -changes from 0 to 1 as the sheet extent changes from 'startExtent' to 'endExtent'. +It is easy to create sheet extent driven animations by using SheetPositionDrivenAnimation, a special kind of [Animation](https://api.flutter.dev/flutter/animation/Animation-class.html) whose value changes from 0 to 1 as the sheet extent changes from 'startExtent' to 'endExtent'. See also: -- [sheet_position_driven_animation](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_position_driven_animation.dart) - for basic usage. -- [airbnb_mobile_app.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/showcase/airbnb_mobile_app.dart), - which show how SheetPositionDrivenAnimation can be used to hide the bottom navigation bar and a - FAB when the sheet is dragged down, and to show them when the sheet is dragged up again. +- [sheet_position_driven_animation](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/sheet_position_driven_animation.dart) for basic usage. +- [airbnb_mobile_app.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/showcase/airbnb_mobile_app.dart), which show how SheetPositionDrivenAnimation can be used to hide the bottom navigation bar and a FAB when the sheet is dragged down, and to show them when the sheet is dragged up again.
### SheetNotification -A sheet dispatches -a [SheetNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetNotification-class.html) -when its extent changes. This can be used to observe the extent of a descendant sheet from an -ancestor widget. +A sheet dispatches a [SheetNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetNotification-class.html) when its extent changes. This can be used to observe the extent of a descendant sheet from an ancestor widget. ```dart -NotificationListener -( -onNotification: (notification) { -debugPrint('${notification.metrics}'); -return false; -}, -child: DraggableSheet(...), +NotificationListener( + onNotification: (notification) { + debugPrint('${notification.metrics}'); + return false; + }, + child: DraggableSheet( + ... + ), ), ``` See also: -- [SheetDragUpdateNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetDragUpdateNotification-class.html), - which is dispatched when the sheet is dragged by the user. -- [SheetUpdateNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetUpdateNotification-class.html), - which is dispatched when the sheet extent is updated by other than user interaction such as - animation. -- [SheetOverflowNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetOverflowNotification-class.html), - which is dispatched when the user tries to drag the sheet beyond its draggable bounds but the - sheet has not changed its extent because its [SheetPhysics](#sheetphysics) does not allow it to - be. -- [NotificationListener](https://api.flutter.dev/flutter/widgets/NotificationListener-class.html), - which can be used to listen for the notifications in a subtree. +- [SheetDragUpdateNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetDragUpdateNotification-class.html), which is dispatched when the sheet is dragged by the user. +- [SheetUpdateNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetUpdateNotification-class.html), which is dispatched when the sheet extent is updated by other than user interaction such as animation. +- [SheetOverflowNotification](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetOverflowNotification-class.html), which is dispatched when the user tries to drag the sheet beyond its draggable bounds but the sheet has not changed its extent because its [SheetPhysics](#sheetphysics) does not allow it to be. +- [NotificationListener](https://api.flutter.dev/flutter/widgets/NotificationListener-class.html), which can be used to listen for the notifications in a subtree.
@@ -426,43 +332,31 @@ See also:
-[SheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetKeyboardDismissBehavior-class.html) -determines when the sheet should dismiss the on-screen keyboard. This feature is similar -to [ScrollViewKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/ScrollViewKeyboardDismissBehavior.html) -for scrollable widgets. +[SheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/SheetKeyboardDismissBehavior-class.html) determines when the sheet should dismiss the on-screen keyboard. This feature is similar to [ScrollViewKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/ScrollViewKeyboardDismissBehavior.html) for scrollable widgets. -Although it is easy to create custom behaviors by implementing SheetKeyboardDismissBehavior -interface, there are 3 types of predefined behaviors for convenience. +Although it is easy to create custom behaviors by implementing SheetKeyboardDismissBehavior interface, there are 3 types of predefined behaviors for convenience. -- [DragSheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/DragSheetKeyboardDismissBehavior-class.html), - which always dismisses the on-screen keyboard when the sheet is dragged. -- [DragDownSheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/DragDownSheetKeyboardDismissBehavior-class.html), - which always dismisses the on-screen keyboard only when the sheet is dragged down. -- [DragUpSheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/DragUpSheetKeyboardDismissBehavior-class.html), - which always dismisses the on-screen keyboard only when the sheet is dragged up. +- [DragSheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/DragSheetKeyboardDismissBehavior-class.html), which always dismisses the on-screen keyboard when the sheet is dragged. +- [DragDownSheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/DragDownSheetKeyboardDismissBehavior-class.html), which always dismisses the on-screen keyboard only when the sheet is dragged down. +- [DragUpSheetKeyboardDismissBehavior](https://pub.dev/documentation/smooth_sheets/latest/smooth_sheets/DragUpSheetKeyboardDismissBehavior-class.html), which always dismisses the on-screen keyboard only when the sheet is dragged up. See also: -- [tutorial/keyboard_dismiss_behavior.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/keyboard_dismiss_behavior.dart) - for basic usage. +- [tutorial/keyboard_dismiss_behavior.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/example/lib/tutorial/keyboard_dismiss_behavior.dart) for basic usage.
## Questions -If you have any questions, feel free to ask them -on [the discussions page](https://github.com/fujidaiti/smooth_sheets/discussions). +If you have any questions, feel free to ask them on [the discussions page](https://github.com/fujidaiti/smooth_sheets/discussions).
## Contributing -Contributions are what make the open source community such an amazing place to learn, inspire, and -create. Any contributions you make are **greatly appreciated**. +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. -If you have a suggestion that would make this better, please fork the repo and create a pull -request. You can also simply open an issue with the tag "enhancement". -Don't forget to give the project a star! Thanks again! +If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again! 1. Fork the Project 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) @@ -480,4 +374,3 @@ Don't forget to give the project a star! Thanks again! - [norelease.dev](https://pub.dev/publishers/norelease.dev/packages)
- diff --git a/example/pubspec.lock b/example/pubspec.lock index acf3ad1..304480e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -195,7 +195,7 @@ packages: path: ".." relative: true source: path - version: "0.9.4" + version: "0.10.0" source_span: dependency: transitive description: diff --git a/lib/src/modal/modal_sheet.dart b/lib/src/modal/modal_sheet.dart index 78a3491..d4492dd 100644 --- a/lib/src/modal/modal_sheet.dart +++ b/lib/src/modal/modal_sheet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../foundation/sheet_drag.dart'; @@ -150,12 +151,24 @@ class ModalSheetRoute extends PageRoute with ModalSheetRouteMixin { mixin ModalSheetRouteMixin on ModalRoute { bool get swipeDismissible; + Curve get transitionCurve; + SwipeDismissSensitivity get swipeDismissSensitivity; @override bool get opaque => false; + /// The curve used for the transition animation. + /// + /// In the middle of a dismiss gesture drag, + /// this returns [Curves.linear] to match the finger motion. + @nonVirtual + @visibleForTesting + Curve get effectiveCurve => (navigator?.userGestureInProgress ?? false) + ? Curves.linear + : transitionCurve; + /// Lazily initialized in case `swipeDismissible` is set to false. late final _swipeDismissibleController = _SwipeDismissibleController( route: this, @@ -188,13 +201,9 @@ mixin ModalSheetRouteMixin on ModalRoute { Widget child, ) { final transitionTween = Tween(begin: const Offset(0, 1), end: Offset.zero); - // In the middle of a dismiss gesture drag, - // let the transition be linear to match finger motions. - final curve = - navigator!.userGestureInProgress ? Curves.linear : this.transitionCurve; return SlideTransition( position: animation.drive( - transitionTween.chain(CurveTween(curve: curve)), + transitionTween.chain(CurveTween(curve: effectiveCurve)), ), child: child, ); @@ -390,19 +399,43 @@ class _SwipeDismissibleController with SheetGestureProxyMixin { )); } - if (transitionController.isAnimating) { - // Keep the userGestureInProgress in true state so we don't change the - // curve of the page transition mid-flight since the route's transition - // depends on userGestureInProgress. + // Reset the transition animation curve back to the default from linear + // indirectly, by resetting the userGestureInProgress flag. + // It is "indirect" because ModalSheetRouteMixin.effectiveCurve returns + // the linear curve when the userGestureInProgress flag is set to true. + // + // If the transition animation has not settled at either the start or end, + // delay resetting the userGestureInProgress until the animation completes + // to ensure the effectiveCurve remains linear during the animation, + // matching the user's swipe motion. This is important to prevent the sheet + // from jerking when the user swipes it down. + // See https://github.com/fujidaiti/smooth_sheets/issues/250. + // + // Note: We cannot use AnimationController.isAnimating here to determine if + // the transition animation is running, because, in Navigator 2.0, + // the pop animation may not have started at this point even if + // Navigator.pop() is called to pop the modal route. + // + // The following sequence of events illustrates why: + // 1. Calling Navigator.pop() updates the internal page stack, triggering + // a rebuild of the Navigator. Note that the transition animation + // does not start here, so AnimationController.isAnimating returns false. + // 2. The Navigator rebuilds with the new page stack. + // 3. The modal route is removed from the Navigator's subtree. + // 4. Route.didPop() is called, initiating the pop transition animation + // by calling AnimationController.reverse(). + if (transitionController.isCompleted || transitionController.isDismissed) { + _isUserGestureInProgress = false; + } else { late final AnimationStatusListener animationStatusCallback; - animationStatusCallback = (_) { - _isUserGestureInProgress = false; - transitionController.removeStatusListener(animationStatusCallback); + animationStatusCallback = (status) { + if (status == AnimationStatus.completed || + status == AnimationStatus.dismissed) { + _isUserGestureInProgress = false; + transitionController.removeStatusListener(animationStatusCallback); + } }; transitionController.addStatusListener(animationStatusCallback); - } else { - // Otherwise, reset the userGestureInProgress state immediately. - _isUserGestureInProgress = false; } if (invokePop) { diff --git a/pubspec.yaml b/pubspec.yaml index 660a56f..6a1336e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,5 +24,6 @@ dev_dependencies: build_runner: ^2.4.9 flutter_test: sdk: flutter + go_router: ^14.2.3 mockito: ^5.4.4 pedantic_mono: ^1.23.0 diff --git a/test/modal/modal_sheet_test.dart b/test/modal/modal_sheet_test.dart index 5e3fba1..4291064 100644 --- a/test/modal/modal_sheet_test.dart +++ b/test/modal/modal_sheet_test.dart @@ -1,37 +1,94 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; import 'package:smooth_sheets/smooth_sheets.dart'; +class _Boilerplate extends StatelessWidget { + const _Boilerplate({ + required this.modalRoute, + }); + + final ModalSheetRoute modalRoute; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Builder( + builder: (context) { + return Scaffold( + body: Center( + child: ElevatedButton( + onPressed: () { + Navigator.push(context, modalRoute); + }, + child: const Text('Open modal'), + ), + ), + ); + }, + ), + ); + } +} + +class _BoilerplateWithGoRouter extends StatelessWidget { + const _BoilerplateWithGoRouter({ + required this.modalPage, + this.onExitModal, + }); + + final ModalSheetPage modalPage; + final FutureOr Function()? onExitModal; + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) { + return Scaffold( + body: Center( + child: ElevatedButton( + onPressed: () => context.go('/modal'), + child: const Text('Open modal'), + ), + ), + ); + }, + routes: [ + GoRoute( + path: 'modal', + pageBuilder: (context, state) => modalPage, + onExit: onExitModal != null + ? (context, state) => onExitModal!() + : null, + ), + ], + ), + ], + ), + ); + } +} + void main() { group('Swipe-to-dismiss action test', () { Widget boilerplate(SwipeDismissSensitivity sensitivity) { - return MaterialApp( - home: Builder( + return _Boilerplate( + modalRoute: ModalSheetRoute( + swipeDismissible: true, + swipeDismissSensitivity: sensitivity, builder: (context) { - return Scaffold( - body: Center( - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - ModalSheetRoute( - swipeDismissible: true, - swipeDismissSensitivity: sensitivity, - builder: (context) { - return DraggableSheet( - child: Container( - key: const Key('sheet'), - color: Colors.white, - width: double.infinity, - height: 600, - ), - ); - }, - ), - ); - }, - child: const Text('Open modal'), - ), + return DraggableSheet( + child: Container( + key: const Key('sheet'), + color: Colors.white, + width: double.infinity, + height: 600, ), ); }, @@ -155,37 +212,21 @@ void main() { setUp(() { isOnPopInvokedCalled = false; - testWidget = MaterialApp( - home: Builder( + testWidget = _Boilerplate( + modalRoute: ModalSheetRoute( + swipeDismissible: true, builder: (context) { - return Scaffold( - body: Center( - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - ModalSheetRoute( - swipeDismissible: true, - builder: (context) { - return DraggableSheet( - child: PopScope( - canPop: false, - onPopInvoked: (didPop) { - isOnPopInvokedCalled = true; - }, - child: Container( - key: const Key('sheet'), - color: Colors.white, - width: double.infinity, - height: 200, - ), - ), - ); - }, - ), - ); - }, - child: const Text('Open modal'), + return DraggableSheet( + child: PopScope( + canPop: false, + onPopInvoked: (didPop) { + isOnPopInvokedCalled = true; + }, + child: Container( + key: const Key('sheet'), + color: Colors.white, + width: double.infinity, + height: 200, ), ), ); @@ -222,4 +263,274 @@ void main() { }, ); }); + + // Regression tests for https://github.com/fujidaiti/smooth_sheets/issues/250 + // TODO: Add test cases using Navigator 2.0. + group('Transition animation status and animation curve consistency test', () { + ({ + Widget testWidget, + ModalSheetRoute modalRoute, + ValueGetter popInvoked, + }) boilerplate() { + var popInvoked = false; + final modalRoute = ModalSheetRoute( + swipeDismissible: true, + transitionCurve: Curves.easeInOut, + builder: (context) { + return DraggableSheet( + child: PopScope( + canPop: false, + onPopInvoked: (didPop) async { + if (!didPop) { + popInvoked = true; + Navigator.pop(context); + } + }, + child: Container( + key: const Key('sheet'), + color: Colors.white, + width: double.infinity, + height: 400, + ), + ), + ); + }, + ); + + return ( + testWidget: _Boilerplate(modalRoute: modalRoute), + modalRoute: modalRoute, + popInvoked: () => popInvoked, + ); + } + + testWidgets('Swipe-to-dismissed', (tester) async { + final env = boilerplate(); + await tester.pumpWidget(env.testWidget); + + await tester.tap(find.text('Open modal')); + await tester.pumpAndSettle(); + expect(env.modalRoute.animation!.isCompleted, isTrue); + expect(env.modalRoute.effectiveCurve, Curves.easeInOut); + + // Start dragging. + final gesture = await tester.press(find.byKey(const Key('sheet'))); + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + // End dragging and then a pop animation starts. + await gesture.moveBy(const Offset(0, 100)); + await gesture.up(); + expect(env.popInvoked(), isTrue); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + // Ensure that the pop animation is completed. + await tester.pumpAndSettle(); + expect(env.modalRoute.animation!.isDismissed, isTrue); + expect(env.modalRoute.effectiveCurve, Curves.easeInOut); + }); + + testWidgets('Swipe-to-dismiss canceled', (tester) async { + final env = boilerplate(); + await tester.pumpWidget(env.testWidget); + + await tester.tap(find.text('Open modal')); + await tester.pumpAndSettle(); + expect(env.modalRoute.animation!.isCompleted, isTrue); + expect(env.modalRoute.effectiveCurve, Curves.easeInOut); + + // Start dragging. + final gesture = await tester.press(find.byKey(const Key('sheet'))); + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + // Release the drag, triggering the modal + // to settle back to its original position. + await gesture.up(); + expect(env.popInvoked(), isFalse); + expect(env.modalRoute.animation!.status, AnimationStatus.forward); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute.animation!.isCompleted, isFalse); + expect(env.modalRoute.animation!.isDismissed, isFalse); + expect(env.modalRoute.effectiveCurve, Curves.linear); + + // Ensure that the pop animation is completed. + await tester.pumpAndSettle(); + expect(env.modalRoute.animation!.isCompleted, isTrue); + expect(env.modalRoute.effectiveCurve, Curves.easeInOut); + }); + }); + + // Regression tests for https://github.com/fujidaiti/smooth_sheets/issues/250 + group( + 'Transition animation status and animation curve consistency test ' + 'with Navigator 2.0', + () { + ({ + Widget testWidget, + ValueGetter?> modalRoute, + ValueGetter popInvoked, + }) boilerplate() { + var popInvoked = false; + ModalSheetRouteMixin? modalRoute; + final testWidget = _BoilerplateWithGoRouter( + onExitModal: () { + popInvoked = true; + return true; + }, + modalPage: ModalSheetPage( + swipeDismissible: true, + transitionCurve: Curves.easeInOut, + child: Builder( + builder: (context) { + modalRoute = + ModalRoute.of(context)! as ModalSheetRouteMixin; + + return DraggableSheet( + child: Container( + key: const Key('sheet'), + color: Colors.white, + width: double.infinity, + height: 400, + ), + ); + }, + ), + ), + ); + + return ( + testWidget: testWidget, + modalRoute: () => modalRoute, + popInvoked: () => popInvoked, + ); + } + + testWidgets('Swipe-to-dismissed', (tester) async { + final env = boilerplate(); + await tester.pumpWidget(env.testWidget); + + await tester.tap(find.text('Open modal')); + await tester.pumpAndSettle(); + expect(env.modalRoute()!.animation!.isCompleted, isTrue); + expect(env.modalRoute()!.effectiveCurve, Curves.easeInOut); + + // Start dragging. + final gesture = await tester.press(find.byKey(const Key('sheet'))); + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + // End dragging and then a pop animation starts. + await gesture.moveBy(const Offset(0, 100)); + await gesture.up(); + expect(env.popInvoked(), isTrue); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + // Ensure that the pop animation is completed. + await tester.pumpAndSettle(); + expect(env.modalRoute()!.animation!.isDismissed, isTrue); + expect(env.modalRoute()!.effectiveCurve, Curves.easeInOut); + }); + + testWidgets('Swipe-to-dismiss canceled', (tester) async { + final env = boilerplate(); + await tester.pumpWidget(env.testWidget); + + await tester.tap(find.text('Open modal')); + await tester.pumpAndSettle(); + expect(env.modalRoute()!.animation!.isCompleted, isTrue); + expect(env.modalRoute()!.effectiveCurve, Curves.easeInOut); + + // Start dragging. + final gesture = await tester.press(find.byKey(const Key('sheet'))); + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + await gesture.moveBy(const Offset(0, 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + // Release the drag, triggering the modal + // to settle back to its original position. + await gesture.up(); + expect(env.popInvoked(), isFalse); + expect(env.modalRoute()!.animation!.status, AnimationStatus.forward); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + await tester.pump(const Duration(milliseconds: 50)); + expect(env.modalRoute()!.animation!.isCompleted, isFalse); + expect(env.modalRoute()!.animation!.isDismissed, isFalse); + expect(env.modalRoute()!.effectiveCurve, Curves.linear); + + // Ensure that the pop animation is completed. + await tester.pumpAndSettle(); + expect(env.modalRoute()!.animation!.isCompleted, isTrue); + expect(env.modalRoute()!.effectiveCurve, Curves.easeInOut); + }); + }, + ); }