+
+
\ 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 00000000..d66593ca
--- /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 00000000..b0fc46d3
--- /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 00000000..6f47469f
--- /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 00000000..868fa853
--- /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 00000000..362e4a07
--- /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 00000000..3a9449a2
--- /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 00000000..e06b0fde
--- /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 00000000..7c4c63c6
--- /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 00000000..f1b68a0b
--- /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 00000000..5d09cc48
--- /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 00000000..516df851
--- /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 00000000..72f737e8
--- /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 00000000..ddb51e96
--- /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 00000000..8ff54dd0
--- /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 00000000..0cef1efa
--- /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 00000000..ec367dea
--- /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 00000000..689adf6b
--- /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 00000000..528edd57
--- /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 00000000..ff8ef49c
--- /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 00000000..75822d55
--- /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 00000000..40962ca3
--- /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 00000000..8e489c0f
--- /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 00000000..3144690d
--- /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 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 19ecfa80..853918bf 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -9,126 +9,154 @@
"request": "launch",
"type": "dart",
"program": "lib/showcase/ai_playlist_generator.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Airbnb Mobile App",
"request": "launch",
"type": "dart",
"program": "lib/showcase/airbnb_mobile_app.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Todo List",
"request": "launch",
"type": "dart",
"program": "lib/showcase/todo_list/main.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Safari",
"request": "launch",
"type": "dart",
"program": "lib/showcase/safari/main.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Scrollable Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/scrollable_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Draggable Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/draggable_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
+ },
+ {
+ "name": "iOS-Style Modal Navigation Sheet (with go_router)",
+ "request": "launch",
+ "type": "dart",
+ "program": "lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart",
+ "cwd": "./example"
},
{
"name": "Declarative Navigation Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/declarative_navigation_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Imperative Navigation Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/imperative_navigation_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Sheet Physics",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/sheet_physics.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Sheet Content Scaffold",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/sheet_content_scaffold.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Bottom Bar Visibility",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/bottom_bar_visibility.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Sheet Controller",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/sheet_controller.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Sheet Draggable",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/sheet_draggable.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Declarative Modal Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/declarative_modal_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Imperative Modal Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/imperative_modal_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Cupertino Modal Sheet",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/cupertino_modal_sheet.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Extent Driven Animation",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/extent_driven_animation.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
},
{
"name": "Keyboard Dismiss Behavior",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/keyboard_dismiss_behavior.dart",
- "cwd": "./cookbook"
+ "cwd": "./example"
+ },
+ {
+ "name": "Navigation Sheet and Keyboard",
+ "request": "launch",
+ "type": "dart",
+ "program": "lib/tutorial/navigation_sheet_and_keyboard.dart",
+ "cwd": "./example"
+ },
+ {
+ "name": "ScrollableSheet and PageView",
+ "request": "launch",
+ "type": "dart",
+ "program": "lib/tutorial/scrollable_pageview_sheet.dart",
+ "cwd": "./example"
+ },
+ {
+ "name": "TextField with multiple stops",
+ "request": "launch",
+ "type": "dart",
+ "program": "lib/tutorial/textfield_with_multiple_stops.dart",
+ "cwd": "./example"
}
]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..acb7585a
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dart.flutterSdkPath": ".fvm/versions/3.22.3"
+}
\ No newline at end of file
diff --git a/package/CHANGELOG.md b/CHANGELOG.md
similarity index 54%
rename from package/CHANGELOG.md
rename to CHANGELOG.md
index b520d170..7c9358d1 100644
--- a/package/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,67 @@
# Changelog
+## 0.10.0 Sep 28, 2024
+
+**This version contains breaking changes.** See the [migration guide](https://github.com/fujidaiti/smooth_sheets/blob/main/migrations/migration-guide-0.10.x.md) for more details.
+
+- Fix: Touch is ignored issue not fixed for top edge (#212)
+- Fix: Closing keyboard slows down snapping animation (#193)
+- Fix: Dynamically changing sheet height doesn't respect snapping constraints (#226)
+- Fix: Snapping effect doesn't work when closing keyboard on non-fullscreen sheet (#192)
+- Fix: Unwanted bouncing when opening or closing the on-screen keyboard on ScrollableSheet (#245)
+
+## 0.9.4 Aug 31, 2024
+
+- Add `SwipeDismissSensitivity`, a way to customize sensitivity of swipe-to-dismiss action on modal sheet (#222)
+
+## 0.9.3 Aug 19, 2024
+
+- Fix: Press-and-hold gesture in PageView doesn't stop momentum scrolling (#219)
+
+## 0.9.2 Aug 14, 2024
+
+- Fix: Keyboard visibility changes disrupt route transition animation in NavigationSheet (#215)
+
+## 0.9.1 Jul 30, 2024
+
+- Fix: Sometimes touch is ignored when scrollable sheet reaches edge (#209)
+
+## 0.9.0 Jul 24, 2024
+
+This version contains some breaking changes. See the [migration guide](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.9.x.md) for more details.
+
+- Dispatch a notification when drag is cancelled (#204)
+- Prefer composition style for SheetKeyboardDismissible (#197)
+- Fix: NavigationSheet throws assertion error when starting to scroll in list view during page transition (#199)
+- Refactor notification dispatch mechanism (#202)
+- Fix: Momentum scrolling continues despite press and hold in list view (#196)
+- Refactor: Lift sheet context up (#201)
+
+## 0.8.2 Jul 11, 2024
+
+- Fix: Opening keyboard interrupts sheet animation (#189)
+
+## 0.8.1 Jun 23, 2024
+
+- Fix: Cupertino style modal transition not working with NavigationSheet (#182)
+
+## 0.8.0 Jun 22, 2024
+
+This version contains some breaking changes. See the [migration guide](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.8.x.md) for more details.
+
+- Make stretching behavior of StretchingSheetPhysics more customizable (#171)
+- Rename "stretching" to "bouncing" (#173, #177)
+- Fix: bouncing physics doesn't respect bounds where sheet can bounce (#178)
+
+## 0.7.3 Jun 9, 2024
+
+- Fix: DropdownButton doesn't work in NavigationSheet (#139)
+
+## 0.7.2 Jun 9, 2024
+
+- Fix: Attaching SheetController to NavigationSheet causes "Null check operator used on a null value" (#151)
+- Fix: SheetController attached to NavigationSheet always emits minPixels = 0.0 (#163)
+
## 0.7.1 Jun 1, 2024
- Fix: Unwanted bouncing effect when opening keyboard on NavigationSheet (#153)
diff --git a/LICENSE b/LICENSE
index 0e89d180..9549527b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2023 Daichi Fujita
+Copyright (c) 2024 Daichi Fujita
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile.toml b/Makefile.toml
deleted file mode 100644
index 54dd28a1..00000000
--- a/Makefile.toml
+++ /dev/null
@@ -1,47 +0,0 @@
-[config]
-skip_core_tasks = true
-
-[env]
-PROJECT_ROOT = { script = ["pwd"] }
-SCRIPTS_DIR = "${PROJECT_ROOT}/scripts"
-
-[tasks.flutter-build]
-script = '''
-flutter clean
-flutter pub get
-'''
-
-[tasks.flutter-analyze]
-script = '''
-echo "Running dart format"
-dart format . -o none --set-exit-if-changed
-echo "Running dart analyze"
-dart analyze
-echo "Running disallowed patterns check"
-bash $SCRIPTS_DIR/pattern_checker.sh "*.dart" "--" "debugPrint"
-'''
-
-[tasks.flutter-check]
-run_task = { name = ['flutter-build', 'flutter-analyze'] }
-
-[tasks.build-all]
-script_runner = "@duckscript"
-script = '''
-echo "Building package"
-cd ./package
-cm_run_task flutter-build
-echo "Building cookbook"
-cd ../cookbook
-cm_run_task flutter-build
-'''
-
-[tasks.check-all]
-script_runner = "@duckscript"
-script = '''
-echo "Running flutter-check for package"
-cd ./package
-cm_run_task flutter-check
-echo "Running flutter-check for cookbook"
-cd ../cookbook
-cm_run_task flutter-check
-'''
diff --git a/README.md b/README.md
deleted file mode 120000
index 89d5ef5f..00000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-package/README.md
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..4fffd1f3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,376 @@
+# smooth_sheets
+
+[![GitHub Repo stars](https://img.shields.io/github/stars/fujidaiti/smooth_sheets)](https://github.com/fujidaiti/smooth_sheets) [![GitHub last commit (branch)](https://img.shields.io/github/last-commit/fujidaiti/smooth_sheets/main?logo=git)](https://github.com/fujidaiti/smooth_sheets/commits/main/) [![Pub Version](https://img.shields.io/pub/v/smooth_sheets)](https://pub.dev/packages/smooth_sheets) ![Pub Likes](https://img.shields.io/pub/likes/smooth_sheets) ![Pub Points](https://img.shields.io/pub/points/smooth_sheets) ![Pub Popularity](https://img.shields.io/pub/popularity/smooth_sheets)
+
+**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 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.
+
+```yaml
+dependencies:
+ # For projects that uses the pre-release versions (requires Flutter 3.24+)
+ smooth_sheets: ^1.0.0-f324.0.9.4
+ # For projects that uses the non-pre-release versions
+ smooth_sheets: ^0.9.4
+```
+
+
+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.
+
+
+
+
+## Migration guide
+
+- [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/tree/main/migrations) for older versions.
+
+
+
+## Showcases
+
+
+
+
+
+
AI Playlist Generator
+
An AI assistant that helps create a music playlist based on the user's preferences. See the cookbook for more details.
+
Key components:
+
+
NavigationSheet
+
ModalSheetPage
+
DraggableNavigationSheetPage
+
ScrollableNavigationSheetPage
+
SheetDismissible
+
+
+
+
+
+
+
Safari app
+
A practical example of ios-style modal sheets. See the cookbook for more details.
+
Key components:
+
+
CupertinoStackedTransition
+
CupertinoModalSheetRoute
+
+
+
+
+
+
+
Airbnb mobile app clone
+
A partial clone of the Airbnb mobile app. The user can drag the house list down to reveal the map behind it. See the cookbook for more details.
+
Key components:
+
+
ScrollableSheet
+
SheetPhysics
+
SheetController
+
ExtentDrivenAnimation
+
+
+
+
+
+
+
Todo List
+
A simple Todo app that shows how a sheet handles the on-screen keyboard. See the cookbook for more details.
+
Used components:
+
+
ScrollableSheet
+
SheetContentScaffold
+
SheetKeyboardDismissBehavior
+
SheetDismissible
+
StickyBottomBarVisibility
+
+
+
+
+
+
+
+
+
+
+## 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:
+
+| | wolt_modal_sheet | smooth_sheets |
+|:----------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------:|
+| Design | Based on Wolt's [design guideline](https://careers.wolt.com/en/blog/tech/an-overview-of-the-multi-page-scrollable-bottom-sheet-ui-design) | Not restricted to a specific design, fully customizable |
+| Navigation mechanism | [Manage the page index in ValueNotifier](https://github.com/woltapp/wolt_modal_sheet#usage) | Works with built-in Navigator API (both of imperative and declarative) |
+| Scrollable content | [Supported](https://github.com/woltapp/wolt_modal_sheet#scrollable-content) | Supported |
+| Persistent sheets | Not supported | Supported |
+| Screen size adaptation | [The sheet appears as a dialog on large screens](https://github.com/woltapp/wolt_modal_sheet#responsive-design) | Not supported |
+
+
+
+## Usage
+
+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).
+- 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.
+
+
+
+### 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.
+
+
+
+### DraggableSheet
+
+
+
+
+
+
+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.
+
+
+
+### ScrollableSheet
+
+
+
+
+
+
+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.
+
+
+
+### NavigationSheet
+
+
+
+
+
+
+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.
+
+
+
+### ModalSheets
+
+
+
+
+
+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.
+
+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.
+
+
+
+### SheetPhysics
+
+
+
+
+
+
+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.
+
+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.
+
+
+
+### SheetController
+
+
+
+
+
+
+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.
+
+
+
+### SheetContentScaffold
+
+
+
+
+
+
+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.
+
+
+
+### 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.
+
+
+
+### SheetPositionDrivenAnimation
+
+
+
+
+
+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.
+
+
+
+### 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.
+
+```dart
+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.
+
+
+
+### SheetKeyboardDismissBehavior
+
+
+
+
+
+
+[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.
+
+- [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.
+
+
+
+## Questions
+
+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**.
+
+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`)
+3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
+4. Push to the Branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
+
+
+
+## Links
+
+- [Roadmap](https://github.com/fujidaiti/smooth_sheets/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22)
+- [API Documentation](https://pub.dev/documentation/smooth_sheets/latest/)
+- [pub.dev](https://pub.dev/packages/smooth_sheets)
+- [norelease.dev](https://pub.dev/publishers/norelease.dev/packages)
+
+
diff --git a/package/analysis_options.yaml b/analysis_options.yaml
similarity index 92%
rename from package/analysis_options.yaml
rename to analysis_options.yaml
index 1674b9f0..7c3057bb 100644
--- a/package/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -12,3 +12,4 @@ linter:
cascade_invocations: false
join_return_with_assignment: false
prefer_relative_imports: true
+ unnecessary_parenthesis: false
diff --git a/resources/cookbook-ai-playlist-generator.gif b/art/cookbook-ai-playlist-generator.gif
similarity index 100%
rename from resources/cookbook-ai-playlist-generator.gif
rename to art/cookbook-ai-playlist-generator.gif
diff --git a/resources/cookbook-airbnb-mobile-app.gif b/art/cookbook-airbnb-mobile-app.gif
similarity index 100%
rename from resources/cookbook-airbnb-mobile-app.gif
rename to art/cookbook-airbnb-mobile-app.gif
diff --git a/resources/cookbook-cupertino-modal-sheet.gif b/art/cookbook-cupertino-modal-sheet.gif
similarity index 100%
rename from resources/cookbook-cupertino-modal-sheet.gif
rename to art/cookbook-cupertino-modal-sheet.gif
diff --git a/resources/cookbook-declarative-navigation-sheet.gif b/art/cookbook-declarative-navigation-sheet.gif
similarity index 100%
rename from resources/cookbook-declarative-navigation-sheet.gif
rename to art/cookbook-declarative-navigation-sheet.gif
diff --git a/resources/cookbook-declerative-modal-sheet.gif b/art/cookbook-declerative-modal-sheet.gif
similarity index 100%
rename from resources/cookbook-declerative-modal-sheet.gif
rename to art/cookbook-declerative-modal-sheet.gif
diff --git a/resources/cookbook-draggable-sheet.gif b/art/cookbook-draggable-sheet.gif
similarity index 100%
rename from resources/cookbook-draggable-sheet.gif
rename to art/cookbook-draggable-sheet.gif
diff --git a/resources/cookbook-extent-driven-animation.gif b/art/cookbook-extent-driven-animation.gif
similarity index 100%
rename from resources/cookbook-extent-driven-animation.gif
rename to art/cookbook-extent-driven-animation.gif
diff --git a/resources/cookbook-imerative-modal-sheet.gif b/art/cookbook-imerative-modal-sheet.gif
similarity index 100%
rename from resources/cookbook-imerative-modal-sheet.gif
rename to art/cookbook-imerative-modal-sheet.gif
diff --git a/resources/cookbook-imperative-modal-sheet.gif b/art/cookbook-imperative-modal-sheet.gif
similarity index 100%
rename from resources/cookbook-imperative-modal-sheet.gif
rename to art/cookbook-imperative-modal-sheet.gif
diff --git a/resources/cookbook-imperative-navigation-sheet.gif b/art/cookbook-imperative-navigation-sheet.gif
similarity index 100%
rename from resources/cookbook-imperative-navigation-sheet.gif
rename to art/cookbook-imperative-navigation-sheet.gif
diff --git a/resources/cookbook-keyboard-dismiss-behavior.gif b/art/cookbook-keyboard-dismiss-behavior.gif
similarity index 100%
rename from resources/cookbook-keyboard-dismiss-behavior.gif
rename to art/cookbook-keyboard-dismiss-behavior.gif
diff --git a/resources/cookbook-safari.gif b/art/cookbook-safari.gif
similarity index 100%
rename from resources/cookbook-safari.gif
rename to art/cookbook-safari.gif
diff --git a/resources/cookbook-scrollable-sheet.gif b/art/cookbook-scrollable-sheet.gif
similarity index 100%
rename from resources/cookbook-scrollable-sheet.gif
rename to art/cookbook-scrollable-sheet.gif
diff --git a/resources/cookbook-sheet-content-scaffold.gif b/art/cookbook-sheet-content-scaffold.gif
similarity index 100%
rename from resources/cookbook-sheet-content-scaffold.gif
rename to art/cookbook-sheet-content-scaffold.gif
diff --git a/resources/cookbook-sheet-controller.gif b/art/cookbook-sheet-controller.gif
similarity index 100%
rename from resources/cookbook-sheet-controller.gif
rename to art/cookbook-sheet-controller.gif
diff --git a/resources/cookbook-sheet-draggable.gif b/art/cookbook-sheet-draggable.gif
similarity index 100%
rename from resources/cookbook-sheet-draggable.gif
rename to art/cookbook-sheet-draggable.gif
diff --git a/resources/cookbook-sheet-physics.gif b/art/cookbook-sheet-physics.gif
similarity index 100%
rename from resources/cookbook-sheet-physics.gif
rename to art/cookbook-sheet-physics.gif
diff --git a/resources/cookbook-todo-list.gif b/art/cookbook-todo-list.gif
similarity index 100%
rename from resources/cookbook-todo-list.gif
rename to art/cookbook-todo-list.gif
diff --git a/resources/index.md b/art/index.md
similarity index 100%
rename from resources/index.md
rename to art/index.md
diff --git a/package/resources/smooth-sheets-thumbnail.png b/art/smooth-sheets-thumbnail.png
similarity index 100%
rename from package/resources/smooth-sheets-thumbnail.png
rename to art/smooth-sheets-thumbnail.png
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 00000000..75d8d60a
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ builders:
+ mockito|mockBuilder:
+ generate_for:
+ - test/src/*.dart
diff --git a/cookbook/lib/issue/issue131.dart b/cookbook/lib/issue/issue131.dart
deleted file mode 100644
index e4f2b79a..00000000
--- a/cookbook/lib/issue/issue131.dart
+++ /dev/null
@@ -1,106 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
-import 'package:smooth_sheets/smooth_sheets.dart';
-
-/// Issue [#131](https://github.com/fujidaiti/smooth_sheets/issues/131):
-/// Unwanted bouncing effect when opening keyboard on NavigationSheet
-void main() {
- runApp(const Issue131());
-}
-
-class Issue131 extends StatelessWidget {
- const Issue131({super.key});
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp.router(routerConfig: router);
- }
-}
-
-final sheetTransitionObserver = NavigationSheetTransitionObserver();
-
-final router = GoRouter(
- routes: [
- GoRoute(
- path: '/',
- builder: (context, state) => const Home(),
- routes: [
- ShellRoute(
- observers: [sheetTransitionObserver],
- pageBuilder: (context, state, navigator) {
- return ModalSheetPage(
- child: ModalSheet(
- navigator: navigator,
- transitionObserver: sheetTransitionObserver,
- ),
- );
- },
- routes: [
- GoRoute(
- path: 'intro',
- pageBuilder: (context, state) {
- return const DraggableNavigationSheetPage(
- child: SheetContent(),
- );
- },
- )
- ],
- )
- ],
- ),
- ],
-);
-
-class Home extends StatelessWidget {
- const Home({super.key});
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: Center(
- child: ElevatedButton(
- onPressed: () => context.go('/intro'),
- child: const Text('Show modal sheet'),
- ),
- ),
- );
- }
-}
-
-class ModalSheet extends StatelessWidget {
- const ModalSheet({
- super.key,
- required this.transitionObserver,
- required this.navigator,
- });
-
- final NavigationSheetTransitionObserver transitionObserver;
- final Widget navigator;
-
- @override
- Widget build(BuildContext context) {
- return NavigationSheet(
- transitionObserver: sheetTransitionObserver,
- child: ColoredBox(
- color: Theme.of(context).colorScheme.surface,
- child: navigator,
- ),
- );
- }
-}
-
-class SheetContent extends StatelessWidget {
- const SheetContent({super.key});
-
- @override
- Widget build(BuildContext context) {
- return SheetContentScaffold(
- body: SizedBox(
- height: 300,
- child: Center(
- child: TextFormField(),
- ),
- ),
- );
- }
-}
diff --git a/cookbook/lib/issue/issue137.dart b/cookbook/lib/issue/issue137.dart
deleted file mode 100644
index 465074c5..00000000
--- a/cookbook/lib/issue/issue137.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:smooth_sheets/smooth_sheets.dart';
-
-/// issue [#137](https://github.com/fujidaiti/smooth_sheets/issues/137):
-/// SheetDismissible not working with NavigationSheet
-void main() {
- runApp(const MaterialApp(
- home: HomePage(),
- ));
-}
-
-class HomePage extends StatelessWidget {
- const HomePage({
- super.key,
- });
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: const Text('Smooth Sheets Example'),
- ),
- body: Center(
- child: ElevatedButton(
- onPressed: () {
- BaseModal.show(context);
- },
- child: const Text('Show Modal'),
- ),
- ),
- );
- }
-}
-
-class BaseModal extends StatelessWidget {
- const BaseModal({super.key});
-
- static Future show(BuildContext context) async {
- return await Navigator.push(
- context,
- ModalSheetRoute(
- swipeDismissible: true,
- builder: (context) => const BaseModal(),
- ));
- }
-
- @override
- Widget build(BuildContext context) {
- final transitionObserver = NavigationSheetTransitionObserver();
-
- final nestedNavigator = Navigator(
- observers: [transitionObserver],
- onGenerateInitialRoutes: (navigator, initialRoute) {
- return [
- ScrollableNavigationSheetRoute(
- builder: (context) {
- return const BasePage();
- },
- ),
- ];
- },
- );
-
- return SafeArea(
- bottom: false,
- child: NavigationSheet(
- transitionObserver: transitionObserver,
- child: Material(
- color: Colors.white,
- clipBehavior: Clip.antiAlias,
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(20),
- topRight: Radius.circular(20),
- ),
- child: nestedNavigator,
- ),
- ),
- );
- }
-}
-
-class BasePage extends StatelessWidget {
- const BasePage({
- super.key,
- });
-
- @override
- Widget build(BuildContext context) {
- return SafeArea(
- top: false,
- child: SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- mainAxisSize: MainAxisSize.min,
- children: [
- Container(
- height: 300,
- color: Colors.amber,
- ),
- const SizedBox(
- height: 10,
- )
- ],
- ),
- ));
- }
-}
diff --git a/cookbook/lib/issue/issue80.dart b/cookbook/lib/issue/issue80.dart
deleted file mode 100644
index 976b0555..00000000
--- a/cookbook/lib/issue/issue80.dart
+++ /dev/null
@@ -1,72 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:smooth_sheets/smooth_sheets.dart';
-
-/// issue [#80](https://github.com/fujidaiti/smooth_sheets/issues/80):
-/// SheetDismissible not working with infinite looping scroll widget
-void main() {
- runApp(
- const CupertinoApp(
- home: _Home(),
- ),
- );
-}
-
-class _Home extends StatelessWidget {
- const _Home();
-
- @override
- Widget build(BuildContext context) {
- return CupertinoPageScaffold(
- child: Center(
- child: CupertinoButton.filled(
- onPressed: () => showTimePickerSheet(context),
- child: const Text('Show Time Picker Sheet'),
- ),
- ),
- );
- }
-}
-
-void showTimePickerSheet(BuildContext context) {
- final modalRoute = ModalSheetRoute(
- swipeDismissible: true,
- builder: (context) => DraggableSheet(
- child: MediaQuery.removePadding(
- context: context,
- removeTop: true,
- child: CupertinoPageScaffold(
- navigationBar: CupertinoNavigationBar(
- automaticallyImplyLeading: false,
- middle: const Text('Time Picker'),
- trailing: CupertinoButton(
- padding: EdgeInsets.zero,
- onPressed: () => Navigator.pop(context),
- child: const Icon(CupertinoIcons.xmark),
- ),
- ),
- child: SafeArea(
- child: SizedBox(
- height: 300,
- child: CupertinoTheme(
- data: const CupertinoThemeData(
- textTheme: CupertinoTextThemeData(
- dateTimePickerTextStyle: TextStyle(fontSize: 56),
- ),
- ),
- child: CupertinoDatePicker(
- initialDateTime: DateTime.now(),
- mode: CupertinoDatePickerMode.time,
- use24hFormat: true,
- onDateTimeChanged: (value) {},
- itemExtent: 80,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- );
-
- Navigator.push(context, modalRoute);
-}
diff --git a/cookbook/.gitignore b/example/.gitignore
similarity index 94%
rename from cookbook/.gitignore
rename to example/.gitignore
index 6bc4ab47..38abd079 100644
--- a/cookbook/.gitignore
+++ b/example/.gitignore
@@ -17,7 +17,6 @@ migrate_working_dir/
.idea/
# Flutter/Dart/Pub related
-/pubspec.lock
**/doc/api/
.dart_tool/
.packages
diff --git a/cookbook/.metadata b/example/.metadata
similarity index 100%
rename from cookbook/.metadata
rename to example/.metadata
diff --git a/cookbook/README.md b/example/README.md
similarity index 100%
rename from cookbook/README.md
rename to example/README.md
diff --git a/cookbook/analysis_options.yaml b/example/analysis_options.yaml
similarity index 100%
rename from cookbook/analysis_options.yaml
rename to example/analysis_options.yaml
diff --git a/cookbook/android/.gitignore b/example/android/.gitignore
similarity index 100%
rename from cookbook/android/.gitignore
rename to example/android/.gitignore
diff --git a/cookbook/android/app/build.gradle b/example/android/app/build.gradle
similarity index 100%
rename from cookbook/android/app/build.gradle
rename to example/android/app/build.gradle
diff --git a/cookbook/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
similarity index 100%
rename from cookbook/android/app/src/debug/AndroidManifest.xml
rename to example/android/app/src/debug/AndroidManifest.xml
diff --git a/cookbook/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from cookbook/android/app/src/main/AndroidManifest.xml
rename to example/android/app/src/main/AndroidManifest.xml
diff --git a/cookbook/android/app/src/main/kotlin/com/example/cookbook/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/cookbook/MainActivity.kt
similarity index 100%
rename from cookbook/android/app/src/main/kotlin/com/example/cookbook/MainActivity.kt
rename to example/android/app/src/main/kotlin/com/example/cookbook/MainActivity.kt
diff --git a/cookbook/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
similarity index 100%
rename from cookbook/android/app/src/main/res/drawable-v21/launch_background.xml
rename to example/android/app/src/main/res/drawable-v21/launch_background.xml
diff --git a/cookbook/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml
similarity index 100%
rename from cookbook/android/app/src/main/res/drawable/launch_background.xml
rename to example/android/app/src/main/res/drawable/launch_background.xml
diff --git a/cookbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from cookbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/cookbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from cookbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/cookbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from cookbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/cookbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from cookbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/cookbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from cookbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/cookbook/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
similarity index 100%
rename from cookbook/android/app/src/main/res/values-night/styles.xml
rename to example/android/app/src/main/res/values-night/styles.xml
diff --git a/cookbook/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
similarity index 100%
rename from cookbook/android/app/src/main/res/values/styles.xml
rename to example/android/app/src/main/res/values/styles.xml
diff --git a/cookbook/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
similarity index 100%
rename from cookbook/android/app/src/profile/AndroidManifest.xml
rename to example/android/app/src/profile/AndroidManifest.xml
diff --git a/cookbook/android/build.gradle b/example/android/build.gradle
similarity index 100%
rename from cookbook/android/build.gradle
rename to example/android/build.gradle
diff --git a/cookbook/android/gradle.properties b/example/android/gradle.properties
similarity index 100%
rename from cookbook/android/gradle.properties
rename to example/android/gradle.properties
diff --git a/cookbook/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
similarity index 74%
rename from cookbook/android/gradle/wrapper/gradle-wrapper.properties
rename to example/android/gradle/wrapper/gradle-wrapper.properties
index 3c472b99..09523c0e 100644
--- a/cookbook/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
diff --git a/cookbook/android/settings.gradle b/example/android/settings.gradle
similarity index 92%
rename from cookbook/android/settings.gradle
rename to example/android/settings.gradle
index 7cd71285..af1ec35d 100644
--- a/cookbook/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -23,7 +23,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "7.3.0" apply false
+ id "com.android.application" version '7.4.2' apply false
}
include ":app"
diff --git a/cookbook/assets/apple_website.png b/example/assets/apple_website.png
similarity index 100%
rename from cookbook/assets/apple_website.png
rename to example/assets/apple_website.png
diff --git a/cookbook/assets/fake_map.png b/example/assets/fake_map.png
similarity index 100%
rename from cookbook/assets/fake_map.png
rename to example/assets/fake_map.png
diff --git a/cookbook/ios/.gitignore b/example/ios/.gitignore
similarity index 100%
rename from cookbook/ios/.gitignore
rename to example/ios/.gitignore
diff --git a/cookbook/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
similarity index 100%
rename from cookbook/ios/Flutter/AppFrameworkInfo.plist
rename to example/ios/Flutter/AppFrameworkInfo.plist
diff --git a/cookbook/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
similarity index 100%
rename from cookbook/ios/Flutter/Debug.xcconfig
rename to example/ios/Flutter/Debug.xcconfig
diff --git a/cookbook/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
similarity index 100%
rename from cookbook/ios/Flutter/Release.xcconfig
rename to example/ios/Flutter/Release.xcconfig
diff --git a/cookbook/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
similarity index 100%
rename from cookbook/ios/Runner.xcodeproj/project.pbxproj
rename to example/ios/Runner.xcodeproj/project.pbxproj
diff --git a/cookbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from cookbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/cookbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from cookbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/cookbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
similarity index 100%
rename from cookbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
diff --git a/cookbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
similarity index 100%
rename from cookbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
diff --git a/cookbook/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from cookbook/ios/Runner.xcworkspace/contents.xcworkspacedata
rename to example/ios/Runner.xcworkspace/contents.xcworkspacedata
diff --git a/cookbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from cookbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/cookbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
similarity index 100%
rename from cookbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
rename to example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
diff --git a/cookbook/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
similarity index 100%
rename from cookbook/ios/Runner/AppDelegate.swift
rename to example/ios/Runner/AppDelegate.swift
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
diff --git a/cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
diff --git a/cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
similarity index 100%
rename from cookbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
diff --git a/cookbook/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
similarity index 100%
rename from cookbook/ios/Runner/Base.lproj/LaunchScreen.storyboard
rename to example/ios/Runner/Base.lproj/LaunchScreen.storyboard
diff --git a/cookbook/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard
similarity index 100%
rename from cookbook/ios/Runner/Base.lproj/Main.storyboard
rename to example/ios/Runner/Base.lproj/Main.storyboard
diff --git a/cookbook/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
similarity index 100%
rename from cookbook/ios/Runner/Info.plist
rename to example/ios/Runner/Info.plist
diff --git a/cookbook/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h
similarity index 100%
rename from cookbook/ios/Runner/Runner-Bridging-Header.h
rename to example/ios/Runner/Runner-Bridging-Header.h
diff --git a/cookbook/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift
similarity index 100%
rename from cookbook/ios/RunnerTests/RunnerTests.swift
rename to example/ios/RunnerTests/RunnerTests.swift
diff --git a/cookbook/lib/showcase/ai_playlist_generator.dart b/example/lib/showcase/ai_playlist_generator.dart
similarity index 98%
rename from cookbook/lib/showcase/ai_playlist_generator.dart
rename to example/lib/showcase/ai_playlist_generator.dart
index a0188d78..723069b1 100644
--- a/cookbook/lib/showcase/ai_playlist_generator.dart
+++ b/example/lib/showcase/ai_playlist_generator.dart
@@ -1,6 +1,7 @@
import 'dart:async';
-import 'dart:io';
+import 'package:flutter/foundation.dart'
+ show TargetPlatform, defaultTargetPlatform;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
@@ -8,7 +9,7 @@ import 'package:smooth_sheets/smooth_sheets.dart';
void main() {
// Make the system navigation bar transparent on Android.
- if (Platform.isAndroid) {
+ if (defaultTargetPlatform == TargetPlatform.android) {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge,
@@ -104,9 +105,9 @@ final _confirmRoute = GoRoute(
path: 'confirm',
pageBuilder: (context, state) {
return const ScrollableNavigationSheetPage(
- initialExtent: Extent.proportional(0.7),
- minExtent: Extent.proportional(0.7),
- physics: StretchingSheetPhysics(
+ initialPosition: SheetAnchor.proportional(0.7),
+ minPosition: SheetAnchor.proportional(0.7),
+ physics: BouncingSheetPhysics(
parent: SnappingSheetPhysics(),
),
child: _ConfirmPage(),
diff --git a/cookbook/lib/showcase/airbnb_mobile_app.dart b/example/lib/showcase/airbnb_mobile_app.dart
similarity index 92%
rename from cookbook/lib/showcase/airbnb_mobile_app.dart
rename to example/lib/showcase/airbnb_mobile_app.dart
index 63ed0944..c31d0485 100644
--- a/cookbook/lib/showcase/airbnb_mobile_app.dart
+++ b/example/lib/showcase/airbnb_mobile_app.dart
@@ -60,7 +60,7 @@ class _Home extends StatelessWidget {
return DefaultTabController(
length: _AppBar.tabs.length,
// Provides a SheetController to the descendant widgets
- // to perform some sheet extent driven animations.
+ // to perform some sheet position driven animations.
// The sheet will look up and use this controller unless
// another one is manually specified in the constructor.
// The descendant widgets can also get this controller by
@@ -80,11 +80,11 @@ class _MapButton extends StatelessWidget {
final sheetController = DefaultSheetController.of(context);
void onPressed() {
- final metrics = sheetController.value;
+ final metrics = sheetController.metrics;
if (metrics.hasDimensions) {
// Collapse the sheet to reveal the map behind.
sheetController.animateTo(
- Extent.pixels(metrics.minPixels),
+ SheetAnchor.pixels(metrics.minPixels),
curve: Curves.fastOutSlowIn,
);
}
@@ -97,19 +97,19 @@ class _MapButton extends StatelessWidget {
icon: const Icon(Icons.map),
);
- // It is easy to create sheet extent driven animations
- // by using 'ExtentDrivenAnimation', a special kind of
+ // It is easy to create sheet position driven animations
+ // by using 'PositionDrivenAnimation', a special kind of
// 'Animation' whose value changes from 0 to 1 as
- // the sheet extent changes from 'startExtent' to 'endExtent'.
- final animation = ExtentDrivenAnimation(
+ // the sheet position changes from 'startPosition' to 'endPosition'.
+ final animation = SheetPositionDrivenAnimation(
controller: DefaultSheetController.of(context),
// The initial value of the animation is required
- // since the sheet extent is not available at the first build.
+ // since the sheet position is not available at the first build.
initialValue: 1,
- // If null, the minimum extent will be used. (Default)
- startExtent: null,
- // If null, the maximum extent will be used. (Default)
- endExtent: null,
+ // If null, the minimum position will be used. (Default)
+ startPosition: null,
+ // If null, the maximum position will be used. (Default)
+ endPosition: null,
).drive(CurveTween(curve: Curves.easeInExpo));
// Hide the button when the sheet is dragged down.
@@ -155,8 +155,8 @@ class _ContentSheet extends StatelessWidget {
final appbarHeight = MediaQuery.of(context).padding.top;
final handleHeight = const _ContentSheetHandle().preferredSize.height;
final sheetHeight = parentHeight - appbarHeight + handleHeight;
- final minSheetExtent =
- Extent.pixels(handleHeight + systemUiInsets.bottom);
+ final minSheetPosition =
+ SheetAnchor.pixels(handleHeight + systemUiInsets.bottom);
const sheetShape = RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
@@ -164,12 +164,12 @@ class _ContentSheet extends StatelessWidget {
),
);
- final sheetPhysics = StretchingSheetPhysics(
+ final sheetPhysics = BouncingSheetPhysics(
parent: SnappingSheetPhysics(
- snappingBehavior: SnapToNearest(
- snapTo: [
- minSheetExtent,
- const Extent.proportional(1),
+ behavior: SnapToNearest(
+ anchors: [
+ minSheetPosition,
+ const SheetAnchor.proportional(1),
],
),
),
@@ -177,7 +177,7 @@ class _ContentSheet extends StatelessWidget {
return ScrollableSheet(
physics: sheetPhysics,
- minExtent: minSheetExtent,
+ minPosition: minSheetPosition,
child: SizedBox(
height: sheetHeight,
child: const Card(
@@ -261,7 +261,7 @@ class _HouseList extends StatelessWidget {
// Hide the list when the sheet is dragged down.
return FadeTransition(
- opacity: ExtentDrivenAnimation(
+ opacity: SheetPositionDrivenAnimation(
controller: DefaultSheetController.of(context),
initialValue: 1,
).drive(
@@ -453,7 +453,7 @@ class _BottomNavigationBar extends StatelessWidget {
// Hide the navigation bar when the sheet is dragged down.
return SlideTransition(
- position: ExtentDrivenAnimation(
+ position: SheetPositionDrivenAnimation(
controller: DefaultSheetController.of(context),
initialValue: 1,
).drive(
diff --git a/cookbook/lib/showcase/safari/actions.dart b/example/lib/showcase/safari/actions.dart
similarity index 100%
rename from cookbook/lib/showcase/safari/actions.dart
rename to example/lib/showcase/safari/actions.dart
diff --git a/cookbook/lib/showcase/safari/bookmark.dart b/example/lib/showcase/safari/bookmark.dart
similarity index 64%
rename from cookbook/lib/showcase/safari/bookmark.dart
rename to example/lib/showcase/safari/bookmark.dart
index 3102eb8d..cfbe9a21 100644
--- a/cookbook/lib/showcase/safari/bookmark.dart
+++ b/example/lib/showcase/safari/bookmark.dart
@@ -27,32 +27,34 @@ class EditBookmarkSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return DraggableSheet(
- keyboardDismissBehavior: const SheetKeyboardDismissBehavior.onDragDown(),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(16),
- child: SheetContentScaffold(
- backgroundColor: CupertinoColors.systemGroupedBackground,
- appBar: CupertinoAppBar(
- title: const Text('Add Bookmark'),
- leading: CupertinoButton(
- onPressed: () => Navigator.pop(context),
- child: const Text('Cancel'),
- ),
- trailing: CupertinoButton(
- onPressed: () =>
- Navigator.popUntil(context, (route) => route.isFirst),
- child: const Text('Save'),
+ return SheetKeyboardDismissible(
+ dismissBehavior: const SheetKeyboardDismissBehavior.onDragDown(),
+ child: DraggableSheet(
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(16),
+ child: SheetContentScaffold(
+ backgroundColor: CupertinoColors.systemGroupedBackground,
+ appBar: CupertinoAppBar(
+ title: const Text('Add Bookmark'),
+ leading: CupertinoButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('Cancel'),
+ ),
+ trailing: CupertinoButton(
+ onPressed: () =>
+ Navigator.popUntil(context, (route) => route.isFirst),
+ child: const Text('Save'),
+ ),
),
- ),
- body: SizedBox.expand(
- child: CupertinoListSection.insetGrouped(
- children: [
- _BookmarkEditor(
- pageUrl: pageUrl,
- faviconUrl: faviconUrl,
- ),
- ],
+ body: SizedBox.expand(
+ child: CupertinoListSection.insetGrouped(
+ children: [
+ _BookmarkEditor(
+ pageUrl: pageUrl,
+ faviconUrl: faviconUrl,
+ ),
+ ],
+ ),
),
),
),
diff --git a/cookbook/lib/showcase/safari/common.dart b/example/lib/showcase/safari/common.dart
similarity index 100%
rename from cookbook/lib/showcase/safari/common.dart
rename to example/lib/showcase/safari/common.dart
diff --git a/cookbook/lib/showcase/safari/home.dart b/example/lib/showcase/safari/home.dart
similarity index 100%
rename from cookbook/lib/showcase/safari/home.dart
rename to example/lib/showcase/safari/home.dart
diff --git a/cookbook/lib/showcase/safari/main.dart b/example/lib/showcase/safari/main.dart
similarity index 100%
rename from cookbook/lib/showcase/safari/main.dart
rename to example/lib/showcase/safari/main.dart
diff --git a/cookbook/lib/showcase/safari/menu.dart b/example/lib/showcase/safari/menu.dart
similarity index 96%
rename from cookbook/lib/showcase/safari/menu.dart
rename to example/lib/showcase/safari/menu.dart
index 6d1e192b..fc6f0857 100644
--- a/cookbook/lib/showcase/safari/menu.dart
+++ b/example/lib/showcase/safari/menu.dart
@@ -22,11 +22,11 @@ class MenuSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
- const halfWayExtent = Extent.proportional(0.5);
+ const halfWayPosition = SheetAnchor.proportional(0.5);
return ScrollableSheet(
- initialExtent: halfWayExtent,
- minExtent: halfWayExtent,
- physics: const StretchingSheetPhysics(
+ initialPosition: halfWayPosition,
+ minPosition: halfWayPosition,
+ physics: const BouncingSheetPhysics(
parent: SnappingSheetPhysics(),
),
child: ClipRRect(
@@ -152,7 +152,7 @@ class _MenuListItem extends StatelessWidget {
trailing: Icon(icon, color: CupertinoColors.black),
onTap: () {
DefaultSheetController.maybeOf(context)
- ?.animateTo(const Extent.proportional(1));
+ ?.animateTo(const SheetAnchor.proportional(1));
showEditBookmarkSheet(context);
},
);
diff --git a/cookbook/lib/showcase/todo_list/main.dart b/example/lib/showcase/todo_list/main.dart
similarity index 100%
rename from cookbook/lib/showcase/todo_list/main.dart
rename to example/lib/showcase/todo_list/main.dart
diff --git a/cookbook/lib/showcase/todo_list/models.dart b/example/lib/showcase/todo_list/models.dart
similarity index 100%
rename from cookbook/lib/showcase/todo_list/models.dart
rename to example/lib/showcase/todo_list/models.dart
diff --git a/cookbook/lib/showcase/todo_list/todo_editor.dart b/example/lib/showcase/todo_list/todo_editor.dart
similarity index 93%
rename from cookbook/lib/showcase/todo_list/todo_editor.dart
rename to example/lib/showcase/todo_list/todo_editor.dart
index 23287447..5b96a5be 100644
--- a/cookbook/lib/showcase/todo_list/todo_editor.dart
+++ b/example/lib/showcase/todo_list/todo_editor.dart
@@ -144,21 +144,22 @@ class _TodoEditorState extends State {
child: PopScope(
canPop: false,
onPopInvoked: onPopInvoked,
- child: ScrollableSheet(
- keyboardDismissBehavior:
- const SheetKeyboardDismissBehavior.onDragDown(
+ child: SheetKeyboardDismissible(
+ dismissBehavior: const SheetKeyboardDismissBehavior.onDragDown(
isContentScrollAware: true,
),
- child: Container(
- clipBehavior: Clip.antiAlias,
- decoration: sheetShape,
- child: SheetContentScaffold(
- resizeBehavior: const ResizeScaffoldBehavior.avoidBottomInset(
- // Make the bottom bar visible when the keyboard is open.
- maintainBottomBar: true,
+ child: ScrollableSheet(
+ child: Container(
+ clipBehavior: Clip.antiAlias,
+ decoration: sheetShape,
+ child: SheetContentScaffold(
+ resizeBehavior: const ResizeScaffoldBehavior.avoidBottomInset(
+ // Make the bottom bar visible when the keyboard is open.
+ maintainBottomBar: true,
+ ),
+ body: body,
+ bottomBar: bottomBar,
),
- body: body,
- bottomBar: bottomBar,
),
),
),
diff --git a/cookbook/lib/tutorial/bottom_bar_visibility.dart b/example/lib/tutorial/bottom_bar_visibility.dart
similarity index 92%
rename from cookbook/lib/tutorial/bottom_bar_visibility.dart
rename to example/lib/tutorial/bottom_bar_visibility.dart
index f2ee5afc..be8fd608 100644
--- a/cookbook/lib/tutorial/bottom_bar_visibility.dart
+++ b/example/lib/tutorial/bottom_bar_visibility.dart
@@ -133,14 +133,14 @@ class _ExampleSheet extends StatelessWidget {
),
);
- const minSize = Extent.proportional(0.3);
- const halfSize = Extent.proportional(0.5);
- const fullSize = Extent.proportional(1);
+ const minSize = SheetAnchor.proportional(0.3);
+ const halfSize = SheetAnchor.proportional(0.5);
+ const fullSize = SheetAnchor.proportional(1);
- final multiStopPhysics = StretchingSheetPhysics(
+ const multiStopPhysics = BouncingSheetPhysics(
parent: SnappingSheetPhysics(
- snappingBehavior: SnapToNearest(
- snapTo: const [minSize, halfSize, fullSize],
+ behavior: SnapToNearest(
+ anchors: [minSize, halfSize, fullSize],
),
),
);
@@ -148,8 +148,8 @@ class _ExampleSheet extends StatelessWidget {
return SafeArea(
bottom: false,
child: DraggableSheet(
- minExtent: minSize,
- initialExtent: halfSize,
+ minPosition: minSize,
+ initialPosition: halfSize,
physics: multiStopPhysics,
child: SheetContentScaffold(
appBar: AppBar(),
@@ -166,7 +166,7 @@ class _ExampleSheet extends StatelessWidget {
getIsVisible: (metrics) {
// The bottom bar is visible when at least 50% of the sheet is visible.
return metrics.pixels >=
- const Extent.proportional(0.5)
+ const SheetAnchor.proportional(0.5)
.resolve(metrics.contentSize);
},
child: bottomBar,
diff --git a/example/lib/tutorial/bouncing_behaviors.dart b/example/lib/tutorial/bouncing_behaviors.dart
new file mode 100644
index 00000000..1d83e7be
--- /dev/null
+++ b/example/lib/tutorial/bouncing_behaviors.dart
@@ -0,0 +1,123 @@
+import 'package:flutter/material.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+
+void main() {
+ runApp(const MaterialApp(home: _Home()));
+}
+
+class _Home extends StatelessWidget {
+ const _Home();
+
+ @override
+ Widget build(BuildContext context) {
+ void showModalSheet(Widget sheet) {
+ Navigator.push(context, ModalSheetRoute(builder: (_) => sheet));
+ }
+
+ return Scaffold(
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ListTile(
+ title: const Text('FixedBouncingBehavior'),
+ subtitle: const Text('with ScrollableSheet'),
+ onTap: () => showModalSheet(
+ const _ScrollableSheet(
+ behavior: FixedBouncingBehavior(
+ // Allows the sheet position to exceed the content bounds
+ // by ±10% of the content height.
+ SheetAnchor.proportional(0.1),
+ ),
+ ),
+ ),
+ ),
+ ListTile(
+ title: const Text('FixedBouncingBehavior'),
+ subtitle: const Text('with DraggableSheet'),
+ onTap: () => showModalSheet(
+ const _DraggableSheet(
+ behavior: FixedBouncingBehavior(
+ // Allows the sheet position to exceed the content bounds by ±50 pixels.
+ SheetAnchor.pixels(50),
+ ),
+ ),
+ ),
+ ),
+ ListTile(
+ title: const Text('DirectionAwareBouncingBehavior'),
+ subtitle: const Text('with ScrollableSheet'),
+ onTap: () => showModalSheet(
+ const _ScrollableSheet(
+ behavior: DirectionAwareBouncingBehavior(
+ // Allows the sheet position to exceed the content bounds by 10 pixels
+ // when dragging the sheet upwards, and by ±30% of the content height
+ // when dragging it downwards.
+ upward: SheetAnchor.pixels(20),
+ downward: SheetAnchor.proportional(0.3),
+ ),
+ ),
+ ),
+ ),
+ ListTile(
+ title: const Text('DirectionAwareBouncingBehavior'),
+ subtitle: const Text('with DraggableSheet'),
+ onTap: () => showModalSheet(
+ const _DraggableSheet(
+ behavior: DirectionAwareBouncingBehavior(
+ // Allows the sheet to bounce only when dragging it downwards.
+ upward: SheetAnchor.pixels(0),
+ downward: SheetAnchor.proportional(0.1),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class _ScrollableSheet extends StatelessWidget {
+ const _ScrollableSheet({required this.behavior});
+
+ final BouncingBehavior behavior;
+
+ @override
+ Widget build(BuildContext context) {
+ return ScrollableSheet(
+ physics: BouncingSheetPhysics(behavior: behavior),
+ child: Material(
+ color: Colors.white,
+ child: SizedBox(
+ height: 500,
+ child: ListView(
+ children: List.generate(
+ 30,
+ (index) => ListTile(title: Text('Item $index')),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _DraggableSheet extends StatelessWidget {
+ const _DraggableSheet({required this.behavior});
+
+ final BouncingBehavior behavior;
+
+ @override
+ Widget build(BuildContext context) {
+ return DraggableSheet(
+ physics: BouncingSheetPhysics(behavior: behavior),
+ child: Container(
+ height: 500,
+ width: double.infinity,
+ color: Colors.white,
+ ),
+ );
+ }
+}
diff --git a/cookbook/lib/tutorial/cupertino_modal_sheet.dart b/example/lib/tutorial/cupertino_modal_sheet.dart
similarity index 86%
rename from cookbook/lib/tutorial/cupertino_modal_sheet.dart
rename to example/lib/tutorial/cupertino_modal_sheet.dart
index 088af809..ac6f315c 100644
--- a/cookbook/lib/tutorial/cupertino_modal_sheet.dart
+++ b/example/lib/tutorial/cupertino_modal_sheet.dart
@@ -48,7 +48,13 @@ void _showModalSheet(BuildContext context, {required bool isFullScreen}) {
// Use `CupertinoModalSheetRoute` to show an ios 15 style modal sheet.
// For declarative navigation (Navigator 2.0), use `CupertinoModalSheetPage` instead.
final modalRoute = CupertinoModalSheetRoute(
- swipeDismissible: true, // Enable the swipe-to-dismiss behavior.
+ // Enable the swipe-to-dismiss behavior.
+ swipeDismissible: true,
+ // Use `SwipeDismissSensitivity` to tweak the sensitivity of the swipe-to-dismiss behavior.
+ swipeDismissSensitivity: const SwipeDismissSensitivity(
+ minFlingVelocityRatio: 2.0,
+ minDragDistance: 300.0,
+ ),
builder: (context) => switch (isFullScreen) {
true => const _FullScreenSheet(),
false => const _HalfScreenSheet(),
@@ -64,11 +70,11 @@ class _HalfScreenSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
// `CupertinoStackedTransition` won't start the transition animation until
- // the visible height of a modal sheet (the extent) exceeds 50% of the screen height.
+ // the visible height of a modal sheet (the position) exceeds 50% of the screen height.
return const DraggableSheet(
- initialExtent: Extent.proportional(0.5),
- minExtent: Extent.proportional(0.5),
- physics: StretchingSheetPhysics(
+ initialPosition: SheetAnchor.proportional(0.5),
+ minPosition: SheetAnchor.proportional(0.5),
+ physics: BouncingSheetPhysics(
parent: SnappingSheetPhysics(),
),
child: _SheetContent(),
@@ -114,7 +120,7 @@ class _SheetContent extends StatelessWidget {
// `DefaultSheetController.of` is a handy way to obtain a `SheetController`
// that is exposed by the parent `CupertinoModalSheetRoute`.
DefaultSheetController.maybeOf(context)
- ?.animateTo(const Extent.proportional(1));
+ ?.animateTo(const SheetAnchor.proportional(1));
_showModalSheet(context, isFullScreen: true);
},
child: const Text('Stack'),
diff --git a/cookbook/lib/tutorial/declarative_modal_sheet.dart b/example/lib/tutorial/declarative_modal_sheet.dart
similarity index 91%
rename from cookbook/lib/tutorial/declarative_modal_sheet.dart
rename to example/lib/tutorial/declarative_modal_sheet.dart
index 5f734beb..99e94d18 100644
--- a/cookbook/lib/tutorial/declarative_modal_sheet.dart
+++ b/example/lib/tutorial/declarative_modal_sheet.dart
@@ -26,6 +26,11 @@ final _router = GoRouter(
key: state.pageKey,
// Enable the swipe-to-dismiss behavior.
swipeDismissible: true,
+ // Use `SwipeDismissSensitivity` to tweak the sensitivity of the swipe-to-dismiss behavior.
+ swipeDismissSensitivity: const SwipeDismissSensitivity(
+ minFlingVelocityRatio: 2.0,
+ minDragDistance: 200.0,
+ ),
child: const _ExampleSheet(),
);
},
diff --git a/cookbook/lib/tutorial/declarative_navigation_sheet.dart b/example/lib/tutorial/declarative_navigation_sheet.dart
similarity index 99%
rename from cookbook/lib/tutorial/declarative_navigation_sheet.dart
rename to example/lib/tutorial/declarative_navigation_sheet.dart
index 08ed1573..3b3e0022 100644
--- a/cookbook/lib/tutorial/declarative_navigation_sheet.dart
+++ b/example/lib/tutorial/declarative_navigation_sheet.dart
@@ -13,7 +13,7 @@ void main() {
}
// NavigationSheet requires a special NavigatorObserver in order to
-// smoothly change its extent during a route transition.
+// smoothly change its position during a route transition.
final transitionObserver = NavigationSheetTransitionObserver();
// To use declarative navigation, we utilize the 'go_router' package.
diff --git a/cookbook/lib/tutorial/draggable_sheet.dart b/example/lib/tutorial/draggable_sheet.dart
similarity index 100%
rename from cookbook/lib/tutorial/draggable_sheet.dart
rename to example/lib/tutorial/draggable_sheet.dart
diff --git a/cookbook/lib/tutorial/extent_driven_animation.dart b/example/lib/tutorial/extent_driven_animation.dart
similarity index 76%
rename from cookbook/lib/tutorial/extent_driven_animation.dart
rename to example/lib/tutorial/extent_driven_animation.dart
index c0bedfd7..6e71884f 100644
--- a/cookbook/lib/tutorial/extent_driven_animation.dart
+++ b/example/lib/tutorial/extent_driven_animation.dart
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:smooth_sheets/smooth_sheets.dart';
void main() {
- runApp(const _ExtentDrivenAnimationExample());
+ runApp(const _PositionDrivenAnimationExample());
}
-class _ExtentDrivenAnimationExample extends StatelessWidget {
- const _ExtentDrivenAnimationExample();
+class _PositionDrivenAnimationExample extends StatelessWidget {
+ const _PositionDrivenAnimationExample();
@override
Widget build(BuildContext context) {
@@ -20,7 +20,7 @@ class _ExampleScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Provides a SheetController to the descendant widgets
- // to perform some sheet extent driven animations.
+ // to perform some sheet position driven animations.
// The sheet will look up and use this controller unless
// another one is manually specified in the constructor.
// The descendant widgets can also get this controller by
@@ -48,18 +48,18 @@ class _ExampleSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
final bottomPadding = MediaQuery.of(context).padding.bottom;
- final minExtent = Extent.pixels(56 + bottomPadding);
+ final minPosition = SheetAnchor.pixels(56 + bottomPadding);
- final physics = StretchingSheetPhysics(
+ final physics = BouncingSheetPhysics(
parent: SnappingSheetPhysics(
- snappingBehavior: SnapToNearest(
- snapTo: [minExtent, const Extent.proportional(1)],
+ behavior: SnapToNearest(
+ anchors: [minPosition, const SheetAnchor.proportional(1)],
),
),
);
return DraggableSheet(
- minExtent: minExtent,
+ minPosition: minPosition,
physics: physics,
child: Card(
margin: EdgeInsets.zero,
@@ -84,19 +84,19 @@ class _BottomAppBar extends StatelessWidget {
// Lookup the nearest controller.
final controller = DefaultSheetController.of(context);
- // It is easy to create sheet extent driven animations
- // by using 'ExtentDrivenAnimation', a special kind of
+ // It is easy to create sheet position driven animations
+ // by using 'PositionDrivenAnimation', a special kind of
// 'Animation' whose value changes from 0 to 1 as
- // the sheet extent changes from 'startExtent' to 'endExtent'.
- final animation = ExtentDrivenAnimation(
+ // the sheet position changes from 'startPosition' to 'endPosition'.
+ final animation = SheetPositionDrivenAnimation(
controller: controller,
// The initial value of the animation is required
- // since the sheet extent is not available at the first build.
+ // since the sheet position is not available at the first build.
initialValue: 1,
- // If null, the minimum extent will be used. (Default)
- startExtent: null,
- // If null, the maximum extent will be used. (Default)
- endExtent: null,
+ // If null, the minimum position will be used. (Default)
+ startPosition: null,
+ // If null, the maximum position will be used. (Default)
+ endPosition: null,
);
final bottomAppBar = BottomAppBar(
@@ -134,7 +134,7 @@ class _RotatedFlutterLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final logo = RotationTransition(
- turns: ExtentDrivenAnimation(
+ turns: SheetPositionDrivenAnimation(
controller: DefaultSheetController.of(context),
initialValue: 1,
),
diff --git a/cookbook/lib/tutorial/imperative_modal_sheet.dart b/example/lib/tutorial/imperative_modal_sheet.dart
similarity index 90%
rename from cookbook/lib/tutorial/imperative_modal_sheet.dart
rename to example/lib/tutorial/imperative_modal_sheet.dart
index 1c6ff82e..13aea697 100644
--- a/cookbook/lib/tutorial/imperative_modal_sheet.dart
+++ b/example/lib/tutorial/imperative_modal_sheet.dart
@@ -38,6 +38,11 @@ void _showModalSheet(BuildContext context) {
final modalRoute = ModalSheetRoute(
// Enable the swipe-to-dismiss behavior.
swipeDismissible: true,
+ // Use `SwipeDismissSensitivity` to tweak the sensitivity of the swipe-to-dismiss behavior.
+ swipeDismissSensitivity: const SwipeDismissSensitivity(
+ minFlingVelocityRatio: 2.0,
+ minDragDistance: 200.0,
+ ),
builder: (context) => const _ExampleSheet(),
);
@@ -62,7 +67,7 @@ class _ExampleSheet extends StatelessWidget {
}
},
child: DraggableSheet(
- minExtent: const Extent.proportional(0.5),
+ minPosition: const SheetAnchor.proportional(0.5),
child: Card(
color: Theme.of(context).colorScheme.secondaryContainer,
margin: EdgeInsets.zero,
diff --git a/cookbook/lib/tutorial/imperative_navigation_sheet.dart b/example/lib/tutorial/imperative_navigation_sheet.dart
similarity index 98%
rename from cookbook/lib/tutorial/imperative_navigation_sheet.dart
rename to example/lib/tutorial/imperative_navigation_sheet.dart
index df9a733e..c2cfb5de 100644
--- a/cookbook/lib/tutorial/imperative_navigation_sheet.dart
+++ b/example/lib/tutorial/imperative_navigation_sheet.dart
@@ -22,7 +22,7 @@ class _ImperativeNavigationSheetExample extends StatelessWidget {
}
// NavigationSheet requires a special NavigatorObserver in order to
-// smoothly change its extent during a route transition.
+// smoothly change its position during a route transition.
final _transitionObserver = NavigationSheetTransitionObserver();
class _ExampleSheet extends StatelessWidget {
diff --git a/example/lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart b/example/lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart
new file mode 100644
index 00000000..ad8dd430
--- /dev/null
+++ b/example/lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+
+/// Example code of iOS style modal `NavigationSheet` with go_router.
+void main() {
+ runApp(const _App());
+}
+
+final transitionObserver = NavigationSheetTransitionObserver();
+
+final router = GoRouter(
+ initialLocation: '/',
+ routes: [
+ GoRoute(
+ path: '/',
+ builder: (context, state) {
+ return const _Home();
+ },
+ routes: [
+ ShellRoute(
+ observers: [transitionObserver],
+ pageBuilder: (context, state, child) {
+ return CupertinoModalSheetPage(
+ key: state.pageKey,
+ child: _Modal(nestedNavigator: child),
+ );
+ },
+ routes: [
+ GoRoute(
+ path: 'modal',
+ pageBuilder: (context, state) {
+ return DraggableNavigationSheetPage(
+ key: state.pageKey,
+ child: Container(color: Colors.white),
+ );
+ },
+ ),
+ ],
+ )
+ ]),
+ ],
+);
+
+class _App extends StatelessWidget {
+ const _App();
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp.router(routerConfig: router);
+ }
+}
+
+class _Home extends StatelessWidget {
+ const _Home();
+
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoStackedTransition(
+ cornerRadius: Tween(begin: 0, end: 20),
+ child: Scaffold(
+ body: Center(
+ child: TextButton(
+ onPressed: () => context.go('/modal'),
+ child: const Text('Show Sheet'),
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _Modal extends StatelessWidget {
+ const _Modal({
+ required this.nestedNavigator,
+ });
+
+ final Widget nestedNavigator;
+
+ @override
+ Widget build(BuildContext context) {
+ return NavigationSheet(
+ transitionObserver: transitionObserver,
+ child: Material(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(20),
+ clipBehavior: Clip.antiAlias,
+ child: nestedNavigator,
+ ),
+ );
+ }
+}
diff --git a/cookbook/lib/tutorial/keyboard_dismiss_behavior.dart b/example/lib/tutorial/keyboard_dismiss_behavior.dart
similarity index 86%
rename from cookbook/lib/tutorial/keyboard_dismiss_behavior.dart
rename to example/lib/tutorial/keyboard_dismiss_behavior.dart
index c254a549..069732c9 100644
--- a/cookbook/lib/tutorial/keyboard_dismiss_behavior.dart
+++ b/example/lib/tutorial/keyboard_dismiss_behavior.dart
@@ -28,10 +28,6 @@ enum _KeyboardDismissBehaviorKind {
onDragUp(
'onDragUp',
'Dismisses the keyboard only when the user drags the sheet upwards.',
- ),
- none(
- 'Null',
- 'Does not automatically dismiss the keyboard.',
);
final String name;
@@ -49,7 +45,7 @@ class _ExampleHome extends StatefulWidget {
class _ExampleHomeState extends State<_ExampleHome> {
_KeyboardDismissBehaviorKind selectedBehavior =
- _KeyboardDismissBehaviorKind.none;
+ _KeyboardDismissBehaviorKind.onDrag;
bool isContentScrollAware = false;
bool isFullScreen = false;
@@ -122,7 +118,6 @@ class _ExampleHomeState extends State<_ExampleHome> {
void showExampleSheet(BuildContext context) {
// This object determines when the sheet should dismisses the on-screen keyboard.
final keyboardDismissBehavior = switch (selectedBehavior) {
- _KeyboardDismissBehaviorKind.none => null,
_KeyboardDismissBehaviorKind.onDrag =>
SheetKeyboardDismissBehavior.onDrag(
isContentScrollAware: isContentScrollAware),
@@ -173,25 +168,27 @@ class _ExampleSheet extends StatelessWidget {
return SafeArea(
bottom: false,
- child: ScrollableSheet(
- keyboardDismissBehavior: keyboardDismissBehavior,
- child: SheetContentScaffold(
- appBar: AppBar(),
- body: body,
- bottomBar: StickyBottomBarVisibility(
- child: BottomAppBar(
- child: Row(
- children: [
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.menu),
- ),
- const Spacer(),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.more_vert),
- ),
- ],
+ child: SheetKeyboardDismissible(
+ dismissBehavior: keyboardDismissBehavior,
+ child: ScrollableSheet(
+ child: SheetContentScaffold(
+ appBar: AppBar(),
+ body: body,
+ bottomBar: StickyBottomBarVisibility(
+ child: BottomAppBar(
+ child: Row(
+ children: [
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.menu),
+ ),
+ const Spacer(),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.more_vert),
+ ),
+ ],
+ ),
),
),
),
diff --git a/example/lib/tutorial/navigation_sheet_and_keyboard.dart b/example/lib/tutorial/navigation_sheet_and_keyboard.dart
new file mode 100644
index 00000000..34adb4c2
--- /dev/null
+++ b/example/lib/tutorial/navigation_sheet_and_keyboard.dart
@@ -0,0 +1,154 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+
+void main() {
+ runApp(MaterialApp.router(routerConfig: _router));
+}
+
+final _transitionObserver = NavigationSheetTransitionObserver();
+
+final _router = GoRouter(
+ routes: [
+ GoRoute(
+ path: '/',
+ builder: (context, state) => const _Home(),
+ routes: [
+ ShellRoute(
+ observers: [_transitionObserver],
+ pageBuilder: (context, state, child) {
+ return ModalSheetPage(
+ child: _MySheet(
+ transitionObserver: _transitionObserver,
+ navigator: child,
+ ),
+ );
+ },
+ routes: [
+ GoRoute(
+ path: 'a',
+ pageBuilder: (context, state) {
+ return DraggableNavigationSheetPage(
+ key: state.pageKey,
+ child: const _EditablePageContent(
+ height: 600,
+ nextLocation: '/a/b',
+ autofocus: true,
+ ),
+ );
+ },
+ routes: [
+ GoRoute(
+ path: 'b',
+ pageBuilder: (context, state) {
+ return DraggableNavigationSheetPage(
+ key: state.pageKey,
+ child: const _EditablePageContent(
+ height: 300,
+ nextLocation: '/a/b/c',
+ autofocus: true,
+ ),
+ );
+ },
+ routes: [
+ GoRoute(
+ path: 'c',
+ pageBuilder: (context, state) {
+ return DraggableNavigationSheetPage(
+ key: state.pageKey,
+ child: const _EditablePageContent(
+ nextLocation: '/',
+ height: double.infinity,
+ autofocus: false,
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+);
+
+class _Home extends StatelessWidget {
+ const _Home();
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Home'),
+ ),
+ body: Center(
+ child: ElevatedButton(
+ onPressed: () {
+ GoRouter.of(context).go('/a');
+ },
+ child: const Text('Open Sheet'),
+ ),
+ ),
+ );
+ }
+}
+
+class _MySheet extends StatelessWidget {
+ const _MySheet({
+ required this.transitionObserver,
+ required this.navigator,
+ });
+
+ final NavigationSheetTransitionObserver transitionObserver;
+ final Widget navigator;
+
+ @override
+ Widget build(BuildContext context) {
+ return NavigationSheet(
+ transitionObserver: transitionObserver,
+ child: ColoredBox(
+ color: Colors.white,
+ child: navigator,
+ ),
+ );
+ }
+}
+
+class _EditablePageContent extends StatelessWidget {
+ const _EditablePageContent({
+ required this.nextLocation,
+ required this.autofocus,
+ required this.height,
+ });
+
+ final double height;
+ final String nextLocation;
+ final bool autofocus;
+
+ @override
+ Widget build(BuildContext context) {
+ return SheetContentScaffold(
+ body: SizedBox(
+ height: height,
+ child: Column(
+ children: [
+ TextField(
+ autofocus: autofocus,
+ ),
+ ElevatedButton(
+ onPressed: () => context.go(nextLocation),
+ child: const Text('Next'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('Back'),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/tutorial/scrollable_pageview_sheet.dart b/example/lib/tutorial/scrollable_pageview_sheet.dart
new file mode 100644
index 00000000..bb7c5d8f
--- /dev/null
+++ b/example/lib/tutorial/scrollable_pageview_sheet.dart
@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+
+void main() {
+ runApp(const _ScrollablePageViewSheetExample());
+}
+
+/// An example of [ScrollableSheet] + [PageView].
+class _ScrollablePageViewSheetExample extends StatelessWidget {
+ const _ScrollablePageViewSheetExample();
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: Builder(
+ builder: (context) {
+ return ElevatedButton(
+ onPressed: () {
+ Navigator.push(
+ context,
+ ModalSheetRoute(builder: (_) => const _MySheet()),
+ );
+ },
+ child: const Text('Show Sheet'),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _MySheet extends StatelessWidget {
+ const _MySheet();
+
+ @override
+ Widget build(BuildContext context) {
+ return ScrollableSheet(
+ child: Material(
+ child: SizedBox(
+ height: 600,
+ child: PageView(
+ children: const [
+ _PageViewItem(),
+ _PageViewItem(),
+ _PageViewItem(),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _PageViewItem extends StatefulWidget {
+ const _PageViewItem();
+
+ @override
+ State<_PageViewItem> createState() => _PageViewItemState();
+}
+
+class _PageViewItemState extends State<_PageViewItem>
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ return ListView.builder(
+ itemCount: 100,
+ itemBuilder: (context, index) {
+ return ListTile(
+ onTap: () {},
+ title: Text('Item $index'),
+ );
+ },
+ );
+ }
+}
diff --git a/cookbook/lib/tutorial/scrollable_sheet.dart b/example/lib/tutorial/scrollable_sheet.dart
similarity index 88%
rename from cookbook/lib/tutorial/scrollable_sheet.dart
rename to example/lib/tutorial/scrollable_sheet.dart
index 757c7fd0..503f4e11 100644
--- a/cookbook/lib/tutorial/scrollable_sheet.dart
+++ b/example/lib/tutorial/scrollable_sheet.dart
@@ -45,14 +45,14 @@ class _MySheet extends StatelessWidget {
child: buildSheetBackground(context, content),
// Optional: Comment out the following lines to add multiple stop positions.
//
- // minExtent: const Extent.proportional(0.2),
- // physics: StretchingSheetPhysics(
+ // minPosition: const SheetAnchor.proportional(0.2),
+ // physics: BouncingSheetPhysics(
// parent: SnappingSheetPhysics(
// snappingBehavior: SnapToNearest(
// snapTo: [
- // const Extent.proportional(0.2),
- // const Extent.proportional(0.5),
- // const Extent.proportional(1),
+ // const SheetAnchor.proportional(0.2),
+ // const SheetAnchor.proportional(0.5),
+ // const SheetAnchor.proportional(1),
// ],
// ),
// ),
diff --git a/cookbook/lib/tutorial/sheet_content_scaffold.dart b/example/lib/tutorial/sheet_content_scaffold.dart
similarity index 88%
rename from cookbook/lib/tutorial/sheet_content_scaffold.dart
rename to example/lib/tutorial/sheet_content_scaffold.dart
index bc34c55c..1e8f55a8 100644
--- a/cookbook/lib/tutorial/sheet_content_scaffold.dart
+++ b/example/lib/tutorial/sheet_content_scaffold.dart
@@ -44,19 +44,20 @@ class _ExampleSheet extends StatelessWidget {
getIsVisible: (metrics) {
return metrics.viewportInsets.bottom == 0 &&
metrics.pixels >
- const Extent.proportional(0.5).resolve(metrics.contentSize);
+ const SheetAnchor.proportional(0.5)
+ .resolve(metrics.contentSize);
},
child: buildBottomBar(),
),
);
- final physics = StretchingSheetPhysics(
+ const physics = BouncingSheetPhysics(
parent: SnappingSheetPhysics(
- snappingBehavior: SnapToNearest(
- snapTo: [
- const Extent.proportional(0.2),
- const Extent.proportional(0.5),
- const Extent.proportional(1),
+ behavior: SnapToNearest(
+ anchors: [
+ SheetAnchor.proportional(0.2),
+ SheetAnchor.proportional(0.5),
+ SheetAnchor.proportional(1),
],
),
),
@@ -64,7 +65,7 @@ class _ExampleSheet extends StatelessWidget {
return DraggableSheet(
physics: physics,
- minExtent: const Extent.pixels(0),
+ minPosition: const SheetAnchor.pixels(0),
child: Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.zero,
diff --git a/cookbook/lib/tutorial/sheet_controller.dart b/example/lib/tutorial/sheet_controller.dart
similarity index 88%
rename from cookbook/lib/tutorial/sheet_controller.dart
rename to example/lib/tutorial/sheet_controller.dart
index c867e4c7..3840bec1 100644
--- a/cookbook/lib/tutorial/sheet_controller.dart
+++ b/example/lib/tutorial/sheet_controller.dart
@@ -47,12 +47,12 @@ class _ExampleHomeState extends State<_ExampleHome> {
child: Align(
alignment: Alignment.topCenter,
// Like ScrollController for scrollable widgets,
- // SheetController can be used to observe changes in the sheet extent.
+ // SheetController can be used to observe changes in the sheet position.
child: ValueListenableBuilder(
valueListenable: controller,
- builder: (context, metrics, child) {
+ builder: (context, pixels, child) {
return Text(
- 'Extent: ${metrics.maybePixels?.toStringAsFixed(1)}',
+ 'Position: ${pixels?.toStringAsFixed(1)}',
style: Theme.of(context).textTheme.displaySmall,
);
},
@@ -69,8 +69,8 @@ class _ExampleHomeState extends State<_ExampleHome> {
foregroundColor: Theme.of(context).colorScheme.onPrimary,
child: const Icon(Icons.arrow_downward_rounded),
onPressed: () {
- // SheetController can also be used to animate the sheet extent.
- controller.animateTo(const Extent.proportional(0.5));
+ // SheetController can also be used to animate the sheet position.
+ controller.animateTo(const SheetAnchor.proportional(0.5));
},
),
);
@@ -88,8 +88,8 @@ class _ExampleSheet extends StatelessWidget {
Widget build(BuildContext context) {
return DraggableSheet(
controller: controller,
- minExtent: const Extent.proportional(0.5),
- physics: const StretchingSheetPhysics(
+ minPosition: const SheetAnchor.proportional(0.5),
+ physics: const BouncingSheetPhysics(
parent: SnappingSheetPhysics(),
),
child: Card(
diff --git a/cookbook/lib/tutorial/sheet_draggable.dart b/example/lib/tutorial/sheet_draggable.dart
similarity index 93%
rename from cookbook/lib/tutorial/sheet_draggable.dart
rename to example/lib/tutorial/sheet_draggable.dart
index 62e46af7..d061d963 100644
--- a/cookbook/lib/tutorial/sheet_draggable.dart
+++ b/example/lib/tutorial/sheet_draggable.dart
@@ -65,8 +65,8 @@ class _ExampleSheet extends StatelessWidget {
],
);
- const minExtent = Extent.proportional(0.5);
- const physics = StretchingSheetPhysics(
+ const minPosition = SheetAnchor.proportional(0.5);
+ const physics = BouncingSheetPhysics(
parent: SnappingSheetPhysics(),
);
@@ -74,8 +74,8 @@ class _ExampleSheet extends StatelessWidget {
bottom: false,
child: ScrollableSheet(
physics: physics,
- minExtent: minExtent,
- initialExtent: minExtent,
+ minPosition: minPosition,
+ initialPosition: minPosition,
child: Card(
margin: EdgeInsets.zero,
color: Theme.of(context).colorScheme.secondaryContainer,
diff --git a/cookbook/lib/tutorial/sheet_physics.dart b/example/lib/tutorial/sheet_physics.dart
similarity index 76%
rename from cookbook/lib/tutorial/sheet_physics.dart
rename to example/lib/tutorial/sheet_physics.dart
index 69f346e4..9965fef3 100644
--- a/cookbook/lib/tutorial/sheet_physics.dart
+++ b/example/lib/tutorial/sheet_physics.dart
@@ -18,9 +18,9 @@ class _SheetPhysicsExample extends StatelessWidget {
enum _PhysicsKind {
clamping('Clamping'),
- stretching('Stretching'),
+ bouncing('Bouncing'),
clampingSnapping('Clamping + Snapping'),
- stretchingSnapping('Stretching + Snapping');
+ bouncingSnapping('Bouncing + Snapping');
final String name;
@@ -35,7 +35,7 @@ class _ExampleHome extends StatefulWidget {
}
class _ExampleHomeState extends State<_ExampleHome> {
- _PhysicsKind selectedPhysics = _PhysicsKind.stretchingSnapping;
+ _PhysicsKind selectedPhysics = _PhysicsKind.bouncingSnapping;
@override
Widget build(BuildContext context) {
@@ -84,46 +84,48 @@ class _MySheet extends StatelessWidget {
SheetPhysics createPhysics(_PhysicsKind kind) {
// With this configuration, the sheet will snap to:
- // - the extent at which ony (_halfwayFraction * 100)% of the content is visible, or
- // - the extent at which the entire content is visible.
- // Note that the "extent" is the visible height of the sheet.
- final snappingPhysics = SnappingSheetPhysics(
- snappingBehavior: SnapToNearest(
- snapTo: [
- const Extent.proportional(_halfwayFraction),
- const Extent.proportional(1),
+ // - the position at which ony (_halfwayFraction * 100)% of the content is visible, or
+ // - the position at which the entire content is visible.
+ // Note that the "position" is the visible height of the sheet.
+ const snappingPhysics = SnappingSheetPhysics(
+ behavior: SnapToNearest(
+ anchors: [
+ SheetAnchor.proportional(_halfwayFraction),
+ SheetAnchor.proportional(1),
],
),
// Tips: The above configuration can be replaced with a 'SnapToNearestEdge',
- // which will snap to either the 'minExtent' or 'maxExtent' of the sheet:
+ // which will snap to either the 'minPosition' or 'maxPosition' of the sheet:
// snappingBehavior: const SnapToNearestEdge(),
);
return switch (kind) {
_PhysicsKind.clamping => const ClampingSheetPhysics(),
- _PhysicsKind.stretching => const StretchingSheetPhysics(),
+ _PhysicsKind.bouncing => const BouncingSheetPhysics(),
_PhysicsKind.clampingSnapping =>
// Use 'parent' to combine multiple physics behaviors.
- ClampingSheetPhysics(parent: snappingPhysics),
- _PhysicsKind.stretchingSnapping =>
- StretchingSheetPhysics(parent: snappingPhysics),
+ const ClampingSheetPhysics(parent: snappingPhysics),
+ _PhysicsKind.bouncingSnapping =>
+ const BouncingSheetPhysics(parent: snappingPhysics),
};
}
@override
Widget build(BuildContext context) {
return DraggableSheet(
- // The 'minExtent' and 'maxExtent' properties determine
- // how far the sheet can be dragged. Note that "extent"
+ // The 'minPosition' and 'maxPosition' properties determine
+ // how far the sheet can be dragged. Note that "position"
// refers to the visible height of the sheet. For example,
// the configuration below ensures that the sheet is fully visible
// at first and can then be dragged down to (_halfwayFraction * 100)%
// of the sheet height at minimum.
- minExtent: const Extent.proportional(_halfwayFraction),
- maxExtent: const Extent.proportional(1), // Default
- initialExtent: const Extent.proportional(1), // Default
+ minPosition: const SheetAnchor.proportional(_halfwayFraction),
+ maxPosition: const SheetAnchor.proportional(1),
+ // Default
+ initialPosition: const SheetAnchor.proportional(1),
+ // Default
// 'physics' determines how the sheet will behave when the user reaches
- // the maximum or minimum extent, or when the user stops dragging.
+ // the maximum or minimum position, or when the user stops dragging.
physics: createPhysics(physicsKind),
child: buildContent(context),
);
diff --git a/example/lib/tutorial/textfield_with_multiple_stops.dart b/example/lib/tutorial/textfield_with_multiple_stops.dart
new file mode 100644
index 00000000..08bd265b
--- /dev/null
+++ b/example/lib/tutorial/textfield_with_multiple_stops.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+
+void main() {
+ runApp(const TextFieldWithMultipleStops());
+}
+
+class TextFieldWithMultipleStops extends StatelessWidget {
+ const TextFieldWithMultipleStops({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Stack(
+ children: [
+ const Scaffold(),
+ ScrollableSheet(
+ initialPosition: const SheetAnchor.proportional(0.7),
+ minPosition: const SheetAnchor.proportional(0.4),
+ physics: const BouncingSheetPhysics(
+ parent: SnappingSheetPhysics(
+ behavior: SnapToNearest(
+ anchors: [
+ SheetAnchor.proportional(0.4),
+ SheetAnchor.proportional(0.7),
+ SheetAnchor.proportional(1),
+ ],
+ ),
+ ),
+ ),
+ child: SheetContentScaffold(
+ primary: true,
+ backgroundColor: Colors.grey,
+ appBar: AppBar(
+ backgroundColor: Colors.grey,
+ title: const Text('Sheet with a TextField'),
+ leading: IconButton(
+ onPressed: () => primaryFocus?.unfocus(),
+ icon: const Icon(Icons.keyboard_hide),
+ ),
+ ),
+ body: ConstrainedBox(
+ constraints: const BoxConstraints(minHeight: 200),
+ child: const SingleChildScrollView(
+ child: TextField(maxLines: null),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/example/pubspec.lock b/example/pubspec.lock
new file mode 100644
index 00000000..4b9761c5
--- /dev/null
+++ b/example/pubspec.lock
@@ -0,0 +1,273 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ animations:
+ dependency: "direct main"
+ description:
+ name: animations
+ sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.11"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.18.0"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ faker:
+ dependency: "direct main"
+ description:
+ name: faker
+ sha256: "746e59f91d8b06a389e74cf76e909a05ed69c12691768e2f93557fdf29200fd0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ go_router:
+ dependency: "direct main"
+ description:
+ name: go_router
+ sha256: c5fa45fa502ee880839e3b2152d987c44abae26d064a2376d4aad434cf0f7b15
+ url: "https://pub.dev"
+ source: hosted
+ version: "12.1.3"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.4"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.16+1"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.12.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.0"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ smooth_sheets:
+ dependency: "direct main"
+ description:
+ path: ".."
+ relative: true
+ source: path
+ version: "0.10.0"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.0"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.2"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.2.1"
+sdks:
+ dart: ">=3.3.0 <4.0.0"
+ flutter: ">=3.18.0-18.0.pre.54"
diff --git a/cookbook/pubspec.yaml b/example/pubspec.yaml
similarity index 94%
rename from cookbook/pubspec.yaml
rename to example/pubspec.yaml
index ae76337d..113f14c9 100644
--- a/cookbook/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -14,7 +14,7 @@ dependencies:
go_router: ^12.1.3
smooth_sheets:
- path: ../package
+ path: ../
dev_dependencies:
flutter_test:
diff --git a/lib/.idea/lib.iml b/lib/.idea/lib.iml
new file mode 100644
index 00000000..d6ebd480
--- /dev/null
+++ b/lib/.idea/lib.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/misc.xml b/lib/.idea/misc.xml
new file mode 100644
index 00000000..a6e1098c
--- /dev/null
+++ b/lib/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/modules.xml b/lib/.idea/modules.xml
new file mode 100644
index 00000000..b0c4ff69
--- /dev/null
+++ b/lib/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/.idea/other.xml b/lib/.idea/other.xml
new file mode 100644
index 00000000..49481ad4
--- /dev/null
+++ b/lib/.idea/other.xml
@@ -0,0 +1,329 @@
+
+
+
+
+ 1714394570665
+
+
+
+
\ No newline at end of file
diff --git a/package/.metadata b/package/.metadata
deleted file mode 100644
index fa347fc6..00000000
--- a/package/.metadata
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- channel: stable
-
-project_type: package
diff --git a/package/LICENSE b/package/LICENSE
deleted file mode 100644
index 0e89d180..00000000
--- a/package/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2023 Daichi Fujita
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/package/README.md b/package/README.md
deleted file mode 100644
index 9c23c0bb..00000000
--- a/package/README.md
+++ /dev/null
@@ -1,385 +0,0 @@
-# smooth_sheets
-
-[![GitHub Repo stars](https://img.shields.io/github/stars/fujidaiti/smooth_sheets)](https://github.com/fujidaiti/smooth_sheets) [![GitHub last commit (branch)](https://img.shields.io/github/last-commit/fujidaiti/smooth_sheets/main?logo=git)](https://github.com/fujidaiti/smooth_sheets/commits/main/) [![Pub Version](https://img.shields.io/pub/v/smooth_sheets)](https://pub.dev/packages/smooth_sheets) ![Pub Likes](https://img.shields.io/pub/likes/smooth_sheets) ![Pub Points](https://img.shields.io/pub/points/smooth_sheets) ![Pub Popularity](https://img.shields.io/pub/popularity/smooth_sheets)
-
-
-**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.
-- **iOS flavor**: The modal sheets in the style of iOS 15 are supported.
-
-
-
-**CAUTION🚨**
-
-This library is currently in the experimental stage. The API may undergo changes without prior notice. For documentation of the latest published version, please visit the [package site](https://pub.dev/packages/smooth_sheets) on pub.dev.
-
-
-
-## Migration guide
-
-- [0.6.x to 0.7.x](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.7.x.md) 🆕
-- [0.5.x to 0.6.x](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.6.x.md)
-
-See [here](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/) for older versions.
-
-
-
-## Showcases
-
-
-
-
-
-
AI Playlist Generator
-
An AI assistant that helps create a music playlist based on the user's preferences. See the cookbook for more details.
-
Key components:
-
-
NavigationSheet
-
ModalSheetPage
-
DraggableNavigationSheetPage
-
ScrollableNavigationSheetPage
-
SheetDismissible
-
-
-
-
-
-
-
Safari app
-
A practical example of ios-style modal sheets. See the cookbook for more details.
-
Key components:
-
-
CupertinoStackedTransition
-
CupertinoModalSheetRoute
-
-
-
-
-
-
-
Airbnb mobile app clone
-
A partial clone of the Airbnb mobile app. The user can drag the house list down to reveal the map behind it. See the cookbook for more details.
-
Key components:
-
-
ScrollableSheet
-
SheetPhysics
-
SheetController
-
ExtentDrivenAnimation
-
-
-
-
-
-
-
Todo List
-
A simple Todo app that shows how a sheet handles the on-screen keyboard. See the cookbook for more details.
-
Used components:
-
-
ScrollableSheet
-
SheetContentScaffold
-
SheetKeyboardDismissBehavior
-
SheetDismissible
-
StickyBottomBarVisibility
-
-
-
-
-
-
-
-
-
-
-## 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:
-
-| | wolt_modal_sheet | smooth_sheets |
-| :--------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
-| Design | Based on Wolt's [design guideline](https://careers.wolt.com/en/blog/tech/an-overview-of-the-multi-page-scrollable-bottom-sheet-ui-design) | Not restricted to a specific design, fully customizable |
-| Navigation mechanism | [Manage the page index in ValueNotifier](https://github.com/woltapp/wolt_modal_sheet#usage) | Works with built-in Navigator API (both of imperative and declarative) |
-| Scrollable content | [Supported](https://github.com/woltapp/wolt_modal_sheet#scrollable-content) | Supported |
-| Persistent sheets | Not supported | Supported |
-| Screen size adaptation | [The sheet appears as a dialog on large screens](https://github.com/woltapp/wolt_modal_sheet#responsive-design) | Not supported |
-
-
-
-## Usage
-
-Several resources are available for learning the functionalities of this package.
-
-- Tutorials: See [cookbook/lib/tutorial/](https://github.com/fujidaiti/smooth_sheets/tree/main/cookbook/lib/tutorial) to learn the basic usage of the core components.
-- Showcases: More practical examples are available in [cookbook/lib/showcase/](https://github.com/fujidaiti/smooth_sheets/tree/main/cookbook/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.
-
-
-
-### Extent
-
-Extent 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.
-
-
-
-### DraggableSheet
-
-
-
-
-
-
-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/cookbook/lib/tutorial/draggable_sheet.dart) for basic usage.
-
-
-
-### ScrollableSheet
-
-
-
-
-
-
-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/cookbook/lib/tutorial/scrollable_sheet.dart) for basic usage.
-
-
-
-### NavigationSheet
-
-
-
-
-
-
-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/cookbook/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/cookbook/lib/tutorial/imperative_navigation_sheet.dart), a tutorial of integration with imperative Navigator API.
-
-
-
-### ModalSheets
-
-
-
-
-
-
-
-
-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.
-
-
-
-
-See also:
-
-- [declarative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/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/cookbook/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/cookbook/lib/tutorial/cupertino_modal_sheet.dart), a tutorial of iOS style modal sheets.
-- [showcase/todo_list](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/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.
-
-
-
-### SheetPhysics
-
-
-
-
-
-
-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 draggable bounds
-- StretchingSheetPhysics: Allows the sheet to go beyond the draggable bounds, but then bounce the sheet back to the edge of those bounds
-- 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. stretching behavior + snapping behavior).
-
-
-
-See also:
-
-- [sheet_physics.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/sheet_physics.dart) for basic usage.
-
-
-
-### SheetController
-
-
-
-
-
-
-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/cookbook/lib/tutorial/sheet_controller.dart) for basic usage.
-
-
-
-### SheetContentScaffold
-
-
-
-
-
-
-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/cookbook/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/cookbook/lib/tutorial/bottom_bar_visibility.dart), which shows the basic usage of BottomBarVisibility widgets.
-
-
-
-### 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/cookbook/lib/tutorial/sheet_draggable.dart) for basic usage.
-
-
-
-### ExtentDrivenAnimation
-
-
-
-
-
-It is easy to create sheet extent driven animations by using ExtentDrivenAnimation, 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:
-
-- [extent_driven_animation](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/extent_driven_animation.dart) for basic usage.
-- [airbnb_mobile_app.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/showcase/airbnb_mobile_app.dart), which show how ExtentDrivenAnimation 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.
-
-```dart
-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.
-
-
-
-### SheetKeyboardDismissBehavior
-
-
-
-
-
-
-[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.
-
-- [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/cookbook/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).
-
-
-
-## 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**.
-
-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`)
-3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
-4. Push to the Branch (`git push origin feature/AmazingFeature`)
-5. Open a Pull Request
-
-
-
-## Links
-
-- [Roadmap](https://github.com/fujidaiti/smooth_sheets/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22)
-- [API Documentation](https://pub.dev/documentation/smooth_sheets/latest/)
-- [pub.dev](https://pub.dev/packages/smooth_sheets)
-- [norelease.dev](https://pub.dev/publishers/norelease.dev/packages)
-
-
-
diff --git a/package/example/.metadata b/package/example/.metadata
deleted file mode 100644
index a778330b..00000000
--- a/package/example/.metadata
+++ /dev/null
@@ -1,45 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
- channel: "stable"
-
-project_type: app
-
-# Tracks metadata for the flutter migrate command
-migration:
- platforms:
- - platform: root
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- - platform: android
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- - platform: ios
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- - platform: linux
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- - platform: macos
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- - platform: web
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- - platform: windows
- create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
-
- # User provided section
-
- # List of Local paths (relative to this file) that should be
- # ignored by the migrate tool.
- #
- # Files that are not part of the templates will be ignored by default.
- unmanaged_files:
- - 'lib/main.dart'
- - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/package/example/README.md b/package/example/README.md
deleted file mode 100644
index 0e84df66..00000000
--- a/package/example/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Examples
-
-Many examples are available in the [cookbook](https://github.com/fujidaiti/smooth_sheets/tree/main/cookbook/lib).
diff --git a/package/lib/src/draggable/draggable_sheet.dart b/package/lib/src/draggable/draggable_sheet.dart
deleted file mode 100644
index 76d2e61f..00000000
--- a/package/lib/src/draggable/draggable_sheet.dart
+++ /dev/null
@@ -1,111 +0,0 @@
-import 'package:flutter/widgets.dart';
-
-import '../foundation/keyboard_dismissible.dart';
-import '../foundation/sheet_controller.dart';
-import '../foundation/sheet_extent.dart';
-import '../foundation/sheet_gesture_tamperer.dart';
-import '../foundation/sheet_physics.dart';
-import '../foundation/sheet_theme.dart';
-import '../foundation/sheet_viewport.dart';
-import '../scrollable/scrollable_sheet.dart';
-import 'draggable_sheet_extent.dart';
-import 'sheet_draggable.dart';
-
-/// A sheet that can be dragged.
-///
-/// Note that this widget does not work with scrollable widgets.
-/// Instead, use [ScrollableSheet] for this usecase.
-class DraggableSheet extends StatelessWidget {
- /// Creates a sheet that can be dragged.
- ///
- /// The maximum height will be equal to the [child]'s height.
- ///
- /// The [physics] determines how the sheet will behave when over-dragged
- /// or under-dragged, or when the user stops dragging.
- ///
- /// The [hitTestBehavior] defaults to [HitTestBehavior.translucent].
- ///
- /// See also:
- /// - [A tutorial](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/draggable_sheet.dart),
- /// minimal code to use a draggable sheet.
- const DraggableSheet({
- super.key,
- this.hitTestBehavior = HitTestBehavior.translucent,
- this.keyboardDismissBehavior,
- this.initialExtent = const Extent.proportional(1),
- this.minExtent = const Extent.proportional(1),
- this.maxExtent = const Extent.proportional(1),
- this.physics,
- required this.child,
- this.controller,
- });
-
- /// The strategy to dismiss the on-screen keyboard when the sheet is dragged.
- final SheetKeyboardDismissBehavior? keyboardDismissBehavior;
-
- final Extent initialExtent;
-
- /// {@macro SheetExtentConfig.minExtent}
- final Extent minExtent;
-
- /// {@macro SheetExtentConfig.maxExtent}
- final Extent maxExtent;
-
- /// {@macro SheetExtentConfig.physics}
- final SheetPhysics? physics;
-
- /// An object that can be used to control and observe the sheet height.
- final SheetController? controller;
-
- /// The content of the sheet.
- final Widget child;
-
- /// How to behave during hit testing.
- ///
- /// This value will be passed to the constructor of internal [SheetDraggable].
- final HitTestBehavior hitTestBehavior;
-
- @override
- Widget build(BuildContext context) {
- final theme = SheetTheme.maybeOf(context);
- final physics = this.physics ?? theme?.physics ?? kDefaultSheetPhysics;
- final keyboardDismissBehavior =
- this.keyboardDismissBehavior ?? theme?.keyboardDismissBehavior;
- final gestureTamper = TamperSheetGesture.maybeOf(context);
-
- Widget result = ImplicitSheetControllerScope(
- controller: controller,
- builder: (context, controller) {
- return SheetExtentScope(
- controller: controller,
- factory: const DraggableSheetExtentFactory(),
- config: DraggableSheetExtentConfig(
- initialExtent: initialExtent,
- minExtent: minExtent,
- maxExtent: maxExtent,
- physics: physics,
- gestureTamperer: gestureTamper,
- debugLabel: 'DraggableSheet',
- ),
- child: SheetViewport(
- child: SheetContentViewport(
- child: SheetDraggable(
- behavior: hitTestBehavior,
- child: child,
- ),
- ),
- ),
- );
- },
- );
-
- if (keyboardDismissBehavior != null) {
- result = SheetKeyboardDismissible(
- dismissBehavior: keyboardDismissBehavior,
- child: result,
- );
- }
-
- return result;
- }
-}
diff --git a/package/lib/src/draggable/draggable_sheet_extent.dart b/package/lib/src/draggable/draggable_sheet_extent.dart
deleted file mode 100644
index b5f263b0..00000000
--- a/package/lib/src/draggable/draggable_sheet_extent.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/rendering.dart';
-import 'package:meta/meta.dart';
-
-import '../foundation/sheet_extent.dart';
-
-@internal
-class DraggableSheetExtentFactory extends SheetExtentFactory<
- DraggableSheetExtentConfig, DraggableSheetExtent> {
- const DraggableSheetExtentFactory();
-
- @override
- DraggableSheetExtent createSheetExtent({
- required SheetContext context,
- required DraggableSheetExtentConfig config,
- }) {
- return DraggableSheetExtent(context: context, config: config);
- }
-}
-
-@internal
-class DraggableSheetExtentConfig extends SheetExtentConfig {
- const DraggableSheetExtentConfig({
- required this.initialExtent,
- required super.minExtent,
- required super.maxExtent,
- required super.physics,
- required super.gestureTamperer,
- super.debugLabel,
- });
-
- final Extent initialExtent;
-
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) return true;
- return other is DraggableSheetExtentConfig &&
- other.initialExtent == initialExtent &&
- super == other;
- }
-
- @override
- int get hashCode => Object.hash(initialExtent, super.hashCode);
-}
-
-@internal
-class DraggableSheetExtent extends SheetExtent {
- DraggableSheetExtent({
- required super.context,
- required super.config,
- });
-
- @override
- void applyNewContentSize(Size contentSize) {
- super.applyNewContentSize(contentSize);
- if (metrics.maybePixels == null) {
- setPixels(config.initialExtent.resolve(metrics.contentSize));
- }
- }
-}
diff --git a/package/lib/src/foundation/sheet_activity.dart b/package/lib/src/foundation/sheet_activity.dart
deleted file mode 100644
index 1c86ff9e..00000000
--- a/package/lib/src/foundation/sheet_activity.dart
+++ /dev/null
@@ -1,304 +0,0 @@
-import 'dart:async';
-import 'dart:math';
-
-import 'package:flutter/widgets.dart';
-import 'package:meta/meta.dart';
-
-import 'sheet_drag.dart';
-import 'sheet_extent.dart';
-import 'sheet_status.dart';
-
-@internal
-@optionalTypeArgs
-abstract class SheetActivity {
- bool _disposed = false;
- bool get disposed {
- assert(!_mounted || !_disposed);
- return _disposed;
- }
-
- bool _mounted = false;
- bool get mounted {
- assert(!_mounted || !_disposed);
- return _mounted;
- }
-
- T? _owner;
- T get owner {
- assert(debugAssertMounted());
- return _owner!;
- }
-
- double get velocity => 0.0;
-
- SheetStatus get status;
-
- @mustCallSuper
- void init(T owner) {
- assert(_owner == null);
- assert(!_mounted);
- assert(!_disposed);
-
- _owner = owner;
- _mounted = true;
- }
-
- @mustCallSuper
- void updateOwner(T owner) {
- _owner = owner;
- }
-
- void dispose() {
- _mounted = false;
- _disposed = true;
- }
-
- bool isCompatibleWith(SheetExtent newOwner) => newOwner is T;
-
- void didChangeContentSize(Size? oldSize) {}
-
- void didChangeViewportDimensions(Size? oldSize, EdgeInsets? oldInsets) {}
-
- void didChangeBoundaryConstraints(
- double? oldMinPixels,
- double? oldMaxPixels,
- ) {}
-
- void didFinalizeDimensions(
- Size? oldContentSize,
- Size? oldViewportSize,
- EdgeInsets? oldViewportInsets,
- ) {
- if (oldContentSize == null && oldViewportSize == null) {
- // The sheet was laid out, but not changed in size.
- return;
- }
-
- final metrics = owner.metrics;
- final oldPixels = metrics.pixels;
- final newInsets = metrics.viewportInsets;
- final oldInsets = oldViewportInsets ?? newInsets;
- final deltaInsetBottom = newInsets.bottom - oldInsets.bottom;
-
- switch (deltaInsetBottom) {
- case > 0:
- // Prevents the sheet from being pushed off the screen by the keyboard.
- final correction = min(0.0, metrics.maxViewPixels - metrics.viewPixels);
- owner.setPixels(oldPixels + correction);
-
- case < 0:
- // Appends the delta of the bottom inset (typically the keyboard height)
- // to keep the visual sheet position unchanged.
- owner.setPixels(
- min(oldPixels - deltaInsetBottom, owner.metrics.maxPixels),
- );
- }
-
- owner.settle();
- }
-
- @protected
- bool debugAssertMounted() {
- assert(() {
- if (!mounted) {
- throw FlutterError(
- 'A $runtimeType was used after being disposed, or '
- 'before init() was called. Once you have called dispose() '
- 'on a $runtimeType, it can no longer be used.',
- );
- }
- return true;
- }());
- return true;
- }
-
- @protected
- bool debugAssertNotDisposed() {
- assert(() {
- if (disposed) {
- throw FlutterError(
- 'A $runtimeType was used after being disposed. Once you have '
- 'called dispose() on a $runtimeType, it can no longer be used.',
- );
- }
- return true;
- }());
- return true;
- }
-}
-
-@internal
-class AnimatedSheetActivity extends SheetActivity
- with ControlledSheetActivityMixin {
- AnimatedSheetActivity({
- required this.from,
- required this.to,
- required this.duration,
- required this.curve,
- }) : assert(duration > Duration.zero);
-
- final double from;
- final double to;
- final Duration duration;
- final Curve curve;
-
- @override
- AnimationController createAnimationController() {
- return AnimationController.unbounded(
- value: from, vsync: owner.context.vsync);
- }
-
- @override
- TickerFuture onAnimationStart() {
- return controller.animateTo(to, duration: duration, curve: curve);
- }
-
- @override
- void onAnimationEnd() {
- owner.goBallistic(0);
- }
-}
-
-@internal
-class BallisticSheetActivity extends SheetActivity
- with ControlledSheetActivityMixin {
- BallisticSheetActivity({
- required this.simulation,
- });
-
- final Simulation simulation;
-
- @override
- AnimationController createAnimationController() {
- return AnimationController.unbounded(vsync: owner.context.vsync);
- }
-
- @override
- TickerFuture onAnimationStart() {
- return controller.animateWith(simulation);
- }
-
- @override
- void onAnimationEnd() {
- owner.goBallistic(0);
- }
-}
-
-@internal
-class IdleSheetActivity extends SheetActivity {
- @override
- SheetStatus get status => SheetStatus.stable;
-}
-
-@internal
-class DragSheetActivity extends SheetActivity
- with UserControlledSheetActivityMixin
- implements SheetDragControllerTarget {
- DragSheetActivity();
-
- @override
- VerticalDirection get dragAxisDirection => VerticalDirection.up;
-
- @override
- Offset computeMinPotentialDeltaConsumption(Offset delta) {
- final metrics = owner.metrics;
-
- switch (delta.dy) {
- case > 0:
- final draggableDistance = max(0.0, metrics.maxPixels - metrics.pixels);
- return Offset(delta.dx, min(draggableDistance, delta.dy));
-
- case < 0:
- final draggableDistance = max(0.0, metrics.pixels - metrics.minPixels);
- return Offset(delta.dx, max(-1 * draggableDistance, delta.dy));
-
- case _:
- return delta;
- }
- }
-
- @override
- void applyUserDragUpdate(Offset offset) {
- final physicsAppliedDelta =
- owner.config.physics.applyPhysicsToOffset(offset.dy, owner.metrics);
- if (physicsAppliedDelta != 0) {
- owner.setPixels(owner.metrics.pixels + physicsAppliedDelta);
- }
- }
-
- @override
- void applyUserDragEnd(Velocity velocity) {
- owner.goBallistic(velocity.pixelsPerSecond.dy);
- }
-}
-
-@internal
-@optionalTypeArgs
-mixin ControlledSheetActivityMixin on SheetActivity {
- late final AnimationController controller;
- late double _lastAnimatedValue;
-
- final _completer = Completer();
- Future get done => _completer.future;
-
- @factory
- AnimationController createAnimationController();
- TickerFuture onAnimationStart();
- void onAnimationEnd() {}
-
- @override
- double get velocity => controller.velocity;
-
- @override
- SheetStatus get status => SheetStatus.animating;
-
- @override
- void init(T delegate) {
- super.init(delegate);
- controller = createAnimationController()..addListener(onAnimationTick);
- // Won't trigger if we dispose 'animation' first.
- onAnimationStart().whenComplete(onAnimationEnd);
- _lastAnimatedValue = controller.value;
- }
-
- void onAnimationTick() {
- if (mounted) {
- final oldPixels = owner.metrics.pixels;
- owner.setPixels(oldPixels + controller.value - _lastAnimatedValue);
- _lastAnimatedValue = controller.value;
- }
- }
-
- @override
- void dispose() {
- controller.dispose();
- _completer.complete();
- super.dispose();
- }
-}
-
-@internal
-@optionalTypeArgs
-mixin UserControlledSheetActivityMixin
- on SheetActivity {
- @override
- SheetStatus get status => SheetStatus.dragging;
-
- @override
- void didFinalizeDimensions(
- Size? oldContentSize,
- Size? oldViewportSize,
- EdgeInsets? oldViewportInsets,
- ) {
- assert(owner.metrics.hasDimensions);
-
- final newInsets = owner.metrics.viewportInsets;
- final oldInsets = oldViewportInsets ?? newInsets;
- final deltaInsetBottom = newInsets.bottom - oldInsets.bottom;
- // Appends the delta of the bottom inset (typically the keyboard height)
- // to keep the visual sheet position unchanged.
- owner.setPixels(owner.metrics.pixels - deltaInsetBottom);
- // We don't call `goSettling` here because the user is still
- // manually controlling the sheet position.
- }
-}
diff --git a/package/lib/src/foundation/sheet_extent.dart b/package/lib/src/foundation/sheet_extent.dart
deleted file mode 100644
index 0e37a94b..00000000
--- a/package/lib/src/foundation/sheet_extent.dart
+++ /dev/null
@@ -1,1036 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
-import 'package:flutter/widgets.dart';
-import 'package:meta/meta.dart';
-
-import '../internal/double_utils.dart';
-import 'sheet_activity.dart';
-import 'sheet_controller.dart';
-import 'sheet_drag.dart';
-import 'sheet_gesture_tamperer.dart';
-import 'sheet_notification.dart';
-import 'sheet_physics.dart';
-import 'sheet_status.dart';
-
-/// A representation of a 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.
-///
-/// See also:
-/// - [ProportionalExtent], which is proportional to the content height.
-/// - [FixedExtent], which is defined by a concrete value in pixels.
-abstract interface class Extent {
- /// {@macro fixed_extent}
- const factory Extent.pixels(double pixels) = FixedExtent;
-
- /// {@macro proportional_extent}
- const factory Extent.proportional(double size) = ProportionalExtent;
-
- /// Resolves the extent to a concrete value in pixels.
- ///
- /// Do not cache the value of [contentSize] because
- /// it may change over time.
- double resolve(Size contentSize);
-}
-
-/// An extent that is proportional to the content height.
-class ProportionalExtent implements Extent {
- /// {@template proportional_extent}
- /// Creates an extent that is proportional to the content height.
- ///
- /// The [factor] must be greater than or equal to 0.
- /// This extent will resolve to `contentSize.height * factor`.
- /// {@endtemplate}
- const ProportionalExtent(this.factor) : assert(factor >= 0);
-
- /// The fraction of the content height.
- final double factor;
-
- @override
- double resolve(Size contentSize) => contentSize.height * factor;
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is ProportionalExtent &&
- runtimeType == other.runtimeType &&
- factor == other.factor);
-
- @override
- int get hashCode => Object.hash(runtimeType, factor);
-}
-
-/// An extent that has an concrete value in pixels.
-class FixedExtent implements Extent {
- /// {@template fixed_extent}
- /// Creates an extent from a concrete value in pixels.
- /// {@endtemplate}
- const FixedExtent(this.pixels) : assert(pixels >= 0);
-
- /// The value in pixels.
- final double pixels;
-
- @override
- double resolve(Size contentSize) => pixels;
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is FixedExtent &&
- runtimeType == other.runtimeType &&
- pixels == other.pixels);
-
- @override
- int get hashCode => Object.hash(runtimeType, pixels);
-}
-
-/// Manages the extent of a sheet.
-///
-/// This object is much like [ScrollPosition] for scrollable widgets.
-/// The [SheetMetrics.pixels] value determines the visible height of a sheet.
-/// As this value changes, the sheet translates its position, which changes the
-/// visible area of the content. The [SheetMetrics.minPixels] and
-/// [SheetMetrics.maxPixels] values limit the range of the *pixels*, but it can
-/// be outside of the range if the [SheetExtentConfig.physics] allows it.
-///
-/// The current [activity] is responsible for how the *pixels* changes
-/// over time, for example, [AnimatedSheetActivity] animates the *pixels* to
-/// a target value, and [IdleSheetActivity] keeps the *pixels* unchanged.
-/// [SheetExtent] starts with [IdleSheetActivity] as the initial activity,
-/// and it can be changed by calling [beginActivity].
-///
-/// This object is [Listenable] that notifies its listeners when *pixels*
-/// changes, even during build or layout phase. For listeners that can cause
-/// any widget to rebuild, consider using [SheetController], which is also
-/// [Listenable] of the extent, but only notifies its listeners between frames.
-///
-/// See also:
-/// - [SheetController], which can be attached to a sheet to control its extent.
-/// - [SheetExtentScope], which creates a [SheetExtent], manages its lifecycle,
-/// and exposes it to the descendant widgets.
-@internal
-@optionalTypeArgs
-abstract class SheetExtent extends ChangeNotifier
- implements ValueListenable {
- /// Creates an object that manages the extent of a sheet.
- SheetExtent({
- required this.context,
- required T config,
- }) : _config = config {
- goIdle();
- }
-
- @override
- SheetMetrics get value => metrics;
-
- SheetStatus get status => activity.status;
-
- /// A handle to the owner of this object.
- final SheetContext context;
-
- T get config => _config;
- T _config;
-
- /// Snapshot of the current sheet's state.
- SheetMetrics get metrics => _metrics;
- SheetMetrics _metrics = SheetMetrics.empty;
-
- /// The current activity of the sheet.
- SheetActivity get activity => _activity!;
- SheetActivity? _activity;
-
- /// The current drag that is currently driving the sheet.
- ///
- /// Intentionally exposed so that a subclass can override
- /// the default implementation of [drag].
- @protected
- SheetDragController? currentDrag;
-
- @mustCallSuper
- void takeOver(SheetExtent other) {
- assert(currentDrag == null);
- if (other.activity.isCompatibleWith(this)) {
- activity.dispose();
- _activity = other.activity;
- // This is necessary to prevent the activity from being disposed of
- // when `other` extent is disposed of.
- other._activity = null;
- activity.updateOwner(this);
-
- if ((other.currentDrag, activity)
- case (final drag?, final SheetDragControllerTarget dragActivity)) {
- currentDrag = drag..updateTarget(dragActivity);
- other.currentDrag = null;
- }
- } else {
- goIdle();
- }
- if (other.metrics.maybePixels case final pixels?) {
- correctPixels(pixels);
- }
- applyNewViewportDimensions(
- other.metrics.viewportSize,
- other.metrics.viewportInsets,
- );
- applyNewContentSize(other.metrics.contentSize);
- }
-
- @mustCallSuper
- void applyNewContentSize(Size contentSize) {
- if (metrics.maybeContentSize != contentSize) {
- final oldMaxPixels = metrics.maybeMaxPixels;
- final oldMinPixels = metrics.maybeMinPixels;
- _oldContentSize = metrics.maybeContentSize;
- _metrics = metrics.copyWith(
- contentSize: contentSize,
- minPixels: config.minExtent.resolve(contentSize),
- maxPixels: config.maxExtent.resolve(contentSize),
- );
- activity.didChangeContentSize(_oldContentSize);
- if (oldMinPixels != metrics.minPixels ||
- oldMaxPixels != metrics.maxPixels) {
- activity.didChangeBoundaryConstraints(oldMinPixels, oldMaxPixels);
- }
- }
- }
-
- @mustCallSuper
- void applyNewViewportDimensions(Size size, EdgeInsets insets) {
- if (metrics.maybeViewportSize != size ||
- metrics.maybeViewportInsets != insets) {
- _oldViewportSize = metrics.maybeViewportSize;
- _oldViewportInsets = metrics.maybeViewportInsets;
- _metrics = metrics.copyWith(viewportSize: size, viewportInsets: insets);
- activity.didChangeViewportDimensions(
- _oldViewportSize,
- _oldViewportInsets,
- );
- }
- }
-
- @mustCallSuper
- void applyNewConfig(T config) {
- if (_config != config) {
- _config = config;
- currentDrag?.updateGestureTamperer(config.gestureTamperer);
- final oldMaxPixels = metrics.maxPixels;
- final oldMinPixels = metrics.minPixels;
- _metrics = metrics.copyWith(
- minPixels: config.minExtent.resolve(metrics.contentSize),
- maxPixels: config.maxExtent.resolve(metrics.contentSize),
- );
- if (oldMinPixels != metrics.minPixels ||
- oldMaxPixels != metrics.maxPixels) {
- activity.didChangeBoundaryConstraints(oldMinPixels, oldMaxPixels);
- }
- }
- }
-
- Size? _oldContentSize;
- Size? _oldViewportSize;
- EdgeInsets? _oldViewportInsets;
- int _markAsDimensionsWillChangeCallCount = 0;
-
- @mustCallSuper
- void markAsDimensionsWillChange() {
- assert(() {
- if (_markAsDimensionsWillChangeCallCount == 0) {
- // Ensure that the number of calls to markAsDimensionsWillChange()
- // matches the number of calls to markAsDimensionsChanged().
- WidgetsBinding.instance.addPostFrameCallback((_) {
- assert(
- _markAsDimensionsWillChangeCallCount == 0,
- _markAsDimensionsWillChangeCallCount > 0
- ? _debugMessage(
- 'markAsDimensionsWillChange() was called more times '
- 'than markAsDimensionsChanged() in a frame.',
- )
- : _debugMessage(
- 'markAsDimensionsChanged() was called more times '
- 'than markAsDimensionsWillChange() in a frame.',
- ),
- );
- });
- }
- return true;
- }());
-
- if (_markAsDimensionsWillChangeCallCount == 0) {
- _oldContentSize = null;
- _oldViewportSize = null;
- _oldViewportInsets = null;
- }
-
- _markAsDimensionsWillChangeCallCount++;
- }
-
- @mustCallSuper
- void markAsDimensionsChanged() {
- assert(
- _markAsDimensionsWillChangeCallCount > 0,
- _debugMessage(
- 'markAsDimensionsChanged() called without '
- 'a matching call to markAsDimensionsWillChange().',
- ),
- );
-
- _markAsDimensionsWillChangeCallCount--;
- if (_markAsDimensionsWillChangeCallCount == 0) {
- onDimensionsFinalized();
- }
- }
-
- @mustCallSuper
- void onDimensionsFinalized() {
- assert(
- _markAsDimensionsWillChangeCallCount == 0,
- _debugMessage(
- 'Do not call this method until all dimensions changes are finalized.',
- ),
- );
- assert(
- metrics.hasDimensions,
- _debugMessage(
- 'All the dimension values must be finalized '
- 'at the time onDimensionsFinalized() is called.',
- ),
- );
-
- _activity!.didFinalizeDimensions(
- _oldContentSize,
- _oldViewportSize,
- _oldViewportInsets,
- );
-
- _oldContentSize = null;
- _oldViewportSize = null;
- _oldViewportInsets = null;
- }
-
- @mustCallSuper
- void beginActivity(SheetActivity activity) {
- final oldActivity = _activity;
- // Update the current activity before initialization.
- _activity = activity;
- activity.init(this);
-
- if (oldActivity == null) {
- return;
- }
-
- final wasDragging = oldActivity.status == SheetStatus.dragging;
- final isDragging = activity.status == SheetStatus.dragging;
-
- // TODO: Make more typesafe
- switch ((wasDragging, isDragging)) {
- case (true, true):
- assert(currentDrag != null);
- assert(activity is SheetDragControllerTarget);
- currentDrag!.updateTarget(activity as SheetDragControllerTarget);
-
- case (true, false):
- assert(currentDrag != null);
- dispatchDragEndNotification();
- currentDrag!.dispose();
- currentDrag = null;
-
- case (false, true):
- assert(currentDrag != null);
- dispatchDragStartNotification();
-
- case (false, false):
- assert(currentDrag == null);
- }
-
- oldActivity.dispose();
- }
-
- void goIdle() {
- beginActivity(IdleSheetActivity());
- }
-
- void goBallistic(double velocity) {
- assert(metrics.hasDimensions);
- final simulation =
- config.physics.createBallisticSimulation(velocity, metrics);
- if (simulation != null) {
- goBallisticWith(simulation);
- } else {
- goIdle();
- }
- }
-
- void goBallisticWith(Simulation simulation) {
- beginActivity(BallisticSheetActivity(simulation: simulation));
- }
-
- void settle() {
- assert(metrics.hasDimensions);
- final simulation = config.physics.createSettlingSimulation(metrics);
- if (simulation != null) {
- // TODO: Begin a SettlingSheetActivity
- goBallisticWith(simulation);
- } else {
- goIdle();
- }
- }
-
- Drag drag(
- DragStartDetails details,
- VoidCallback dragCancelCallback,
- ) {
- assert(currentDrag == null);
- final dragActivity = DragSheetActivity();
- var startDetails = SheetDragStartDetails(
- sourceTimeStamp: details.sourceTimeStamp,
- axisDirection: dragActivity.dragAxisDirection,
- localPositionX: details.localPosition.dx,
- localPositionY: details.localPosition.dy,
- globalPositionX: details.globalPosition.dx,
- globalPositionY: details.globalPosition.dy,
- kind: details.kind,
- );
- if (config.gestureTamperer case final tamperer?) {
- startDetails = tamperer.tamperWithDragStart(startDetails);
- }
-
- final drag = currentDrag = SheetDragController(
- target: dragActivity,
- gestureTamperer: config.gestureTamperer,
- details: startDetails,
- onDragCanceled: dragCancelCallback,
- // TODO: Specify a correct value.
- carriedVelocity: 0,
- motionStartDistanceThreshold:
- config.physics.dragStartDistanceMotionThreshold,
- );
- beginActivity(dragActivity);
- return drag;
- }
-
- @override
- void dispose() {
- _activity?.dispose();
- currentDrag?.dispose();
- _activity = null;
- currentDrag = null;
- super.dispose();
- }
-
- void setPixels(double pixels) {
- final oldPixels = metrics.maybePixels;
- correctPixels(pixels);
- if (oldPixels != pixels) {
- notifyListeners();
- if (currentDrag?.lastDetails is SheetDragUpdateDetails) {
- dispatchDragUpdateNotification();
- } else {
- dispatchUpdateNotification();
- }
- }
- }
-
- void correctPixels(double pixels) {
- if (metrics.maybePixels != pixels) {
- _metrics = metrics.copyWith(pixels: pixels);
- }
- }
-
- /// Animates the extent to the given value.
- ///
- /// The returned future completes when the animation ends,
- /// whether it completed successfully or whether it was
- /// interrupted prematurely.
- Future animateTo(
- Extent newExtent, {
- Curve curve = Curves.easeInOut,
- Duration duration = const Duration(milliseconds: 300),
- }) {
- assert(metrics.hasDimensions);
- final destination = newExtent.resolve(metrics.contentSize);
- if (metrics.pixels == destination) {
- return Future.value();
- } else {
- final activity = AnimatedSheetActivity(
- from: metrics.pixels,
- to: destination,
- duration: duration,
- curve: curve,
- );
-
- beginActivity(activity);
- return activity.done;
- }
- }
-
- void dispatchUpdateNotification() {
- if (metrics.hasDimensions) {
- _dispatchNotification(
- SheetUpdateNotification(
- metrics: metrics,
- status: status,
- ),
- );
- }
- }
-
- void dispatchDragStartNotification() {
- assert(metrics.hasDimensions);
- assert(currentDrag != null);
- final details = currentDrag!.lastDetails;
- assert(details is SheetDragStartDetails);
- _dispatchNotification(
- SheetDragStartNotification(
- metrics: metrics,
- dragDetails: details as SheetDragStartDetails,
- ),
- );
- }
-
- void dispatchDragEndNotification() {
- assert(metrics.hasDimensions);
- assert(currentDrag != null);
- final details = currentDrag!.lastDetails;
- assert(details is SheetDragEndDetails);
- _dispatchNotification(
- SheetDragEndNotification(
- metrics: metrics,
- dragDetails: details as SheetDragEndDetails,
- ),
- );
- }
-
- void dispatchDragUpdateNotification() {
- assert(metrics.hasDimensions);
- assert(currentDrag != null);
- final details = currentDrag!.lastDetails;
- assert(details is SheetDragUpdateDetails);
- _dispatchNotification(
- SheetDragUpdateNotification(
- metrics: metrics,
- dragDetails: details as SheetDragUpdateDetails,
- ),
- );
- }
-
- void dispatchOverflowNotification(double overflow) {
- assert(metrics.hasDimensions);
- _dispatchNotification(
- SheetOverflowNotification(
- metrics: metrics,
- status: status,
- overflow: overflow,
- ),
- );
- }
-
- void _dispatchNotification(SheetNotification notification) {
- // Avoid dispatching a notification in the middle of a build.
- switch (SchedulerBinding.instance.schedulerPhase) {
- case SchedulerPhase.postFrameCallbacks:
- notification.dispatch(context.notificationContext);
- case SchedulerPhase.idle:
- case SchedulerPhase.midFrameMicrotasks:
- case SchedulerPhase.persistentCallbacks:
- case SchedulerPhase.transientCallbacks:
- SchedulerBinding.instance.addPostFrameCallback((_) {
- notification.dispatch(context.notificationContext);
- });
- }
- }
-
- String _debugMessage(String message) {
- return switch (config.debugLabel) {
- null => message,
- final debugLabel => '$debugLabel: $message',
- };
- }
-}
-
-@internal
-class SheetExtentConfig {
- const SheetExtentConfig({
- required this.minExtent,
- required this.maxExtent,
- required this.physics,
- required this.gestureTamperer,
- this.debugLabel,
- });
-
- /// {@template SheetExtentConfig.minExtent}
- /// The minimum extent of the sheet.
- ///
- /// The sheet may below this extent if the [physics] allows it.
- /// {@endtemplate}
- final Extent minExtent;
-
- /// {@template SheetExtentConfig.maxExtent}
- /// The maximum extent of the sheet.
- ///
- /// The sheet may exceed this extent if the [physics] allows it.
- /// {@endtemplate}
- final Extent maxExtent;
-
- /// {@template SheetExtentConfig.physics}
- /// How the sheet extent should respond to user input.
- ///
- /// This determines how the sheet will behave when over-dragged or
- /// under-dragged, or when the user stops dragging.
- /// {@endtemplate}
- final SheetPhysics physics;
-
- final SheetGestureTamperer? gestureTamperer;
-
- /// A label that is used to identify this object in debug output.
- final String? debugLabel;
-
- @override
- bool operator ==(Object other) {
- return identical(this, other) ||
- (other is SheetExtentConfig &&
- runtimeType == other.runtimeType &&
- minExtent == other.minExtent &&
- maxExtent == other.maxExtent &&
- physics == other.physics &&
- gestureTamperer == other.gestureTamperer &&
- debugLabel == other.debugLabel);
- }
-
- @override
- int get hashCode => Object.hash(
- runtimeType,
- minExtent,
- maxExtent,
- physics,
- gestureTamperer,
- debugLabel,
- );
-}
-
-/// An immutable snapshot of the sheet's state.
-class SheetMetrics {
- /// Creates an immutable snapshot of the sheet's state.
- const SheetMetrics({
- required double? pixels,
- required double? minPixels,
- required double? maxPixels,
- required Size? contentSize,
- required Size? viewportSize,
- required EdgeInsets? viewportInsets,
- }) : maybePixels = pixels,
- maybeMinPixels = minPixels,
- maybeMaxPixels = maxPixels,
- maybeContentSize = contentSize,
- maybeViewportSize = viewportSize,
- maybeViewportInsets = viewportInsets;
-
- static const empty = SheetMetrics(
- pixels: null,
- minPixels: null,
- maxPixels: null,
- contentSize: null,
- viewportSize: null,
- viewportInsets: null,
- );
-
- final double? maybePixels;
- final double? maybeMinPixels;
- final double? maybeMaxPixels;
- final Size? maybeContentSize;
- final Size? maybeViewportSize;
- final EdgeInsets? maybeViewportInsets;
-
- /// The current extent of the sheet.
- double get pixels {
- assert(_debugAssertHasProperty('pixels', maybePixels));
- return maybePixels!;
- }
-
- /// The minimum extent of the sheet.
- double get minPixels {
- assert(_debugAssertHasProperty('minPixels', maybeMinPixels));
- return maybeMinPixels!;
- }
-
- /// The maximum extent of the sheet.
- double get maxPixels {
- assert(_debugAssertHasProperty('maxPixels', maybeMaxPixels));
- return maybeMaxPixels!;
- }
-
- /// The size of the sheet's content.
- Size get contentSize {
- assert(_debugAssertHasProperty('contentSize', maybeContentSize));
- return maybeContentSize!;
- }
-
- /// The size of the viewport that hosts the sheet.
- Size get viewportSize {
- assert(_debugAssertHasProperty('viewportSize', maybeViewportSize));
- return maybeViewportSize!;
- }
-
- EdgeInsets get viewportInsets {
- assert(_debugAssertHasProperty('viewportInsets', maybeViewportInsets));
- return maybeViewportInsets!;
- }
-
- /// The visible height of the sheet measured from the bottom of the viewport.
- ///
- /// If the on-screen keyboard is visible, this value is the sum of
- /// [pixels] and the keyboard's height. Otherwise, it is equal to [pixels].
- double get viewPixels => pixels + viewportInsets.bottom;
- double? get maybeViewPixels => hasDimensions ? viewPixels : null;
-
- /// The minimum visible height of the sheet measured from the bottom
- /// of the viewport.
- double get minViewPixels => minPixels + viewportInsets.bottom;
- double? get maybeMinViewPixels => hasDimensions ? minViewPixels : null;
-
- /// The maximum visible height of the sheet measured from the bottom
- /// of the viewport.
- double get maxViewPixels => maxPixels + viewportInsets.bottom;
- double? get maybeMaxViewPixels => hasDimensions ? maxViewPixels : null;
-
- /// Whether the all metrics are available.
- ///
- /// Returns true if all of [pixels], [minPixels], [maxPixels],
- /// [contentSize], [viewportInsets], and [viewportSize] are not null.
- bool get hasDimensions =>
- maybePixels != null &&
- maybeMinPixels != null &&
- maybeMaxPixels != null &&
- maybeContentSize != null &&
- maybeViewportSize != null &&
- maybeViewportInsets != null;
-
- /// Whether the sheet is within the range of [minPixels] and [maxPixels]
- /// (inclusive of both bounds).
- bool get isPixelsInBounds =>
- hasDimensions && pixels.isInBounds(minPixels, maxPixels);
-
- /// Whether the sheet is outside the range of [minPixels] and [maxPixels].
- bool get isPixelsOutOfBounds => !isPixelsInBounds;
-
- bool _debugAssertHasProperty(String name, Object? value) {
- assert(() {
- if (value == null) {
- throw FlutterError(
- 'SheetMetrics.$name cannot be accessed before the value is set. '
- 'Consider using the corresponding SheetMetrics.maybe* getter '
- 'to handle the case when the value is null. SheetMetrics.hasPixels '
- 'is also useful to check if all the metrics values are set '
- 'before accessing them.',
- );
- }
- return true;
- }());
- return true;
- }
-
- /// Creates a copy of this object with the given fields replaced.
- SheetMetrics copyWith({
- SheetStatus? status,
- double? pixels,
- double? minPixels,
- double? maxPixels,
- Size? contentSize,
- Size? viewportSize,
- EdgeInsets? viewportInsets,
- }) {
- return SheetMetrics(
- pixels: pixels ?? maybePixels,
- minPixels: minPixels ?? maybeMinPixels,
- maxPixels: maxPixels ?? maybeMaxPixels,
- contentSize: contentSize ?? maybeContentSize,
- viewportSize: viewportSize ?? maybeViewportSize,
- viewportInsets: viewportInsets ?? maybeViewportInsets,
- );
- }
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is SheetMetrics &&
- runtimeType == other.runtimeType &&
- maybePixels == other.pixels &&
- maybeMinPixels == other.minPixels &&
- maybeMaxPixels == other.maxPixels &&
- maybeContentSize == other.contentSize &&
- maybeViewportSize == other.viewportSize &&
- maybeViewportInsets == other.viewportInsets);
-
- @override
- int get hashCode => Object.hash(
- runtimeType,
- maybePixels,
- maybeMinPixels,
- maybeMaxPixels,
- maybeContentSize,
- maybeViewportSize,
- maybeViewportInsets,
- );
-
- @override
- String toString() => (
- hasPixels: hasDimensions,
- pixels: maybePixels,
- minPixels: maybeMinPixels,
- maxPixels: maybeMaxPixels,
- viewPixels: maybeViewPixels,
- minViewPixels: maybeMinViewPixels,
- maxViewPixels: maybeMaxViewPixels,
- contentSize: maybeContentSize,
- viewportSize: maybeViewportSize,
- viewportInsets: maybeViewportInsets,
- ).toString();
-}
-
-/// An interface that provides the necessary context to a [SheetExtent].
-///
-/// Typically, [State]s that host a [SheetExtent] will implement this interface.
-@internal
-abstract class SheetContext {
- TickerProvider get vsync;
- BuildContext? get notificationContext;
-}
-
-@internal
-@optionalTypeArgs
-class SheetExtentScopeKey
- extends LabeledGlobalKey<_SheetExtentScopeState> {
- SheetExtentScopeKey({String? debugLabel}) : super(debugLabel);
-
- final List _onCreatedListeners = [];
-
- T? get maybeCurrentExtent =>
- switch (currentState?._extent) { final T extent => extent, _ => null };
-
- T get currentExtent => maybeCurrentExtent!;
-
- void addOnCreatedListener(VoidCallback listener) {
- _onCreatedListeners.add(listener);
- // Immediately notify the listener if the extent is already created.
- if (maybeCurrentExtent != null) {
- listener();
- }
- }
-
- void removeOnCreatedListener(VoidCallback listener) {
- _onCreatedListeners.remove(listener);
- }
-
- void removeAllOnCreatedListeners() {
- _onCreatedListeners.clear();
- }
-
- void _notifySheetExtentCreation() {
- for (final listener in _onCreatedListeners) {
- listener();
- }
- }
-}
-
-@internal
-@optionalTypeArgs
-abstract class SheetExtentFactory> {
- const SheetExtentFactory();
-
- @factory
- E createSheetExtent({required SheetContext context, required C config});
-}
-
-/// A widget that creates a [SheetExtent], manages its lifecycle,
-/// and exposes it to the descendant widgets.
-@internal
-@optionalTypeArgs
-class SheetExtentScope>
- extends StatefulWidget {
- /// Creates a widget that hosts a [SheetExtent].
- const SheetExtentScope({
- super.key,
- required this.controller,
- required this.config,
- required this.factory,
- this.isPrimary = true,
- required this.child,
- });
-
- /// The [SheetController] that will be attached to the created [SheetExtent].
- final SheetController controller;
-
- final C config;
-
- /// The factory that creates the [SheetExtent].
- final SheetExtentFactory factory;
-
- final bool isPrimary;
-
- /// The widget below this widget in the tree.
- final Widget child;
-
- @override
- State createState() => _SheetExtentScopeState();
-
- /// Retrieves a [SheetExtent] from the closest [SheetExtentScope]
- /// that encloses the given context, if any.
- ///
- /// Use of this method will cause the given context to rebuild any time
- /// that the [config] property of the ancestor [SheetExtentScope] changes.
- // TODO: Add 'useRoot' option.
- static E? maybeOf(BuildContext context) {
- final inherited = context
- .dependOnInheritedWidgetOfExactType<_InheritedSheetExtentScope>()
- ?.extent;
-
- return inherited is E ? inherited : null;
- }
-
- /// Retrieves a [SheetExtent] from the closest [SheetExtentScope]
- /// that encloses the given context.
- ///
- /// Use of this method will cause the given context to rebuild any time
- /// that the [config] property of the ancestor [SheetExtentScope] changes.
- static E of(BuildContext context) {
- final extent = maybeOf(context);
-
- assert(() {
- if (extent == null) {
- throw FlutterError(
- 'No $SheetExtentScope ancestor for $E could be found starting '
- 'from the context that was passed to $SheetExtentScope.of(). '
- 'The context used was:\n'
- '$context',
- );
- }
- return true;
- }());
-
- return extent!;
- }
-}
-
-class _SheetExtentScopeState> extends State>
- with TickerProviderStateMixin
- implements SheetContext {
- late E _extent;
-
- @override
- TickerProvider get vsync => this;
-
- @override
- BuildContext? get notificationContext => mounted ? context : null;
-
- SheetExtentScopeKey? get _scopeKey => switch (widget.key) {
- final SheetExtentScopeKey key => key,
- _ => null,
- };
-
- @override
- void initState() {
- super.initState();
- _extent = _createExtent();
- _scopeKey?._notifySheetExtentCreation();
- }
-
- @override
- void dispose() {
- _discard(_extent);
- super.dispose();
- }
-
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- _invalidateExtentOwnership();
- }
-
- @override
- void didUpdateWidget(SheetExtentScope oldWidget) {
- super.didUpdateWidget(oldWidget);
- final oldExtent = _extent;
- if (widget.factory != oldWidget.factory) {
- _extent = _createExtent()..takeOver(oldExtent);
- _scopeKey?._notifySheetExtentCreation();
- _discard(oldExtent);
- } else if (widget.config != oldWidget.config) {
- _extent.applyNewConfig(widget.config);
- }
- _invalidateExtentOwnership();
- }
-
- E _createExtent() {
- return widget.factory.createSheetExtent(
- context: this,
- config: widget.config,
- );
- }
-
- void _discard(E extent) {
- widget.controller.detach(extent);
- extent.dispose();
- }
-
- void _invalidateExtentOwnership() {
- assert(_debugAssertExtentOwnership());
-
- if (widget.isPrimary) {
- widget.controller.attach(_extent);
- } else {
- widget.controller.detach(_extent);
- }
- }
-
- bool _debugAssertExtentOwnership() {
- assert(
- () {
- final parentScope = context
- .dependOnInheritedWidgetOfExactType<_InheritedSheetExtentScope>();
- if (!widget.isPrimary ||
- parentScope == null ||
- !parentScope.isPrimary) {
- return true;
- }
-
- throw FlutterError(
- 'Nesting SheetExtentScope widgets that are marked as primary '
- 'is not allowed. Typically, this error occurs when you try to nest '
- 'sheet widgets such as DraggableSheet or ScrollableSheet.',
- );
- }(),
- );
-
- return true;
- }
-
- @override
- Widget build(BuildContext context) {
- return _InheritedSheetExtentScope(
- extent: _extent,
- isPrimary: widget.isPrimary,
- child: widget.child,
- );
- }
-}
-
-class _InheritedSheetExtentScope extends InheritedWidget {
- const _InheritedSheetExtentScope({
- required this.extent,
- required this.isPrimary,
- required super.child,
- });
-
- final SheetExtent extent;
- final bool isPrimary;
-
- @override
- bool updateShouldNotify(_InheritedSheetExtentScope oldWidget) =>
- extent != oldWidget.extent || isPrimary != oldWidget.isPrimary;
-}
diff --git a/package/lib/src/foundation/sheet_gesture_tamperer.dart b/package/lib/src/foundation/sheet_gesture_tamperer.dart
deleted file mode 100644
index f46dc743..00000000
--- a/package/lib/src/foundation/sheet_gesture_tamperer.dart
+++ /dev/null
@@ -1,106 +0,0 @@
-import 'package:flutter/widgets.dart';
-import 'package:meta/meta.dart';
-
-import 'sheet_drag.dart';
-
-// TODO: Expose this as a public API.
-@internal
-class TamperSheetGesture extends StatefulWidget {
- const TamperSheetGesture({
- super.key,
- required this.tamperer,
- required this.child,
- });
-
- final SheetGestureTamperer tamperer;
- final Widget child;
-
- @override
- State createState() => _TamperSheetGestureState();
-
- static SheetGestureTamperer? maybeOf(BuildContext context) {
- return context
- .dependOnInheritedWidgetOfExactType<_TamperSheetGestureScope>()
- ?.tamperer;
- }
-}
-
-class _TamperSheetGestureState extends State {
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- widget.tamperer.updateParent(TamperSheetGesture.maybeOf(context));
- }
-
- @override
- void didUpdateWidget(TamperSheetGesture oldWidget) {
- super.didUpdateWidget(oldWidget);
- oldWidget.tamperer.updateParent(null);
- widget.tamperer.updateParent(TamperSheetGesture.maybeOf(context));
- }
-
- @override
- void dispose() {
- widget.tamperer.updateParent(null);
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return _TamperSheetGestureScope(
- tamperer: widget.tamperer,
- child: widget.child,
- );
- }
-}
-
-class _TamperSheetGestureScope extends InheritedWidget {
- const _TamperSheetGestureScope({
- required this.tamperer,
- required super.child,
- });
-
- final SheetGestureTamperer tamperer;
-
- @override
- bool updateShouldNotify(_TamperSheetGestureScope oldWidget) =>
- oldWidget.tamperer != tamperer;
-}
-
-// TODO: Expose this as a public API.
-@internal
-mixin SheetGestureTamperer {
- SheetGestureTamperer? _parent;
-
- @mustCallSuper
- void updateParent(SheetGestureTamperer? parent) {
- _parent = parent;
- }
-
- @useResult
- @mustCallSuper
- SheetDragStartDetails tamperWithDragStart(SheetDragStartDetails details) {
- return _parent?.tamperWithDragStart(details) ?? details;
- }
-
- @useResult
- @mustCallSuper
- SheetDragUpdateDetails tamperWithDragUpdate(
- SheetDragUpdateDetails details,
- Offset minPotentialDeltaConsumption,
- ) {
- return switch (_parent) {
- null => details,
- final parent => parent.tamperWithDragUpdate(
- details,
- minPotentialDeltaConsumption,
- ),
- };
- }
-
- @useResult
- @mustCallSuper
- SheetDragEndDetails tamperWithDragEnd(SheetDragEndDetails details) {
- return _parent?.tamperWithDragEnd(details) ?? details;
- }
-}
diff --git a/package/lib/src/foundation/sheet_physics.dart b/package/lib/src/foundation/sheet_physics.dart
deleted file mode 100644
index 977c8194..00000000
--- a/package/lib/src/foundation/sheet_physics.dart
+++ /dev/null
@@ -1,463 +0,0 @@
-import 'dart:io';
-import 'dart:math';
-import 'dart:ui';
-
-import 'package:flutter/gestures.dart';
-import 'package:flutter/widgets.dart';
-
-import '../internal/double_utils.dart';
-import 'sheet_extent.dart';
-
-/// The default [SpringDescription] used by [SheetPhysics] subclasses.
-///
-/// This spring has the same configuration as the resulting spring
-/// from the [SpringDescription.withDampingRatio] constructor with
-/// a ratio of `1.1`, a mass of `0.5`, and a stiffness of `100.0`.
-const kDefaultSheetSpring = SpringDescription(
- mass: 0.5,
- stiffness: 100.0,
- // Use a pre-calculated value to define the spring as a const variable.
- // See the implementation of withDampingRatio() for the formula.
- damping: 15.5563491861, // 1.1 * 2.0 * sqrt(0.5 * 100.0)
-);
-
-const _kMinSettlingDuration = Duration(milliseconds: 160);
-const _kDefaultSettlingSpeed = 600.0; // logical pixels per second
-
-/// The default [SheetPhysics] used by sheet widgets.
-const kDefaultSheetPhysics =
- StretchingSheetPhysics(parent: SnappingSheetPhysics());
-
-// TODO: Implement `equals` and `hashCode` for SheetPhysics classes.
-abstract class SheetPhysics {
- const SheetPhysics({this.parent});
-
- final SheetPhysics? parent;
-
- /// The minimum amount of pixel distance drags must move by to start motion
- /// the first time or after each time the drag motion stopped.
- ///
- /// If null, no minimum threshold is enforced.
- double? get dragStartDistanceMotionThreshold {
- return Platform.isIOS
- ? const BouncingScrollPhysics().dragStartDistanceMotionThreshold
- : null;
- }
-
- /// Create a copy of this object appending the [ancestor] to
- /// the physics chain, much like [ScrollPhysics.applyTo].
- ///
- /// Can be used to dynamically create an inheritance relationship
- /// between [SheetPhysics] objects. For example, [SheetPhysics] `x`
- /// and `y` in the following code will have the same behavior.
- /// ```dart
- /// final x = FooSheetPhysics().applyTo(BarSheetPhysics());
- /// final y = FooSheetPhysics(parent: BarSheetPhysics());
- /// ```
- SheetPhysics applyTo(SheetPhysics ancestor) {
- return copyWith(parent: parent?.applyTo(ancestor) ?? ancestor);
- }
-
- /// Create a copy of this object with the given fields replaced
- /// by the new values.
- SheetPhysics copyWith({SheetPhysics? parent, SpringDescription? spring});
-
- double computeOverflow(double offset, SheetMetrics metrics);
-
- double applyPhysicsToOffset(double offset, SheetMetrics metrics);
-
- Simulation? createBallisticSimulation(double velocity, SheetMetrics metrics);
-
- Simulation? createSettlingSimulation(SheetMetrics metrics);
-}
-
-mixin SheetPhysicsMixin on SheetPhysics {
- SpringDescription get spring => kDefaultSheetSpring;
-
- @override
- double computeOverflow(double offset, SheetMetrics metrics) {
- if (parent case final parent?) {
- return parent.computeOverflow(offset, metrics);
- }
-
- final newPixels = metrics.pixels + offset;
- if (newPixels > metrics.maxPixels) {
- return min(newPixels - metrics.maxPixels, offset);
- } else if (newPixels < metrics.minPixels) {
- return max(newPixels - metrics.minPixels, offset);
- } else {
- return 0;
- }
- }
-
- @override
- double applyPhysicsToOffset(double offset, SheetMetrics metrics) {
- if (parent case final parent?) {
- return parent.applyPhysicsToOffset(offset, metrics);
- } else if (offset > 0 && metrics.pixels < metrics.maxPixels) {
- // Prevent the pixels from going beyond the maximum value.
- return min(metrics.maxPixels, metrics.pixels + offset) - metrics.pixels;
- } else if (offset < 0 && metrics.pixels > metrics.minPixels) {
- // Prevent the pixels from going beyond the minimum value.
- return max(metrics.minPixels, metrics.pixels + offset) - metrics.pixels;
- } else {
- return 0;
- }
- }
-
- @override
- Simulation? createBallisticSimulation(double velocity, SheetMetrics metrics) {
- if (parent case final parent?) {
- return parent.createBallisticSimulation(velocity, metrics);
- } else if (metrics.isPixelsInBounds) {
- return null;
- }
-
- final destination =
- metrics.pixels.nearest(metrics.minPixels, metrics.maxPixels);
- final direction = (destination - metrics.pixels).sign;
-
- return ScrollSpringSimulation(
- spring,
- metrics.pixels,
- destination,
- // The simulation velocity is intentionally set to 0 if the velocity is
- // is in the opposite direction of the destination, as flinging up an
- // over-dragged sheet or flinging down an under-dragged sheet tends to
- // cause unstable motion.
- velocity.sign == direction ? velocity : 0.0,
- );
- }
-
- @override
- Simulation? createSettlingSimulation(SheetMetrics metrics) {
- if (parent case final parent?) {
- return parent.createSettlingSimulation(metrics);
- } else if (metrics.isPixelsInBounds) {
- return null;
- }
- final settleTo =
- metrics.pixels.nearest(metrics.minPixels, metrics.maxPixels);
-
- return InterpolationSimulation(
- start: metrics.pixels,
- end: settleTo,
- curve: Curves.easeInOut,
- durationInSeconds: max(
- (metrics.pixels - settleTo).abs() / _kDefaultSettlingSpeed,
- _kMinSettlingDuration.inMicroseconds / Duration.microsecondsPerSecond,
- ),
- );
- }
-}
-
-/// A [Simulation] that interpolates between two values over a given duration.
-class InterpolationSimulation extends Simulation {
- /// Creates a [Simulation] that interpolates between two values
- /// over a given duration.
- ///
- /// Make sure that [start] and [end] are not equal, and the
- /// [durationInSeconds] must be greater than 0.
- InterpolationSimulation({
- required this.start,
- required this.end,
- required this.curve,
- required this.durationInSeconds,
- }) : assert(start != end),
- assert(durationInSeconds > 0);
-
- /// The start value of the interpolation.
- final double start;
-
- /// The end value of the interpolation.
- final double end;
-
- /// The curve to use for the interpolation.
- final Curve curve;
-
- /// The duration of the interpolation in seconds.
- late final double durationInSeconds;
-
- @override
- double dx(double time) {
- final epsilon = tolerance.time;
- return (x(time + epsilon) - x(time - epsilon)) / (2 * epsilon);
- }
-
- @override
- double x(double time) {
- final t = curve.transform((time / durationInSeconds).clamp(0, 1));
- return lerpDouble(start, end, t)!;
- }
-
- @override
- bool isDone(double time) {
- return x(time).isApprox(end);
- }
-}
-
-abstract interface class SnappingSheetBehavior {
- double? findSnapPixels(double velocity, SheetMetrics metrics);
-}
-
-mixin _SnapToNearestMixin implements SnappingSheetBehavior {
- /// The lowest speed (in logical pixels per second)
- /// at which a gesture is considered to be a fling.
- double get minFlingSpeed;
-
- @protected
- (double, double) _getSnapBoundsContains(SheetMetrics metrics);
-
- @override
- double? findSnapPixels(double velocity, SheetMetrics metrics) {
- assert(minFlingSpeed >= 0);
-
- if (metrics.pixels.isOutOfBounds(metrics.minPixels, metrics.maxPixels)) {
- return null;
- }
-
- final (nearestSmaller, nearestGreater) = _getSnapBoundsContains(metrics);
- if (velocity.abs() < minFlingSpeed) {
- return metrics.pixels.nearest(nearestSmaller, nearestGreater);
- } else if (velocity < 0) {
- return nearestSmaller;
- } else {
- return nearestGreater;
- }
- }
-}
-
-/// A [SnappingSheetBehavior] that snaps to either [SheetMetrics.minPixels]
-/// or [SheetMetrics.maxPixels] based on the current sheet position and
-/// the gesture velocity.
-///
-/// If the absolute value of the gesture velocity is less than
-/// [minFlingSpeed], the sheet will snap to the nearest of
-/// [SheetMetrics.minPixels] and [SheetMetrics.maxPixels].
-/// Otherwise, the gesture is considered to be a fling, and the sheet will snap
-/// towards the direction of the fling. For example, if the sheet is flung up,
-/// it will snap to [SheetMetrics.maxPixels].
-///
-/// Using this behavior is functionally identical to using [SnapToNearest]
-/// with the snap positions of [SheetExtentConfig.minExtent] and
-/// [SheetExtentConfig.maxExtent], but more simplified and efficient.
-class SnapToNearestEdge with _SnapToNearestMixin {
- /// Creates a [SnappingSheetBehavior] that snaps to either
- /// [SheetMetrics.minPixels] or [SheetMetrics.maxPixels].
- ///
- /// The [minFlingSpeed] defaults to [kMinFlingVelocity],
- /// and must be non-negative.
- const SnapToNearestEdge({
- this.minFlingSpeed = kMinFlingVelocity,
- }) : assert(minFlingSpeed >= 0);
-
- @override
- final double minFlingSpeed;
-
- @override
- (double, double) _getSnapBoundsContains(SheetMetrics metrics) {
- assert(metrics.pixels.isInBounds(metrics.minPixels, metrics.maxPixels));
- return (metrics.minPixels, metrics.maxPixels);
- }
-}
-
-class SnapToNearest with _SnapToNearestMixin {
- SnapToNearest({
- required this.snapTo,
- this.minFlingSpeed = kMinFlingVelocity,
- }) : assert(snapTo.isNotEmpty),
- assert(minFlingSpeed >= 0);
-
- final List snapTo;
-
- @override
- final double minFlingSpeed;
-
- /// Cached results of [Extent.resolve] for each snap position in [snapTo].
- ///
- /// Always call [_ensureCacheIsValid] before accessing this list
- /// to ensure that the cache is up-to-date and sorted in ascending order.
- List _snapTo = const [];
- Size? _cachedContentSize;
-
- void _ensureCacheIsValid(SheetMetrics metrics) {
- if (_cachedContentSize != metrics.contentSize) {
- _cachedContentSize = metrics.contentSize;
- _snapTo = snapTo
- .map((e) => e.resolve(metrics.contentSize))
- .toList(growable: false)
- ..sort();
-
- assert(
- _snapTo.first.isGreaterThanOrApprox(metrics.minPixels) &&
- _snapTo.last.isLessThanOrApprox(metrics.maxPixels),
- 'The snap positions must be within the range of '
- "'SheetMetrics.minPixels' and 'SheetMetrics.maxPixels'.",
- );
- }
- }
-
- @override
- (double, double) _getSnapBoundsContains(SheetMetrics metrics) {
- _ensureCacheIsValid(metrics);
- if (_snapTo.length == 1) {
- return (_snapTo.first, _snapTo.first);
- }
-
- var nearestSmaller = _snapTo[0];
- var nearestGreater = _snapTo[1];
- for (var index = 0; index < _snapTo.length - 1; index++) {
- if (_snapTo[index].isLessThan(metrics.pixels)) {
- nearestSmaller = _snapTo[index];
- nearestGreater = _snapTo[index + 1];
- } else {
- break;
- }
- }
-
- return (nearestSmaller, nearestGreater);
- }
-}
-
-class SnappingSheetPhysics extends SheetPhysics with SheetPhysicsMixin {
- const SnappingSheetPhysics({
- super.parent,
- this.spring = kDefaultSheetSpring,
- this.snappingBehavior = const SnapToNearestEdge(),
- });
-
- final SnappingSheetBehavior snappingBehavior;
-
- @override
- final SpringDescription spring;
-
- @override
- SheetPhysics copyWith({
- SheetPhysics? parent,
- SpringDescription? spring,
- SnappingSheetBehavior? snappingBehavior,
- }) {
- return SnappingSheetPhysics(
- parent: parent ?? this.parent,
- spring: spring ?? this.spring,
- snappingBehavior: snappingBehavior ?? this.snappingBehavior,
- );
- }
-
- @override
- Simulation? createBallisticSimulation(double velocity, SheetMetrics metrics) {
- final snapPixels = snappingBehavior.findSnapPixels(velocity, metrics);
- if (snapPixels != null && !metrics.pixels.isApprox(snapPixels)) {
- return ScrollSpringSimulation(
- spring,
- metrics.pixels,
- snapPixels,
- velocity,
- );
- } else {
- return super.createBallisticSimulation(velocity, metrics);
- }
- }
-}
-
-class ClampingSheetPhysics extends SheetPhysics with SheetPhysicsMixin {
- const ClampingSheetPhysics({
- super.parent,
- this.spring = kDefaultSheetSpring,
- });
-
- @override
- final SpringDescription spring;
-
- @override
- SheetPhysics copyWith({SheetPhysics? parent, SpringDescription? spring}) {
- return ClampingSheetPhysics(
- parent: parent ?? this.parent,
- spring: spring ?? this.spring,
- );
- }
-}
-
-class StretchingSheetPhysics extends SheetPhysics with SheetPhysicsMixin {
- const StretchingSheetPhysics({
- super.parent,
- this.stretchingRange = const Extent.proportional(0.12),
- this.frictionCurve = Curves.easeOutSine,
- this.spring = kDefaultSheetSpring,
- });
-
- final Extent stretchingRange;
- final Curve frictionCurve;
-
- @override
- final SpringDescription spring;
-
- @override
- SheetPhysics copyWith({
- SheetPhysics? parent,
- SpringDescription? spring,
- Extent? stretchingRange,
- Curve? frictionCurve,
- }) {
- return StretchingSheetPhysics(
- parent: parent ?? this.parent,
- spring: spring ?? this.spring,
- stretchingRange: stretchingRange ?? this.stretchingRange,
- frictionCurve: frictionCurve ?? this.frictionCurve,
- );
- }
-
- @override
- double computeOverflow(double offset, SheetMetrics metrics) {
- final stretchingRange = this.stretchingRange.resolve(metrics.contentSize);
-
- if (stretchingRange != 0) {
- return 0;
- }
-
- return super.computeOverflow(offset, metrics);
- }
-
- @override
- double applyPhysicsToOffset(double offset, SheetMetrics metrics) {
- final currentPixels = metrics.pixels;
- final minPixels = metrics.minPixels;
- final maxPixels = metrics.maxPixels;
-
- if (currentPixels.isInBounds(minPixels, maxPixels) ||
- (currentPixels > maxPixels && offset < 0) ||
- (currentPixels < minPixels && offset > 0)) {
- // The friction is not applied if the current 'pixels' is within the range
- // or the motion direction is towards the range.
- return offset;
- }
-
- final stretchingRange = this.stretchingRange.resolve(metrics.contentSize);
-
- if (stretchingRange.isApprox(0)) {
- return 0;
- }
-
- // We divide the delta into smaller fragments
- // and apply friction to each fragment in sequence.
- // This ensures that the friction is not too small
- // if the delta is too large relative to the overflowing pixels,
- // preventing the sheet from slipping too far.
- const fragmentSize = 18.0;
- var newPixels = currentPixels;
- var consumedOffset = 0.0;
- while (consumedOffset.abs() < offset.abs()) {
- final fragment = (offset - consumedOffset).clampAbs(fragmentSize);
- final overflowPastStart = max(minPixels - currentPixels, 0.0);
- final overflowPastEnd = max(currentPixels - maxPixels, 0.0);
- final overflowPast = max(overflowPastStart, overflowPastEnd);
- final overflowFraction = (overflowPast / stretchingRange).clampAbs(1);
- final frictionFactor = frictionCurve.transform(overflowFraction);
-
- newPixels += fragment * (1.0 - frictionFactor);
- consumedOffset += fragment;
- }
-
- return newPixels - currentPixels;
- }
-}
diff --git a/package/lib/src/internal/double_utils.dart b/package/lib/src/internal/double_utils.dart
deleted file mode 100644
index 7763246c..00000000
--- a/package/lib/src/internal/double_utils.dart
+++ /dev/null
@@ -1,31 +0,0 @@
-import 'dart:math';
-
-import 'package:flutter/physics.dart';
-
-extension DoubleUtils on double {
- bool isApprox(double value) =>
- nearEqual(this, value, Tolerance.defaultTolerance.distance);
-
- bool isLessThan(double value) => this < value && !isApprox(value);
-
- bool isGreaterThan(double value) => this > value && !isApprox(value);
-
- bool isLessThanOrApprox(double value) => isLessThan(value) || isApprox(value);
-
- bool isGreaterThanOrApprox(double value) =>
- isGreaterThan(value) || isApprox(value);
-
- bool isOutOfBounds(double min, double max) =>
- isLessThan(min) || isGreaterThan(max);
-
- bool isInBounds(double min, double max) => !isOutOfBounds(min, max);
-
- double clampAbs(double norm) => min(max(-norm, this), norm);
-
- double nearest(double a, double b) =>
- (a - this).abs() < (b - this).abs() ? a : b;
-}
-
-double inverseLerp(double min, double max, double value) {
- return min == max ? 1.0 : (value - min) / (max - min);
-}
diff --git a/package/lib/src/navigation/navigation_route.dart b/package/lib/src/navigation/navigation_route.dart
deleted file mode 100644
index ffceb414..00000000
--- a/package/lib/src/navigation/navigation_route.dart
+++ /dev/null
@@ -1,176 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../foundation/sheet_controller.dart';
-import '../foundation/sheet_extent.dart';
-import '../foundation/sheet_viewport.dart';
-import 'navigation_sheet.dart';
-import 'navigation_sheet_extent.dart';
-import 'navigation_sheet_viewport.dart';
-
-abstract class NavigationSheetRoute extends PageRoute {
- NavigationSheetRoute({super.settings});
-
- late NavigationSheetExtent _globalExtent;
-
- @override
- void install() {
- super.install();
- assert(_debugAssertDependencies());
-
- _globalExtent = SheetExtentScope.of(navigator!.context);
- _globalExtent.createLocalExtentScopeKey(this, debugLabel);
- }
-
- @override
- void changedExternalState() {
- super.changedExternalState();
- // Keep the reference to the global extent up-to-date since we need
- // to call disposeLocalExtentScopeKey() in dispose().
- _globalExtent = SheetExtentScope.of(navigator!.context);
- }
-
- @override
- void dispose() {
- _globalExtent.disposeLocalExtentScopeKey(this);
- super.dispose();
- }
-
- bool _debugAssertDependencies() {
- assert(
- () {
- final globalExtent = SheetExtentScope.maybeOf(navigator!.context);
- if (globalExtent is NavigationSheetExtent) {
- return true;
- }
- throw FlutterError(
- 'A $SheetExtentScope that hosts a $NavigationSheetExtent '
- 'is not found in the given context. This is likely because '
- 'this $NavigationSheetRoute is not a route of the navigator '
- 'enclosed by a $NavigationSheet.',
- );
- }(),
- );
- return true;
- }
-
- @override
- Color? get barrierColor => null;
-
- @override
- String? get barrierLabel => null;
-
- RouteTransitionsBuilder? get transitionsBuilder;
-
- @override
- Widget buildTransitions(
- BuildContext context,
- Animation animation,
- Animation secondaryAnimation,
- Widget child,
- ) {
- final builder = transitionsBuilder ?? _buildDefaultTransitions;
- return builder(context, animation, secondaryAnimation, child);
- }
-
- Widget _buildDefaultTransitions(
- BuildContext context,
- Animation animation,
- Animation secondaryAnimation,
- Widget child,
- ) {
- final theme = Theme.of(context).pageTransitionsTheme;
- final platformAdaptiveTransitions = theme.buildTransitions(
- this, context, animation, secondaryAnimation, child);
-
- final fadeInTween = TweenSequence([
- TweenSequenceItem(tween: ConstantTween(0.0), weight: 1),
- TweenSequenceItem(
- tween: Tween(begin: 0.0, end: 1.0).chain(
- CurveTween(curve: Curves.easeIn),
- ),
- weight: 1,
- ),
- ]);
-
- final fadeOutTween = TweenSequence([
- TweenSequenceItem(
- tween: Tween(begin: 1.0, end: 0.0).chain(
- CurveTween(curve: Curves.easeIn),
- ),
- weight: 1,
- ),
- TweenSequenceItem(tween: ConstantTween(0.0), weight: 1),
- ]);
-
- return FadeTransition(
- opacity: animation.drive(fadeInTween),
- child: FadeTransition(
- opacity: secondaryAnimation.drive(fadeOutTween),
- child: platformAdaptiveTransitions,
- ),
- );
- }
-}
-
-class NavigationSheetRouteContent extends StatelessWidget {
- const NavigationSheetRouteContent({
- super.key,
- required this.factory,
- required this.config,
- required this.child,
- });
-
- final SheetExtentFactory factory;
- final SheetExtentConfig config;
- final Widget child;
-
- @override
- Widget build(BuildContext context) {
- assert(_debugAssertDependencies(context));
- final parentRoute = ModalRoute.of(context)!;
- final globalExtent = SheetExtentScope.of(context);
- final localScopeKey = globalExtent.getLocalExtentScopeKey(parentRoute);
-
- return SheetExtentScope(
- key: localScopeKey,
- isPrimary: false,
- factory: factory,
- config: config,
- controller: SheetControllerScope.of(context),
- child: NavigationSheetRouteViewport(
- child: SheetContentViewport(child: child),
- ),
- );
- }
-
- bool _debugAssertDependencies(BuildContext context) {
- assert(
- () {
- final parentRoute = ModalRoute.of(context);
- if (parentRoute is NavigationSheetRoute) {
- return true;
- }
- throw FlutterError(
- 'The $NavigationSheetRouteContent must be the content of '
- 'a $NavigationSheetRoute, but the result of ModalRoute.of(context) '
- 'is ${parentRoute?.runtimeType}.',
- );
- }(),
- );
- assert(
- () {
- final globalExtent = SheetExtentScope.maybeOf(context);
- if (globalExtent is NavigationSheetExtent) {
- return true;
- }
- throw FlutterError(
- 'A $SheetExtentScope that hosts a $NavigationSheetExtent '
- 'is not found in the given context. This is likely because '
- 'this $NavigationSheetRouteContent is not the content of a '
- '$NavigationSheetRoute.',
- );
- }(),
- );
- return true;
- }
-}
diff --git a/package/lib/src/navigation/navigation_sheet.dart b/package/lib/src/navigation/navigation_sheet.dart
deleted file mode 100644
index 44cdb87e..00000000
--- a/package/lib/src/navigation/navigation_sheet.dart
+++ /dev/null
@@ -1,100 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
-import '../foundation/keyboard_dismissible.dart';
-import '../foundation/sheet_controller.dart';
-import '../foundation/sheet_extent.dart';
-import '../foundation/sheet_gesture_tamperer.dart';
-import '../foundation/sheet_physics.dart';
-import '../foundation/sheet_theme.dart';
-import '../foundation/sheet_viewport.dart';
-import '../internal/transition_observer.dart';
-import 'navigation_sheet_extent.dart';
-import 'navigation_sheet_viewport.dart';
-
-typedef NavigationSheetTransitionObserver = TransitionObserver;
-
-class NavigationSheet extends StatefulWidget with TransitionAwareWidgetMixin {
- const NavigationSheet({
- super.key,
- required this.transitionObserver,
- this.keyboardDismissBehavior,
- this.controller,
- required this.child,
- });
-
- @override
- final NavigationSheetTransitionObserver transitionObserver;
-
- final SheetKeyboardDismissBehavior? keyboardDismissBehavior;
- final SheetController? controller;
- final Widget child;
-
- @override
- State createState() => _NavigationSheetState();
-}
-
-class _NavigationSheetState extends State
- with TransitionAwareStateMixin
- implements SheetExtentFactory {
- final _scopeKey = SheetExtentScopeKey(
- debugLabel: kDebugMode ? 'NavigationSheet' : null,
- );
-
- @override
- void didChangeTransitionState(Transition? transition) {
- _scopeKey.maybeCurrentExtent?.handleRouteTransition(transition);
- }
-
- @factory
- @override
- NavigationSheetExtent createSheetExtent({
- required SheetContext context,
- required SheetExtentConfig config,
- }) {
- return NavigationSheetExtent(
- context: context,
- config: config,
- );
- }
-
- @override
- Widget build(BuildContext context) {
- final theme = SheetTheme.maybeOf(context);
- final keyboardDismissBehavior =
- widget.keyboardDismissBehavior ?? theme?.keyboardDismissBehavior;
- final gestureTamper = TamperSheetGesture.maybeOf(context);
-
- Widget result = ImplicitSheetControllerScope(
- controller: widget.controller,
- builder: (context, controller) {
- return SheetExtentScope(
- key: _scopeKey,
- controller: controller,
- factory: this,
- isPrimary: true,
- config: SheetExtentConfig(
- minExtent: const Extent.pixels(0),
- maxExtent: const Extent.proportional(1),
- // TODO: Use more appropriate physics.
- physics: const ClampingSheetPhysics(),
- gestureTamperer: gestureTamper,
- debugLabel: kDebugMode ? 'NavigationSheet' : null,
- ),
- child: NavigationSheetViewport(
- child: SheetContentViewport(child: widget.child),
- ),
- );
- },
- );
-
- if (keyboardDismissBehavior != null) {
- result = SheetKeyboardDismissible(
- dismissBehavior: keyboardDismissBehavior,
- child: result,
- );
- }
-
- return result;
- }
-}
diff --git a/package/lib/src/navigation/navigation_sheet_extent.dart b/package/lib/src/navigation/navigation_sheet_extent.dart
deleted file mode 100644
index 90a916b5..00000000
--- a/package/lib/src/navigation/navigation_sheet_extent.dart
+++ /dev/null
@@ -1,171 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:meta/meta.dart';
-
-import '../foundation/sheet_extent.dart';
-import '../internal/transition_observer.dart';
-import 'navigation_sheet_activity.dart';
-
-@internal
-class NavigationSheetExtent extends SheetExtent {
- NavigationSheetExtent({
- required super.context,
- required super.config,
- });
-
- final _localExtentScopeKeyRegistry = , SheetExtentScopeKey>{};
- Transition? _lastReportedTransition;
-
- SheetExtentScopeKey createLocalExtentScopeKey(
- Route route,
- String? debugLabel,
- ) {
- assert(!_localExtentScopeKeyRegistry.containsKey(route));
- final key = SheetExtentScopeKey(debugLabel: debugLabel);
- _localExtentScopeKeyRegistry[route] = key;
- return key;
- }
-
- void disposeLocalExtentScopeKey(Route route) {
- assert(_localExtentScopeKeyRegistry.containsKey(route));
- final key = _localExtentScopeKeyRegistry.remove(route);
- key!.removeAllOnCreatedListeners();
- }
-
- SheetExtentScopeKey getLocalExtentScopeKey(Route route) {
- assert(_localExtentScopeKeyRegistry.containsKey(route));
- return _localExtentScopeKeyRegistry[route]!;
- }
-
- bool containsLocalExtentScopeKey(Route route) {
- return _localExtentScopeKeyRegistry.containsKey(route);
- }
-
- @override
- void takeOver(SheetExtent other) {
- super.takeOver(other);
- if (other is NavigationSheetExtent) {
- assert(_localExtentScopeKeyRegistry.isEmpty);
- _lastReportedTransition = other._lastReportedTransition;
- _localExtentScopeKeyRegistry.addAll(other._localExtentScopeKeyRegistry);
- // Prevent the scope keys in `other._localExtentScopeKeyRegistry` from
- // being discarded when `other` is disposed.
- other._localExtentScopeKeyRegistry.clear();
- assert(_debugAssertActivityTypeConsistency());
- }
- }
-
- @override
- void dispose() {
- super.dispose();
- for (final scopeKey in _localExtentScopeKeyRegistry.values) {
- scopeKey.removeAllOnCreatedListeners();
- }
- _localExtentScopeKeyRegistry.clear();
- }
-
- void handleRouteTransition(Transition? transition) {
- _lastReportedTransition = transition;
- // TODO: Provide a way to customize animation curves.
- switch (transition) {
- case NoTransition(:final currentRoute):
- beginActivity(ProxySheetActivity(route: currentRoute));
-
- case final ForwardTransition tr:
- beginActivity(TransitionSheetActivity(
- currentRoute: tr.originRoute,
- nextRoute: tr.destinationRoute,
- animation: tr.animation,
- animationCurve: Curves.easeInOutCubic,
- ));
-
- case final BackwardTransition tr:
- beginActivity(TransitionSheetActivity(
- currentRoute: tr.originRoute,
- nextRoute: tr.destinationRoute,
- animation: tr.animation,
- animationCurve: Curves.easeInOutCubic,
- ));
-
- case final UserGestureTransition tr:
- beginActivity(TransitionSheetActivity(
- currentRoute: tr.currentRoute,
- nextRoute: tr.previousRoute,
- animation: tr.animation,
- animationCurve: Curves.linear,
- ));
-
- case _:
- goIdle();
- }
-
- assert(_debugAssertActivityTypeConsistency());
- }
-
- @override
- void goIdle() {
- switch (_lastReportedTransition) {
- case NoTransition(:final currentRoute):
- beginActivity(ProxySheetActivity(route: currentRoute));
- case _:
- super.goIdle();
- }
- }
-
- @override
- void dispatchUpdateNotification() {
- // Do not dispatch a notifications if a local extent is active.
- if (activity is! NavigationSheetActivity) {
- super.dispatchUpdateNotification();
- }
- }
-
- @override
- void dispatchDragStartNotification() {
- // Do not dispatch a notifications if a local extent is active.
- if (activity is! NavigationSheetActivity) {
- super.dispatchDragStartNotification();
- }
- }
-
- @override
- void dispatchDragEndNotification() {
- // Do not dispatch a notifications if a local extent is active.
- if (activity is! NavigationSheetActivity) {
- super.dispatchDragEndNotification();
- }
- }
-
- @override
- void dispatchDragUpdateNotification() {
- // Do not dispatch a notifications if a local extent is active.
- if (activity is! NavigationSheetActivity) {
- super.dispatchDragUpdateNotification();
- }
- }
-
- @override
- void dispatchOverflowNotification(double overflow) {
- // Do not dispatch a notifications if a local extent is active.
- if (activity is! NavigationSheetActivity) {
- super.dispatchOverflowNotification(overflow);
- }
- }
-
- bool _debugAssertActivityTypeConsistency() {
- assert(
- () {
- switch ((_lastReportedTransition, activity)) {
- case (NoTransition(), ProxySheetActivity()):
- case (ForwardTransition(), TransitionSheetActivity()):
- case (BackwardTransition(), TransitionSheetActivity()):
- case (UserGestureTransition(), TransitionSheetActivity()):
- case (null, _):
- return true;
- case _:
- return false;
- }
- }(),
- );
- return true;
- }
-}
diff --git a/package/lib/src/scrollable/scrollable_sheet.dart b/package/lib/src/scrollable/scrollable_sheet.dart
deleted file mode 100644
index eb6c3413..00000000
--- a/package/lib/src/scrollable/scrollable_sheet.dart
+++ /dev/null
@@ -1,117 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-import 'package:meta/meta.dart';
-
-import '../foundation/keyboard_dismissible.dart';
-import '../foundation/sheet_controller.dart';
-import '../foundation/sheet_extent.dart';
-import '../foundation/sheet_gesture_tamperer.dart';
-import '../foundation/sheet_physics.dart';
-import '../foundation/sheet_theme.dart';
-import '../foundation/sheet_viewport.dart';
-import 'scrollable_sheet_extent.dart';
-import 'sheet_scrollable.dart';
-
-class ScrollableSheet extends StatelessWidget {
- const ScrollableSheet({
- super.key,
- this.keyboardDismissBehavior,
- this.initialExtent = const Extent.proportional(1),
- this.minExtent = const Extent.proportional(1),
- this.maxExtent = const Extent.proportional(1),
- this.physics,
- this.controller,
- required this.child,
- });
-
- /// The strategy to dismiss the on-screen keyboard when the sheet is dragged.
- final SheetKeyboardDismissBehavior? keyboardDismissBehavior;
-
- /// {@macro ScrollableSheetExtent.initialExtent}
- final Extent initialExtent;
-
- /// {@macro SheetExtentConfig.minExtent}
- final Extent minExtent;
-
- /// {@macro SheetExtentConfig.maxExtent}
- final Extent maxExtent;
-
- /// {@macro SheetExtentConfig.physics}
- final SheetPhysics? physics;
-
- /// An object that can be used to control and observe the sheet height.
- final SheetController? controller;
-
- /// The content of the sheet.
- final Widget child;
-
- @override
- Widget build(BuildContext context) {
- final theme = SheetTheme.maybeOf(context);
- final keyboardDismissBehavior =
- this.keyboardDismissBehavior ?? theme?.keyboardDismissBehavior;
- final gestureTamper = TamperSheetGesture.maybeOf(context);
-
- Widget result = ImplicitSheetControllerScope(
- controller: controller,
- builder: (context, controller) {
- return SheetExtentScope(
- controller: controller,
- factory: const ScrollableSheetExtentFactory(),
- config: ScrollableSheetExtentConfig.withFallbacks(
- initialExtent: initialExtent,
- minExtent: minExtent,
- maxExtent: maxExtent,
- physics: physics ?? theme?.physics,
- gestureTamperer: gestureTamper,
- debugLabel: 'ScrollableSheet',
- ),
- child: SheetViewport(
- child: SheetContentViewport(
- child: ScrollableSheetContent(child: child),
- )),
- );
- },
- );
-
- if (keyboardDismissBehavior != null) {
- result = SheetKeyboardDismissible(
- dismissBehavior: keyboardDismissBehavior,
- child: result,
- );
- }
-
- return result;
- }
-}
-
-@internal
-class ScrollableSheetContent extends StatelessWidget {
- const ScrollableSheetContent({
- super.key,
- this.debugLabel,
- this.keepScrollOffset = true,
- this.initialScrollOffset = 0,
- required this.child,
- });
-
- final String? debugLabel;
- final bool keepScrollOffset;
- final double initialScrollOffset;
- final Widget child;
-
- @override
- Widget build(BuildContext context) {
- return SheetScrollable(
- debugLabel: debugLabel,
- keepScrollOffset: keepScrollOffset,
- initialScrollOffset: initialScrollOffset,
- builder: (context, controller) {
- return PrimaryScrollController(
- controller: controller,
- child: child,
- );
- },
- );
- }
-}
diff --git a/package/pubspec.lock b/package/pubspec.lock
new file mode 100644
index 00000000..b81de8ea
--- /dev/null
+++ b/package/pubspec.lock
@@ -0,0 +1,213 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: "direct main"
+ description:
+ name: collection
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.18.0"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: transitive
+ description:
+ name: flutter_lints
+ sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.0"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.16+1"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.0"
+ meta:
+ dependency: "direct main"
+ description:
+ name: meta
+ sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.0"
+ pedantic_mono:
+ dependency: "direct dev"
+ description:
+ name: pedantic_mono
+ sha256: "3c42858286d72fba37af67f635b8bf4f0a51b1909f97a52d822d8d5cb3a7dbc9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.26.0"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.1"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
+ url: "https://pub.dev"
+ source: hosted
+ version: "13.0.0"
+sdks:
+ dart: ">=3.3.0 <4.0.0"
+ flutter: ">=3.16.0"
diff --git a/package/test/foundation/physics_test.dart b/package/test/foundation/physics_test.dart
deleted file mode 100644
index 3204bc61..00000000
--- a/package/test/foundation/physics_test.dart
+++ /dev/null
@@ -1,356 +0,0 @@
-// ignore_for_file: lines_longer_than_80_chars
-import 'package:flutter/widgets.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:smooth_sheets/smooth_sheets.dart';
-
-class _SheetPhysicsWithDefaultConfiguration extends SheetPhysics
- with SheetPhysicsMixin {
- const _SheetPhysicsWithDefaultConfiguration();
-
- @override
- SheetPhysics copyWith({SheetPhysics? parent, SpringDescription? spring}) {
- return const _SheetPhysicsWithDefaultConfiguration();
- }
-}
-
-const _referenceSheetMetrics = SheetMetrics(
- minPixels: 0,
- maxPixels: 600,
- pixels: 600,
- contentSize: Size(360, 600),
- viewportSize: Size(360, 700),
- viewportInsets: EdgeInsets.zero,
-);
-
-final _positionAtTopEdge =
- _referenceSheetMetrics.copyWith(pixels: _referenceSheetMetrics.maxPixels);
-
-final _positionAtBottomEdge =
- _referenceSheetMetrics.copyWith(pixels: _referenceSheetMetrics.minPixels);
-
-final _positionAtMiddle = _referenceSheetMetrics.copyWith(
- pixels: (_positionAtTopEdge.pixels + _positionAtBottomEdge.pixels) / 2,
-);
-
-void main() {
- group('$SheetPhysics subclasses', () {
- test('can create dynamic inheritance relationships', () {
- const clamp = ClampingSheetPhysics();
- const stretch = StretchingSheetPhysics();
- const snap = SnappingSheetPhysics();
-
- List getChain(SheetPhysics physics) {
- return switch (physics.parent) {
- null => [physics.runtimeType],
- final parent => [physics.runtimeType, ...getChain(parent)],
- };
- }
-
- expect(
- getChain(clamp.applyTo(stretch).applyTo(snap)).join(' -> '),
- 'ClampingSheetPhysics -> StretchingSheetPhysics -> SnappingSheetPhysics',
- );
-
- expect(
- getChain(snap.applyTo(stretch).applyTo(clamp)).join(' -> '),
- 'SnappingSheetPhysics -> StretchingSheetPhysics -> ClampingSheetPhysics',
- );
-
- expect(
- getChain(stretch.applyTo(clamp).applyTo(snap)).join(' -> '),
- 'StretchingSheetPhysics -> ClampingSheetPhysics -> SnappingSheetPhysics',
- );
- });
- });
-
- group('Default configuration of $SheetPhysics', () {
- late SheetPhysics physicsUnderTest;
-
- setUp(() {
- physicsUnderTest = const _SheetPhysicsWithDefaultConfiguration();
- });
-
- test('does not allow over/under dragging', () {
- expect(
- physicsUnderTest.computeOverflow(10, _positionAtTopEdge),
- moreOrLessEquals(10),
- );
- expect(
- physicsUnderTest.computeOverflow(-10, _positionAtBottomEdge),
- moreOrLessEquals(-10),
- );
- });
-
- test('does not apply any resistance if position is in bounds', () {
- final positionAtNearTopEdge = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels - 10);
- final positionAtNearBottomEdge = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.minPixels + 10);
-
- expect(
- physicsUnderTest.applyPhysicsToOffset(10, _positionAtMiddle),
- moreOrLessEquals(10),
- );
- expect(
- physicsUnderTest.applyPhysicsToOffset(10, positionAtNearTopEdge),
- moreOrLessEquals(10),
- );
- expect(
- physicsUnderTest.applyPhysicsToOffset(-10, positionAtNearBottomEdge),
- moreOrLessEquals(-10),
- );
- });
-
- test('prevents position from going out of bounds', () {
- expect(
- physicsUnderTest.applyPhysicsToOffset(10, _positionAtTopEdge),
- moreOrLessEquals(0),
- );
- expect(
- physicsUnderTest.applyPhysicsToOffset(-10, _positionAtBottomEdge),
- moreOrLessEquals(0),
- );
- });
-
- test('creates no ballistic simulation if position is in bounds', () {
- expect(
- physicsUnderTest.createBallisticSimulation(0, _positionAtMiddle),
- isNull,
- );
- expect(
- physicsUnderTest.createBallisticSimulation(0, _positionAtTopEdge),
- isNull,
- );
- expect(
- physicsUnderTest.createBallisticSimulation(0, _positionAtBottomEdge),
- isNull,
- );
- });
-
- test('creates ballistic simulation which ends at the nearest edge', () {
- final overDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels + 10,
- );
- final underDragPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.minPixels - 10,
- );
- final overDragSimulation =
- physicsUnderTest.createBallisticSimulation(0, overDraggedPosition);
- final underDraggedSimulation =
- physicsUnderTest.createBallisticSimulation(0, underDragPosition);
-
- expect(overDragSimulation, isNotNull);
- expect(
- overDragSimulation!.x(5), // 5s passed
- moreOrLessEquals(_referenceSheetMetrics.maxPixels),
- );
- expect(
- overDragSimulation.dx(5), // 5s passed
- moreOrLessEquals(0),
- );
-
- expect(underDraggedSimulation, isNotNull);
- expect(
- underDraggedSimulation!.x(5), // 5s passed
- moreOrLessEquals(_referenceSheetMetrics.minPixels),
- );
- expect(
- underDraggedSimulation.dx(5), // 5s passed
- moreOrLessEquals(0),
- );
- });
-
- test('creates no settling simulation if the position is in bounds', () {
- expect(
- physicsUnderTest.createSettlingSimulation(_positionAtMiddle),
- isNull,
- );
- expect(
- physicsUnderTest.createSettlingSimulation(_positionAtTopEdge),
- isNull,
- );
- expect(
- physicsUnderTest.createSettlingSimulation(_positionAtBottomEdge),
- isNull,
- );
- });
-
- test('creates settling simulation which ends at nearest edge', () {
- final moreOverDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels + 200,
- );
- final lessOverDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels + 10,
- );
- final moreOverDragSimulation =
- physicsUnderTest.createSettlingSimulation(moreOverDraggedPosition);
- final lessOverDragSimulation =
- physicsUnderTest.createSettlingSimulation(lessOverDraggedPosition);
-
- // The settling simulation runs with the average velocity of 600px/s
- // if the starting position is far enough from the edge.
- expect(moreOverDragSimulation, isNotNull);
- expect(
- moreOverDragSimulation!.x(0.170), // 170ms passed
- greaterThan(_referenceSheetMetrics.maxPixels),
- );
- expect(
- moreOverDragSimulation.x(0.334), // 334ms passed (≈ 200px / 600px/s)
- moreOrLessEquals(_referenceSheetMetrics.maxPixels),
- );
-
- // The default behavior ensures that the settling simulation runs for
- // at least 160ms even if the starting position is too close to the edge.
- expect(lessOverDragSimulation, isNotNull);
- expect(
- lessOverDragSimulation!.x(0.08), // 80ms passed
- greaterThan(_referenceSheetMetrics.maxPixels),
- );
- expect(
- lessOverDragSimulation.x(0.16), // 160ms passed
- moreOrLessEquals(_referenceSheetMetrics.maxPixels),
- );
- });
- });
-
- group('$SnapToNearestEdge', () {
- late SnapToNearestEdge behaviorUnderTest;
-
- setUp(() {
- behaviorUnderTest = const SnapToNearestEdge(minFlingSpeed: 50);
- });
-
- test('snaps to nearest edge if velocity is small enough', () {
- final positionAtNearTopEdge = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels - 50,
- );
- final positionAtNearBottomEdge = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.minPixels + 50,
- );
-
- expect(
- behaviorUnderTest.findSnapPixels(0, positionAtNearTopEdge),
- moreOrLessEquals(_referenceSheetMetrics.maxPixels),
- );
- expect(
- behaviorUnderTest.findSnapPixels(0, positionAtNearBottomEdge),
- moreOrLessEquals(_referenceSheetMetrics.minPixels),
- );
- });
-
- test('is aware of fling gesture direction', () {
- expect(
- behaviorUnderTest.findSnapPixels(50, _positionAtBottomEdge),
- moreOrLessEquals(_referenceSheetMetrics.maxPixels),
- );
- expect(
- behaviorUnderTest.findSnapPixels(-50, _positionAtTopEdge),
- moreOrLessEquals(_referenceSheetMetrics.minPixels),
- );
- });
-
- test('is disabled if position is out of bounds', () {
- final overDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels + 10,
- );
- final underDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.minPixels - 10,
- );
-
- expect(
- behaviorUnderTest.findSnapPixels(0, overDraggedPosition),
- isNull,
- );
- expect(
- behaviorUnderTest.findSnapPixels(0, underDraggedPosition),
- isNull,
- );
- });
- });
- group('$SnapToNearest', () {
- late SnapToNearest behaviorUnderTest;
-
- setUp(() {
- behaviorUnderTest = SnapToNearest(
- minFlingSpeed: 50,
- snapTo: [
- Extent.pixels(_positionAtBottomEdge.pixels),
- Extent.pixels(_positionAtMiddle.pixels),
- Extent.pixels(_positionAtTopEdge.pixels),
- ],
- );
- });
-
- test('snaps to nearest edge if velocity is small enough', () {
- final positionAtNearTopEdge = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels - 50,
- );
- final positionAtNearMiddle = _referenceSheetMetrics.copyWith(
- pixels: _positionAtMiddle.pixels + 50,
- );
- final positionAtNearBottomEdge = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.minPixels + 50,
- );
-
- expect(
- behaviorUnderTest.findSnapPixels(0, positionAtNearTopEdge),
- moreOrLessEquals(_referenceSheetMetrics.maxPixels),
- );
- expect(
- behaviorUnderTest.findSnapPixels(0, positionAtNearMiddle),
- moreOrLessEquals(_positionAtMiddle.pixels),
- );
- expect(
- behaviorUnderTest.findSnapPixels(0, positionAtNearBottomEdge),
- moreOrLessEquals(_referenceSheetMetrics.minPixels),
- );
- });
-
- test('is aware of fling gesture direction', () {
- final positionAtAboveMiddle = _positionAtMiddle.copyWith(
- pixels: _positionAtMiddle.pixels + 10,
- );
- final positionAtBelowMiddle = _positionAtMiddle.copyWith(
- pixels: _positionAtMiddle.pixels - 10,
- );
- // Flings up at the bottom edge
- expect(
- behaviorUnderTest.findSnapPixels(50, _positionAtBottomEdge),
- moreOrLessEquals(_positionAtMiddle.pixels),
- );
- // Flings up at the slightly above the middle position
- expect(
- behaviorUnderTest.findSnapPixels(50, positionAtAboveMiddle),
- moreOrLessEquals(_positionAtTopEdge.pixels),
- );
- // Flings down at the top edge
- expect(
- behaviorUnderTest.findSnapPixels(-50, _positionAtTopEdge),
- moreOrLessEquals(_positionAtMiddle.pixels),
- );
- // Flings down at the slightly below the middle position
- expect(
- behaviorUnderTest.findSnapPixels(-50, positionAtBelowMiddle),
- moreOrLessEquals(_positionAtBottomEdge.pixels),
- );
- });
-
- test('is disabled if position is out of bounds', () {
- final overDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.maxPixels + 10,
- );
- final underDraggedPosition = _referenceSheetMetrics.copyWith(
- pixels: _referenceSheetMetrics.minPixels - 10,
- );
-
- expect(
- behaviorUnderTest.findSnapPixels(0, overDraggedPosition),
- isNull,
- );
- expect(
- behaviorUnderTest.findSnapPixels(0, underDraggedPosition),
- isNull,
- );
- });
- });
-}
diff --git a/package/pubspec.yaml b/pubspec.yaml
similarity index 75%
rename from package/pubspec.yaml
rename to pubspec.yaml
index 3fc49f5c..85f8e1dc 100644
--- a/package/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,10 +1,14 @@
name: smooth_sheets
description: Sheet widgets with smooth motion and great flexibility. Also supports nested navigation in both imperative and declarative ways.
-version: 0.7.1
+version: 0.10.0
repository: https://github.com/fujidaiti/smooth_sheets
screenshots:
- description: Practical examples of smooth_sheets.
- path: resources/smooth-sheets-thumbnail.png
+ path: art/smooth-sheets-thumbnail.png
+topics:
+ - ui
+ - widget
+ - bottom-sheet
environment:
sdk: ">=3.2.0 <4.0.0"
@@ -17,6 +21,9 @@ dependencies:
meta: ^1.9.1
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/resources/cookbook-ai-playlist-generator.mp4 b/resources/cookbook-ai-playlist-generator.mp4
deleted file mode 100644
index c1dcf3b1..00000000
Binary files a/resources/cookbook-ai-playlist-generator.mp4 and /dev/null differ
diff --git a/resources/cookbook-airbnb.mp4 b/resources/cookbook-airbnb.mp4
deleted file mode 100644
index 5f24e44a..00000000
Binary files a/resources/cookbook-airbnb.mp4 and /dev/null differ
diff --git a/resources/cookbook-declarative-modal-sheet.mp4 b/resources/cookbook-declarative-modal-sheet.mp4
deleted file mode 100644
index 6c4e93d2..00000000
Binary files a/resources/cookbook-declarative-modal-sheet.mp4 and /dev/null differ
diff --git a/resources/cookbook-declarative-navigation-sheet.mp4 b/resources/cookbook-declarative-navigation-sheet.mp4
deleted file mode 100644
index e85cff6a..00000000
Binary files a/resources/cookbook-declarative-navigation-sheet.mp4 and /dev/null differ
diff --git a/resources/cookbook-draggable-sheet.mp4 b/resources/cookbook-draggable-sheet.mp4
deleted file mode 100644
index 0b5594ae..00000000
Binary files a/resources/cookbook-draggable-sheet.mp4 and /dev/null differ
diff --git a/resources/cookbook-imperative-modal-sheet.mp4 b/resources/cookbook-imperative-modal-sheet.mp4
deleted file mode 100644
index 74577d96..00000000
Binary files a/resources/cookbook-imperative-modal-sheet.mp4 and /dev/null differ
diff --git a/resources/cookbook-imperative-navigation-sheet.mp4 b/resources/cookbook-imperative-navigation-sheet.mp4
deleted file mode 100644
index 474cd81b..00000000
Binary files a/resources/cookbook-imperative-navigation-sheet.mp4 and /dev/null differ
diff --git a/resources/cookbook-scrollable-sheet.mp4 b/resources/cookbook-scrollable-sheet.mp4
deleted file mode 100644
index 5199d008..00000000
Binary files a/resources/cookbook-scrollable-sheet.mp4 and /dev/null differ
diff --git a/resources/cookbook-sheet-content-scaffold.mp4 b/resources/cookbook-sheet-content-scaffold.mp4
deleted file mode 100644
index c0d2983e..00000000
Binary files a/resources/cookbook-sheet-content-scaffold.mp4 and /dev/null differ
diff --git a/resources/cookbook-sheet-controller.mp4 b/resources/cookbook-sheet-controller.mp4
deleted file mode 100644
index 6219eda0..00000000
Binary files a/resources/cookbook-sheet-controller.mp4 and /dev/null differ
diff --git a/resources/cookbook-sheet-draggable.mp4 b/resources/cookbook-sheet-draggable.mp4
deleted file mode 100644
index ad396215..00000000
Binary files a/resources/cookbook-sheet-draggable.mp4 and /dev/null differ
diff --git a/resources/cookbook-sheet-physics.mp4 b/resources/cookbook-sheet-physics.mp4
deleted file mode 100644
index 3d05a822..00000000
Binary files a/resources/cookbook-sheet-physics.mp4 and /dev/null differ
diff --git a/scripts/copilot/pattern_checker_session.json b/scripts/copilot/pattern_checker_session.json
deleted file mode 100644
index 7a992032..00000000
--- a/scripts/copilot/pattern_checker_session.json
+++ /dev/null
@@ -1,496 +0,0 @@
-{
- "requesterUsername": "fujidaiti",
- "requesterAvatarIconUri": {
- "$mid": 1,
- "path": "/fujidaiti",
- "scheme": "https",
- "authority": "avatars.githubusercontent.com"
- },
- "responderUsername": "GitHub Copilot",
- "welcomeMessage": [
- "Hi **@fujidaiti**, how can I help you?"
- ],
- "requests": [
- {
- "message": {
- "parts": [
- {
- "range": {
- "start": 0,
- "endExclusive": 1019
- },
- "editorRange": {
- "startLineNumber": 1,
- "startColumn": 1,
- "endLineNumber": 13,
- "endColumn": 120
- },
- "text": "Could you write a shell script which fails if specified words or patterns are found in local files? The inputs are:\n\nA list of patterns of local files (e.g. *.dart), which will be targets of this script.\nA list of disallowed words or patterns (regex), for which this script will fail if at least one of them is found in the target files.\nThis script must exit with 1 (error status) if any disallowed words or patterns are found in the target files, othwerwise, it must exist with 0. Make sure the all of the target files must be processed even if an problem is found within a file in the middle of the entier process.\n\nIn addition, this script must echo human friendly logs for each problem found in the process. Each log must contains the following information:\n\nFile name\nThe line number in which a disallowed word or a pattenrn is found\nA snippet of the single line in which the problem is found\n\nNote that this script must not modify the original files. It just scans them and prints diagnosis found in the process.",
- "kind": "text"
- }
- ],
- "text": "Could you write a shell script which fails if specified words or patterns are found in local files? The inputs are:\n\nA list of patterns of local files (e.g. *.dart), which will be targets of this script.\nA list of disallowed words or patterns (regex), for which this script will fail if at least one of them is found in the target files.\nThis script must exit with 1 (error status) if any disallowed words or patterns are found in the target files, othwerwise, it must exist with 0. Make sure the all of the target files must be processed even if an problem is found within a file in the middle of the entier process.\n\nIn addition, this script must echo human friendly logs for each problem found in the process. Each log must contains the following information:\n\nFile name\nThe line number in which a disallowed word or a pattenrn is found\nA snippet of the single line in which the problem is found\n\nNote that this script must not modify the original files. It just scans them and prints diagnosis found in the process."
- },
- "response": [
- {
- "value": "Here is a shell script that meets your requirements. This script uses `grep` to search for the disallowed patterns in the specified files. If a match is found, it prints the filename, line number, and the matching line. If any matches are found, the script exits with a status of 1.\n\n```bash\n#!/bin/bash\n\n# List of file patterns\nfile_patterns=(\"*.dart\")\n\n# List of disallowed words or patterns\ndisallowed_patterns=(\"pattern1\" \"pattern2\")\n\n# Flag to track if any disallowed pattern is found\nerror_found=0\n\n# Loop over each file pattern\nfor file_pattern in \"${file_patterns[@]}\"; do\n # Find files matching the pattern\n files=$(find . -name \"$file_pattern\")\n\n # Loop over each file\n for file in $files; do\n # Loop over each disallowed pattern\n for pattern in \"${disallowed_patterns[@]}\"; do\n # Search for the pattern in the file\n result=$(grep -n \"$pattern\" \"$file\")\n\n # If a match is found\n if [[ $result ]]; then\n # Set the error flag\n error_found=1\n\n # Print the filename, line number, and matching line\n echo \"Disallowed pattern found in file: $file\"\n echo \"$result\"\n fi\n done\n done\ndone\n\n# If any disallowed pattern was found, exit with status 1\nif [[ $error_found -eq 1 ]]; then\n exit 1\nfi\n\n# If no disallowed pattern was found, exit with status 0\nexit 0\n```\n\nYou can customize the `file_patterns` and `disallowed_patterns` arrays as needed. This script will recursively search for files in the current directory and its subdirectories. If you want to search in a different directory, you can change the `.` in the `find` command to the path of the directory you want to search in.",
- "isTrusted": false,
- "supportThemeIcons": false,
- "supportHtml": false
- }
- ],
- "followups": [
- {
- "kind": "reply",
- "message": "How can I exclude certain directories from the file search in the shell script?",
- "title": "How can I exclude certain directories from the file search in the shell script?"
- }
- ],
- "isCanceled": false,
- "agent": {
- "id": "",
- "metadata": {
- "description": "",
- "fullName": "GitHub Copilot",
- "hasSlashCommands": true,
- "hasFollowups": true,
- "isDefault": true,
- "helpTextPrefix": "You can ask me general programming questions, or chat with the following agents which have specialized expertise and can perform actions:",
- "helpTextPostfix": {
- "value": "To have a great conversation, ask me questions as if I was a real programmer:\n\n* **Show me the code** you want to talk about by having the files open and selecting the most important lines.\n* **Make refinements** by asking me follow-up questions, adding clarifications, providing errors, etc.\n* **Review my suggested code** and tell me about issues or improvements, so I can iterate on it.\n\nYou can also ask me questions about your editor selection by [starting an inline chat session](command:inlineChat.start).\n\nLearn more about [GitHub Copilot](https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode) in [Visual Studio Code](https://code.visualstudio.com/docs/editor/artificial-intelligence).",
- "isTrusted": {
- "enabledCommands": [
- "inlineChat.start"
- ]
- },
- "supportThemeIcons": false,
- "supportHtml": false,
- "uris": {
- "command:inlineChat.start": {
- "$mid": 1,
- "path": "inlineChat.start",
- "scheme": "command"
- },
- "https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode": {
- "$mid": 1,
- "path": "/copilot/getting-started-with-github-copilot",
- "scheme": "https",
- "authority": "docs.github.com",
- "query": "tool=vscode"
- },
- "https://code.visualstudio.com/docs/editor/artificial-intelligence": {
- "$mid": 1,
- "path": "/docs/editor/artificial-intelligence",
- "scheme": "https",
- "authority": "code.visualstudio.com"
- }
- }
- },
- "supportIssueReporting": false
- }
- },
- "contentReferences": []
- },
- {
- "message": {
- "parts": [
- {
- "range": {
- "start": 0,
- "endExclusive": 135
- },
- "editorRange": {
- "startLineNumber": 1,
- "startColumn": 1,
- "endLineNumber": 1,
- "endColumn": 136
- },
- "text": "Nice! Could you modify the script so that we can get the target file patterns and the disallowed patterns from the script's argumenets?",
- "kind": "text"
- }
- ],
- "text": "Nice! Could you modify the script so that we can get the target file patterns and the disallowed patterns from the script's argumenets?"
- },
- "response": [
- {
- "value": "Sure, here's how you can modify the script to accept the target file patterns and disallowed patterns as arguments. The first argument will be the target file pattern, and all subsequent arguments will be treated as disallowed patterns.\n\n```bash\n#!/bin/bash\n\n# File pattern from the first argument\nfile_pattern=$1\n\n# Disallowed patterns from the rest of the arguments\ndisallowed_patterns=${@:2}\n\n# Flag to track if any disallowed pattern is found\nerror_found=0\n\n# Find files matching the pattern\nfiles=$(find . -name \"$file_pattern\")\n\n# Loop over each file\nfor file in $files; do\n # Loop over each disallowed pattern\n for pattern in $disallowed_patterns; do\n # Search for the pattern in the file\n result=$(grep -n \"$pattern\" \"$file\")\n\n # If a match is found\n if [[ $result ]]; then\n # Set the error flag\n error_found=1\n\n # Print the filename, line number, and matching line\n echo \"Disallowed pattern found in file: $file\"\n echo \"$result\"\n fi\n done\ndone\n\n# If any disallowed pattern was found, exit with status 1\nif [[ $error_found -eq 1 ]]; then\n exit 1\nfi\n\n# If no disallowed pattern was found, exit with status 0\nexit 0\n```\n\nYou can run this script with the file pattern as the first argument and the disallowed patterns as the rest of the arguments. For example:\n\n```bash\n./script.sh \"*.dart\" \"pattern1\" \"pattern2\"\n```",
- "isTrusted": false,
- "supportThemeIcons": false,
- "supportHtml": false
- }
- ],
- "followups": [
- {
- "kind": "reply",
- "message": "How can I pass multiple file patterns to the shell script?",
- "title": "How can I pass multiple file patterns to the shell script?"
- }
- ],
- "isCanceled": false,
- "agent": {
- "id": "",
- "metadata": {
- "description": "",
- "fullName": "GitHub Copilot",
- "hasSlashCommands": true,
- "hasFollowups": true,
- "isDefault": true,
- "helpTextPrefix": "You can ask me general programming questions, or chat with the following agents which have specialized expertise and can perform actions:",
- "helpTextPostfix": {
- "value": "To have a great conversation, ask me questions as if I was a real programmer:\n\n* **Show me the code** you want to talk about by having the files open and selecting the most important lines.\n* **Make refinements** by asking me follow-up questions, adding clarifications, providing errors, etc.\n* **Review my suggested code** and tell me about issues or improvements, so I can iterate on it.\n\nYou can also ask me questions about your editor selection by [starting an inline chat session](command:inlineChat.start).\n\nLearn more about [GitHub Copilot](https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode) in [Visual Studio Code](https://code.visualstudio.com/docs/editor/artificial-intelligence).",
- "isTrusted": {
- "enabledCommands": [
- "inlineChat.start"
- ]
- },
- "supportThemeIcons": false,
- "supportHtml": false,
- "uris": {
- "command:inlineChat.start": {
- "$mid": 1,
- "path": "inlineChat.start",
- "scheme": "command"
- },
- "https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode": {
- "$mid": 1,
- "path": "/copilot/getting-started-with-github-copilot",
- "scheme": "https",
- "authority": "docs.github.com",
- "query": "tool=vscode"
- },
- "https://code.visualstudio.com/docs/editor/artificial-intelligence": {
- "$mid": 1,
- "path": "/docs/editor/artificial-intelligence",
- "scheme": "https",
- "authority": "code.visualstudio.com"
- }
- }
- },
- "supportIssueReporting": false
- }
- },
- "contentReferences": []
- },
- {
- "message": {
- "parts": [
- {
- "range": {
- "start": 0,
- "endExclusive": 41
- },
- "editorRange": {
- "startLineNumber": 1,
- "startColumn": 1,
- "endLineNumber": 1,
- "endColumn": 42
- },
- "text": "How can I specify multiple file patterns?",
- "kind": "text"
- }
- ],
- "text": "How can I specify multiple file patterns?"
- },
- "response": [
- {
- "value": "To specify multiple file patterns, you can pass them as separate arguments and modify the script to treat each argument before the disallowed patterns as a file pattern. You'll need to specify a delimiter argument (like `--`) to separate the file patterns from the disallowed patterns.\n\nHere's how you can modify the script:\n\n```bash\n#!/bin/bash\n\n# Arrays to hold file patterns and disallowed patterns\nfile_patterns=()\ndisallowed_patterns=()\n\n# Flag to track if we're still reading file patterns\nreading_patterns=1\n\n# Loop over each argument\nfor arg in \"$@\"; do\n # If we encounter the delimiter, stop reading file patterns\n if [[ $arg == \"--\" ]]; then\n reading_patterns=0\n # If we're still reading file patterns\n elif [[ $reading_patterns -eq 1 ]]; then\n file_patterns+=(\"$arg\")\n # If we're reading disallowed patterns\n else\n disallowed_patterns+=(\"$arg\")\n fi\ndone\n\n# Flag to track if any disallowed pattern is found\nerror_found=0\n\n# Loop over each file pattern\nfor file_pattern in \"${file_patterns[@]}\"; do\n # Find files matching the pattern\n files=$(find . -name \"$file_pattern\")\n\n # Loop over each file\n for file in $files; do\n # Loop over each disallowed pattern\n for pattern in \"${disallowed_patterns[@]}\"; do\n # Search for the pattern in the file\n result=$(grep -n \"$pattern\" \"$file\")\n\n # If a match is found\n if [[ $result ]]; then\n # Set the error flag\n error_found=1\n\n # Print the filename, line number, and matching line\n echo \"Disallowed pattern found in file: $file\"\n echo \"$result\"\n fi\n done\n done\ndone\n\n# If any disallowed pattern was found, exit with status 1\nif [[ $error_found -eq 1 ]]; then\n exit 1\nfi\n\n# If no disallowed pattern was found, exit with status 0\nexit 0\n```\n\nYou can run this script with the file patterns as the first set of arguments, the delimiter `--`, and the disallowed patterns as the rest of the arguments. For example:\n\n```bash\n./script.sh \"*.dart\" \"*.txt\" \"--\" \"pattern1\" \"pattern2\"\n```",
- "isTrusted": false,
- "supportThemeIcons": false,
- "supportHtml": false
- }
- ],
- "followups": [
- {
- "kind": "reply",
- "message": "How can I exclude certain directories from the file search in the shell script?",
- "title": "How can I exclude certain directories from the file search in the shell script?"
- }
- ],
- "isCanceled": false,
- "agent": {
- "id": "",
- "metadata": {
- "description": "",
- "fullName": "GitHub Copilot",
- "hasSlashCommands": true,
- "hasFollowups": true,
- "isDefault": true,
- "helpTextPrefix": "You can ask me general programming questions, or chat with the following agents which have specialized expertise and can perform actions:",
- "helpTextPostfix": {
- "value": "To have a great conversation, ask me questions as if I was a real programmer:\n\n* **Show me the code** you want to talk about by having the files open and selecting the most important lines.\n* **Make refinements** by asking me follow-up questions, adding clarifications, providing errors, etc.\n* **Review my suggested code** and tell me about issues or improvements, so I can iterate on it.\n\nYou can also ask me questions about your editor selection by [starting an inline chat session](command:inlineChat.start).\n\nLearn more about [GitHub Copilot](https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode) in [Visual Studio Code](https://code.visualstudio.com/docs/editor/artificial-intelligence).",
- "isTrusted": {
- "enabledCommands": [
- "inlineChat.start"
- ]
- },
- "supportThemeIcons": false,
- "supportHtml": false,
- "uris": {
- "command:inlineChat.start": {
- "$mid": 1,
- "path": "inlineChat.start",
- "scheme": "command"
- },
- "https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode": {
- "$mid": 1,
- "path": "/copilot/getting-started-with-github-copilot",
- "scheme": "https",
- "authority": "docs.github.com",
- "query": "tool=vscode"
- },
- "https://code.visualstudio.com/docs/editor/artificial-intelligence": {
- "$mid": 1,
- "path": "/docs/editor/artificial-intelligence",
- "scheme": "https",
- "authority": "code.visualstudio.com"
- }
- }
- },
- "supportIssueReporting": false
- }
- },
- "contentReferences": []
- },
- {
- "message": {
- "parts": [
- {
- "range": {
- "start": 0,
- "endExclusive": 77
- },
- "editorRange": {
- "startLineNumber": 1,
- "startColumn": 1,
- "endLineNumber": 1,
- "endColumn": 78
- },
- "text": "Soooooooooo nice, thanks! Lastly, what is the good name of this great script?",
- "kind": "text"
- }
- ],
- "text": "Soooooooooo nice, thanks! Lastly, what is the good name of this great script?"
- },
- "response": [
- {
- "value": "You're welcome! A good name for this script could be `pattern_checker.sh` or `disallowed_pattern_detector.sh`. This name suggests that the script checks for certain patterns in files, which is its main function. Remember to make the script executable with the command `chmod +x pattern_checker.sh` before running it.",
- "isTrusted": false,
- "supportThemeIcons": false,
- "supportHtml": false
- }
- ],
- "followups": [
- {
- "kind": "reply",
- "message": "How can I exclude specific directories from the file search in the script?",
- "title": "How can I exclude specific directories from the file search in the script?"
- }
- ],
- "isCanceled": false,
- "agent": {
- "id": "",
- "metadata": {
- "description": "",
- "fullName": "GitHub Copilot",
- "hasSlashCommands": true,
- "hasFollowups": true,
- "isDefault": true,
- "helpTextPrefix": "You can ask me general programming questions, or chat with the following agents which have specialized expertise and can perform actions:",
- "helpTextPostfix": {
- "value": "To have a great conversation, ask me questions as if I was a real programmer:\n\n* **Show me the code** you want to talk about by having the files open and selecting the most important lines.\n* **Make refinements** by asking me follow-up questions, adding clarifications, providing errors, etc.\n* **Review my suggested code** and tell me about issues or improvements, so I can iterate on it.\n\nYou can also ask me questions about your editor selection by [starting an inline chat session](command:inlineChat.start).\n\nLearn more about [GitHub Copilot](https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode) in [Visual Studio Code](https://code.visualstudio.com/docs/editor/artificial-intelligence).",
- "isTrusted": {
- "enabledCommands": [
- "inlineChat.start"
- ]
- },
- "supportThemeIcons": false,
- "supportHtml": false,
- "uris": {
- "command:inlineChat.start": {
- "$mid": 1,
- "path": "inlineChat.start",
- "scheme": "command"
- },
- "https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode": {
- "$mid": 1,
- "path": "/copilot/getting-started-with-github-copilot",
- "scheme": "https",
- "authority": "docs.github.com",
- "query": "tool=vscode"
- },
- "https://code.visualstudio.com/docs/editor/artificial-intelligence": {
- "$mid": 1,
- "path": "/docs/editor/artificial-intelligence",
- "scheme": "https",
- "authority": "code.visualstudio.com"
- }
- }
- },
- "supportIssueReporting": false
- }
- },
- "contentReferences": []
- },
- {
- "message": {
- "parts": [
- {
- "range": {
- "start": 0,
- "endExclusive": 60
- },
- "editorRange": {
- "startLineNumber": 1,
- "startColumn": 1,
- "endLineNumber": 1,
- "endColumn": 61
- },
- "text": "Thanks. Can you write a small documentation for this script?",
- "kind": "text"
- }
- ],
- "text": "Thanks. Can you write a small documentation for this script?"
- },
- "response": [
- {
- "value": "Sure, here's a brief documentation for the `pattern_checker.sh` script:\n\n---\n\n# Pattern Checker Script\n\nThe `pattern_checker.sh` script is a utility for scanning files for disallowed patterns. It accepts a list of file patterns and a list of disallowed patterns as arguments, and scans all files matching the file patterns for the disallowed patterns.\n\n## Usage\n\n```bash\n./pattern_checker.sh [file patterns] -- [disallowed patterns]\n```\n\n### Parameters:\n\n- `[file patterns]`: A list of file patterns to scan. Each pattern should be quoted and separated by a space. For example, `\"*.dart\" \"*.txt\"`.\n- `[disallowed patterns]`: A list of disallowed patterns to search for in the files. Each pattern should be quoted and separated by a space. For example, `\"pattern1\" \"pattern2\"`.\n\nThe `--` argument is used to separate the file patterns from the disallowed patterns.\n\n### Example:\n\n```bash\n./pattern_checker.sh \"*.dart\" \"*.txt\" -- \"pattern1\" \"pattern2\"\n```\n\nThis command will scan all `.dart` and `.txt` files for the patterns `pattern1` and `pattern2`.\n\n## Output\n\nIf a disallowed pattern is found in a file, the script will print the filename, line number, and the matching line. If any disallowed patterns are found, the script will exit with a status of 1. If no disallowed patterns are found, the script will exit with a status of 0.\n\n---\n\nRemember to make the script executable with the command `chmod +x pattern_checker.sh` before running it.",
- "isTrusted": false,
- "supportThemeIcons": false,
- "supportHtml": false
- }
- ],
- "followups": [
- {
- "kind": "reply",
- "message": "How can I exclude certain directories from being scanned by the `pattern_checker.sh` script?",
- "title": "How can I exclude certain directories from being scanned by the `pattern_checker.sh` script?"
- }
- ],
- "isCanceled": false,
- "agent": {
- "id": "",
- "metadata": {
- "description": "",
- "fullName": "GitHub Copilot",
- "hasSlashCommands": true,
- "hasFollowups": true,
- "isDefault": true,
- "helpTextPrefix": "You can ask me general programming questions, or chat with the following agents which have specialized expertise and can perform actions:",
- "helpTextPostfix": {
- "value": "To have a great conversation, ask me questions as if I was a real programmer:\n\n* **Show me the code** you want to talk about by having the files open and selecting the most important lines.\n* **Make refinements** by asking me follow-up questions, adding clarifications, providing errors, etc.\n* **Review my suggested code** and tell me about issues or improvements, so I can iterate on it.\n\nYou can also ask me questions about your editor selection by [starting an inline chat session](command:inlineChat.start).\n\nLearn more about [GitHub Copilot](https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode) in [Visual Studio Code](https://code.visualstudio.com/docs/editor/artificial-intelligence).",
- "isTrusted": {
- "enabledCommands": [
- "inlineChat.start"
- ]
- },
- "supportThemeIcons": false,
- "supportHtml": false,
- "uris": {
- "command:inlineChat.start": {
- "$mid": 1,
- "path": "inlineChat.start",
- "scheme": "command"
- },
- "https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode": {
- "$mid": 1,
- "path": "/copilot/getting-started-with-github-copilot",
- "scheme": "https",
- "authority": "docs.github.com",
- "query": "tool=vscode"
- },
- "https://code.visualstudio.com/docs/editor/artificial-intelligence": {
- "$mid": 1,
- "path": "/docs/editor/artificial-intelligence",
- "scheme": "https",
- "authority": "code.visualstudio.com"
- }
- }
- },
- "supportIssueReporting": false
- }
- },
- "contentReferences": []
- },
- {
- "message": {
- "parts": [
- {
- "range": {
- "start": 0,
- "endExclusive": 158
- },
- "editorRange": {
- "startLineNumber": 1,
- "startColumn": 1,
- "endLineNumber": 1,
- "endColumn": 159
- },
- "text": "Thanks. Can you write a small documentation comments for this script? I will put it at the top of the script file, so each line must begins with a single `#`.",
- "kind": "text"
- }
- ],
- "text": "Thanks. Can you write a small documentation comments for this script? I will put it at the top of the script file, so each line must begins with a single `#`."
- },
- "response": [
- {
- "value": "Absolutely, here's a brief documentation comment for the `pattern_checker.sh` script:\n\n```bash\n# Pattern Checker Script\n#\n# This script is a utility for scanning files for disallowed patterns. It accepts a list of file patterns and a list of disallowed patterns as arguments, and scans all files matching the file patterns for the disallowed patterns.\n#\n# Usage:\n# ./pattern_checker.sh [file patterns] -- [disallowed patterns]\n#\n# Parameters:\n# [file patterns]: A list of file patterns to scan. Each pattern should be quoted and separated by a space. For example, \"*.dart\" \"*.txt\".\n# [disallowed patterns]: A list of disallowed patterns to search for in the files. Each pattern should be quoted and separated by a space. For example, \"pattern1\" \"pattern2\".\n# The -- argument is used to separate the file patterns from the disallowed patterns.\n#\n# Example:\n# ./pattern_checker.sh \"*.dart\" \"*.txt\" -- \"pattern1\" \"pattern2\"\n# This command will scan all .dart and .txt files for the patterns pattern1 and pattern2.\n#\n# Output:\n# If a disallowed pattern is found in a file, the script will print the filename, line number, and the matching line. If any disallowed patterns are found, the script will exit with a status of 1. If no disallowed patterns are found, the script will exit with a status of 0.\n#\n# Remember to make the script executable with the command chmod +x pattern_checker.sh before running it.\n```\n\nYou can place this comment at the top of your script file. It provides a brief overview of what the script does, how to use it, and what output to expect.",
- "isTrusted": false,
- "supportThemeIcons": false,
- "supportHtml": false
- }
- ],
- "followups": [
- {
- "kind": "reply",
- "message": "How can I exclude specific directories from being scanned by the `pattern_checker.sh` script?",
- "title": "How can I exclude specific directories from being scanned by the `pattern_checker.sh` script?"
- }
- ],
- "isCanceled": false,
- "agent": {
- "id": "",
- "metadata": {
- "description": "",
- "fullName": "GitHub Copilot",
- "hasSlashCommands": true,
- "hasFollowups": true,
- "isDefault": true,
- "helpTextPrefix": "You can ask me general programming questions, or chat with the following agents which have specialized expertise and can perform actions:",
- "helpTextPostfix": {
- "value": "To have a great conversation, ask me questions as if I was a real programmer:\n\n* **Show me the code** you want to talk about by having the files open and selecting the most important lines.\n* **Make refinements** by asking me follow-up questions, adding clarifications, providing errors, etc.\n* **Review my suggested code** and tell me about issues or improvements, so I can iterate on it.\n\nYou can also ask me questions about your editor selection by [starting an inline chat session](command:inlineChat.start).\n\nLearn more about [GitHub Copilot](https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode) in [Visual Studio Code](https://code.visualstudio.com/docs/editor/artificial-intelligence).",
- "isTrusted": {
- "enabledCommands": [
- "inlineChat.start"
- ]
- },
- "supportThemeIcons": false,
- "supportHtml": false,
- "uris": {
- "command:inlineChat.start": {
- "$mid": 1,
- "path": "inlineChat.start",
- "scheme": "command"
- },
- "https://docs.github.com/copilot/getting-started-with-github-copilot?tool=vscode": {
- "$mid": 1,
- "path": "/copilot/getting-started-with-github-copilot",
- "scheme": "https",
- "authority": "docs.github.com",
- "query": "tool=vscode"
- },
- "https://code.visualstudio.com/docs/editor/artificial-intelligence": {
- "$mid": 1,
- "path": "/docs/editor/artificial-intelligence",
- "scheme": "https",
- "authority": "code.visualstudio.com"
- }
- }
- },
- "supportIssueReporting": false
- }
- },
- "contentReferences": []
- }
- ],
- "providerId": "copilot"
-}
\ No newline at end of file
diff --git a/test/draggable/draggable_sheet_test.dart b/test/draggable/draggable_sheet_test.dart
new file mode 100644
index 00000000..c7bf83aa
--- /dev/null
+++ b/test/draggable/draggable_sheet_test.dart
@@ -0,0 +1,191 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+import 'package:smooth_sheets/src/foundation/sheet_controller.dart';
+
+import '../src/keyboard_inset_simulation.dart';
+
+class _TestApp extends StatelessWidget {
+ const _TestApp({
+ this.useMaterial = false,
+ required this.child,
+ });
+
+ final bool useMaterial;
+ final Widget child;
+
+ @override
+ Widget build(BuildContext context) {
+ if (useMaterial) {
+ return MaterialApp(
+ home: child,
+ );
+ } else {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: child,
+ ),
+ );
+ }
+ }
+}
+
+class _TestSheetContent extends StatelessWidget {
+ const _TestSheetContent({
+ this.height = 500,
+ });
+
+ final double? height;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ height: height,
+ width: double.infinity,
+ color: Colors.white,
+ );
+ }
+}
+
+void main() {
+ testWidgets('Inherited controller should be attached', (tester) async {
+ final controller = SheetController();
+ await tester.pumpWidget(
+ SheetControllerScope(
+ controller: controller,
+ child: const _TestApp(
+ child: DraggableSheet(
+ child: _TestSheetContent(),
+ ),
+ ),
+ ),
+ );
+
+ expect(controller.hasClient, isTrue,
+ reason: 'The controller should have a client.');
+ });
+
+ // Regression test for https://github.com/fujidaiti/smooth_sheets/issues/14
+ testWidgets('Opening keyboard does not interrupt sheet animation',
+ (tester) async {
+ final controller = SheetController();
+ final sheetKey = GlobalKey();
+ final keyboardSimulationKey = GlobalKey();
+
+ await tester.pumpWidget(
+ _TestApp(
+ useMaterial: true,
+ child: KeyboardInsetSimulation(
+ key: keyboardSimulationKey,
+ keyboardHeight: 200,
+ child: DraggableSheet(
+ key: sheetKey,
+ controller: controller,
+ minPosition: const SheetAnchor.pixels(200),
+ initialPosition: const SheetAnchor.pixels(200),
+ child: const Material(
+ child: _TestSheetContent(
+ height: 500,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ expect(controller.metrics.pixels, 200,
+ reason: 'The sheet should be at the initial position.');
+ expect(controller.metrics.minPixels < controller.metrics.maxPixels, isTrue,
+ reason: 'The sheet should be draggable.');
+
+ // Start animating the sheet to the max position.
+ unawaited(
+ controller.animateTo(
+ const SheetAnchor.proportional(1),
+ duration: const Duration(milliseconds: 250),
+ ),
+ );
+ // Then, show the keyboard while the animation is running.
+ unawaited(
+ keyboardSimulationKey.currentState!
+ .showKeyboard(const Duration(milliseconds: 250)),
+ );
+ await tester.pumpAndSettle();
+ expect(MediaQuery.viewInsetsOf(sheetKey.currentContext!).bottom, 200,
+ reason: 'The keyboard should be fully shown.');
+ expect(
+ controller.metrics.pixels,
+ controller.metrics.maxPixels,
+ reason: 'After the keyboard is fully shown, '
+ 'the entire sheet should also be visible.',
+ );
+ });
+
+ group('SheetKeyboardDismissible', () {
+ late FocusNode focusNode;
+ late ScrollController scrollController;
+ late Widget testWidget;
+
+ setUp(() {
+ focusNode = FocusNode();
+ scrollController = ScrollController();
+ testWidget = _TestApp(
+ useMaterial: true,
+ child: SheetKeyboardDismissible(
+ dismissBehavior: const SheetKeyboardDismissBehavior.onDrag(
+ isContentScrollAware: true,
+ ),
+ child: DraggableSheet(
+ child: Material(
+ child: TextField(
+ focusNode: focusNode,
+ scrollController: scrollController,
+ maxLines: 2,
+ ),
+ ),
+ ),
+ ),
+ );
+ });
+
+ tearDown(() {
+ focusNode.dispose();
+ scrollController.dispose();
+ });
+
+ testWidgets('should dismiss the keyboard when dragging', (tester) async {
+ await tester.pumpWidget(testWidget);
+
+ final textField = find.byType(TextField);
+ await tester.showKeyboard(textField);
+ expect(focusNode.hasFocus, isTrue,
+ reason: 'The keyboard should be shown.');
+
+ await tester.drag(textField, const Offset(0, -40));
+ await tester.pumpAndSettle();
+ expect(focusNode.hasFocus, isFalse,
+ reason: 'Downward dragging should dismiss the keyboard.');
+ });
+
+ testWidgets('should dismiss the keyboard when scrolling', (tester) async {
+ await tester.pumpWidget(testWidget);
+
+ final textField = find.byType(TextField);
+ await tester.enterText(textField, 'Hello, world! ' * 100);
+ await tester.pumpAndSettle();
+ expect(focusNode.hasFocus, isTrue,
+ reason: 'The keyboard should be shown.');
+ expect(scrollController.position.extentBefore, greaterThan(0),
+ reason: 'The text field should be able to scroll downwards.');
+
+ await tester.drag(textField, const Offset(0, 40));
+ await tester.pumpAndSettle();
+ expect(focusNode.hasFocus, isFalse,
+ reason: 'Downward dragging should dismiss the keyboard.');
+ });
+ });
+}
diff --git a/test/flutter_test_config.dart b/test/flutter_test_config.dart
new file mode 100644
index 00000000..a63fb510
--- /dev/null
+++ b/test/flutter_test_config.dart
@@ -0,0 +1,8 @@
+import 'dart:async';
+
+import 'package:flutter_test/flutter_test.dart';
+
+Future testExecutable(FutureOr Function() testMain) async {
+ WidgetController.hitTestWarningShouldBeFatal = true;
+ await testMain();
+}
diff --git a/test/foundation/physics_test.dart b/test/foundation/physics_test.dart
new file mode 100644
index 00000000..79d17f86
--- /dev/null
+++ b/test/foundation/physics_test.dart
@@ -0,0 +1,603 @@
+// ignore_for_file: lines_longer_than_80_chars
+import 'package:flutter/foundation.dart'
+ show debugDefaultTargetPlatformOverride;
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+import 'package:smooth_sheets/src/foundation/sheet_physics.dart';
+
+class _SheetPhysicsWithDefaultConfiguration extends SheetPhysics
+ with SheetPhysicsMixin {
+ const _SheetPhysicsWithDefaultConfiguration();
+
+ @override
+ SheetPhysics copyWith({SheetPhysics? parent, SpringDescription? spring}) {
+ return const _SheetPhysicsWithDefaultConfiguration();
+ }
+}
+
+const _referenceSheetMetrics = SheetMetricsSnapshot(
+ minPosition: SheetAnchor.pixels(0),
+ maxPosition: SheetAnchor.proportional(1),
+ pixels: 600,
+ contentSize: Size(360, 600),
+ viewportSize: Size(360, 700),
+ viewportInsets: EdgeInsets.zero,
+);
+
+final _positionAtTopEdge =
+ _referenceSheetMetrics.copyWith(pixels: _referenceSheetMetrics.maxPixels);
+
+final _positionAtBottomEdge =
+ _referenceSheetMetrics.copyWith(pixels: _referenceSheetMetrics.minPixels);
+
+final _positionAtMiddle = _referenceSheetMetrics.copyWith(
+ pixels: (_positionAtTopEdge.pixels + _positionAtBottomEdge.pixels) / 2,
+);
+
+void main() {
+ group('$SheetPhysics subclasses', () {
+ test('can create dynamic inheritance relationships', () {
+ const clamp = ClampingSheetPhysics();
+ const bounce = BouncingSheetPhysics();
+ const snap = SnappingSheetPhysics();
+
+ List getChain(SheetPhysics physics) {
+ return switch (physics.parent) {
+ null => [physics.runtimeType],
+ final parent => [physics.runtimeType, ...getChain(parent)],
+ };
+ }
+
+ expect(
+ getChain(clamp.applyTo(bounce).applyTo(snap)).join(' -> '),
+ 'ClampingSheetPhysics -> BouncingSheetPhysics -> SnappingSheetPhysics',
+ );
+
+ expect(
+ getChain(snap.applyTo(bounce).applyTo(clamp)).join(' -> '),
+ 'SnappingSheetPhysics -> BouncingSheetPhysics -> ClampingSheetPhysics',
+ );
+
+ expect(
+ getChain(bounce.applyTo(clamp).applyTo(snap)).join(' -> '),
+ 'BouncingSheetPhysics -> ClampingSheetPhysics -> SnappingSheetPhysics',
+ );
+ });
+ });
+
+ group('Default configuration of $SheetPhysics', () {
+ late SheetPhysics physicsUnderTest;
+
+ setUp(() {
+ physicsUnderTest = const _SheetPhysicsWithDefaultConfiguration();
+ });
+
+ test('dragStartDistanceMotionThreshold for different platforms', () {
+ for (final testTargetPlatform in TargetPlatform.values) {
+ debugDefaultTargetPlatformOverride = testTargetPlatform;
+ switch (testTargetPlatform) {
+ case TargetPlatform.iOS:
+ expect(
+ physicsUnderTest.dragStartDistanceMotionThreshold,
+ const BouncingScrollPhysics().dragStartDistanceMotionThreshold,
+ );
+ case _:
+ expect(physicsUnderTest.dragStartDistanceMotionThreshold, null);
+ }
+ debugDefaultTargetPlatformOverride = null;
+ }
+ });
+
+ test('does not allow over/under dragging', () {
+ expect(
+ physicsUnderTest.computeOverflow(10, _positionAtTopEdge),
+ moreOrLessEquals(10),
+ );
+ expect(
+ physicsUnderTest.computeOverflow(-10, _positionAtBottomEdge),
+ moreOrLessEquals(-10),
+ );
+ });
+
+ test('does not apply any resistance if position is in bounds', () {
+ final positionAtNearTopEdge = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels - 10);
+ final positionAtNearBottomEdge = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels + 10);
+
+ expect(
+ physicsUnderTest.applyPhysicsToOffset(10, _positionAtMiddle),
+ moreOrLessEquals(10),
+ );
+ expect(
+ physicsUnderTest.applyPhysicsToOffset(10, positionAtNearTopEdge),
+ moreOrLessEquals(10),
+ );
+ expect(
+ physicsUnderTest.applyPhysicsToOffset(-10, positionAtNearBottomEdge),
+ moreOrLessEquals(-10),
+ );
+ });
+
+ test('prevents position from going out of bounds', () {
+ expect(
+ physicsUnderTest.applyPhysicsToOffset(10, _positionAtTopEdge),
+ moreOrLessEquals(0),
+ );
+ expect(
+ physicsUnderTest.applyPhysicsToOffset(-10, _positionAtBottomEdge),
+ moreOrLessEquals(0),
+ );
+ });
+
+ test('creates no ballistic simulation if position is in bounds', () {
+ expect(
+ physicsUnderTest.createBallisticSimulation(0, _positionAtMiddle),
+ isNull,
+ );
+ expect(
+ physicsUnderTest.createBallisticSimulation(0, _positionAtTopEdge),
+ isNull,
+ );
+ expect(
+ physicsUnderTest.createBallisticSimulation(0, _positionAtBottomEdge),
+ isNull,
+ );
+ });
+
+ test('creates ballistic simulation which ends at the nearest edge', () {
+ final overDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels + 10,
+ );
+ final underDragPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels - 10,
+ );
+ final overDragSimulation =
+ physicsUnderTest.createBallisticSimulation(0, overDraggedPosition);
+ final underDraggedSimulation =
+ physicsUnderTest.createBallisticSimulation(0, underDragPosition);
+
+ expect(overDragSimulation, isNotNull);
+ expect(
+ overDragSimulation!.x(5), // 5s passed
+ moreOrLessEquals(_referenceSheetMetrics.maxPixels),
+ );
+ expect(
+ overDragSimulation.dx(5), // 5s passed
+ moreOrLessEquals(0),
+ );
+
+ expect(underDraggedSimulation, isNotNull);
+ expect(
+ underDraggedSimulation!.x(5), // 5s passed
+ moreOrLessEquals(_referenceSheetMetrics.minPixels),
+ );
+ expect(
+ underDraggedSimulation.dx(5), // 5s passed
+ moreOrLessEquals(0),
+ );
+ });
+
+ test('findSettledPosition', () {
+ expect(
+ physicsUnderTest.findSettledPosition(0, _positionAtMiddle),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ reason: 'Should return the current position if it is in bounds',
+ );
+ expect(
+ physicsUnderTest.findSettledPosition(1000, _positionAtMiddle),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ reason: 'The velocity should not affect the result',
+ );
+
+ final overDraggedPosition = _positionAtTopEdge.copyWith(
+ pixels: _positionAtTopEdge.maxPixels + 10,
+ );
+ expect(
+ physicsUnderTest.findSettledPosition(0, overDraggedPosition),
+ _referenceSheetMetrics.maxPosition,
+ reason: 'Should return the max position if the position '
+ 'is out of the upper bound',
+ );
+ expect(
+ physicsUnderTest.findSettledPosition(1000, overDraggedPosition),
+ _referenceSheetMetrics.maxPosition,
+ reason: 'The velocity should not affect the result',
+ );
+
+ final underDraggedPosition = _positionAtBottomEdge.copyWith(
+ pixels: _positionAtBottomEdge.minPixels - 10,
+ );
+ expect(
+ physicsUnderTest.findSettledPosition(0, underDraggedPosition),
+ _referenceSheetMetrics.minPosition,
+ reason: 'Should return the min position if the position '
+ 'is out of the lower bound',
+ );
+ expect(
+ physicsUnderTest.findSettledPosition(1000, underDraggedPosition),
+ _referenceSheetMetrics.minPosition,
+ reason: 'The velocity should not affect the result',
+ );
+
+ // Boundary conditions
+ expect(
+ physicsUnderTest.findSettledPosition(1000, _positionAtTopEdge),
+ _referenceSheetMetrics.maxPosition,
+ reason:
+ 'Should return the max position if the position is at the upper bound',
+ );
+ expect(
+ physicsUnderTest.findSettledPosition(1000, _positionAtBottomEdge),
+ _referenceSheetMetrics.minPosition,
+ reason:
+ 'Should return the min position if the position is at the lower bound',
+ );
+ });
+ });
+
+ group('$SnapToNearestEdge', () {
+ late SnapToNearestEdge behaviorUnderTest;
+
+ setUp(() {
+ behaviorUnderTest = const SnapToNearestEdge(minFlingSpeed: 50);
+ });
+
+ test('snaps to nearest edge if velocity is small enough', () {
+ final positionAtNearTopEdge = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels - 50,
+ );
+ final positionAtNearBottomEdge = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels + 50,
+ );
+
+ expect(
+ behaviorUnderTest.findSettledPosition(0, positionAtNearTopEdge),
+ _referenceSheetMetrics.maxPosition,
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(0, positionAtNearBottomEdge),
+ _referenceSheetMetrics.minPosition,
+ );
+ });
+
+ test('is aware of fling gesture direction', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(50, _positionAtBottomEdge),
+ _referenceSheetMetrics.maxPosition,
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, _positionAtTopEdge),
+ _referenceSheetMetrics.minPosition,
+ );
+ });
+
+ test('is disabled if position is out of bounds', () {
+ final overDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels + 10,
+ );
+ final underDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels - 10,
+ );
+
+ expect(
+ behaviorUnderTest.findSettledPosition(0, overDraggedPosition),
+ isNull,
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(0, underDraggedPosition),
+ isNull,
+ );
+ });
+
+ test('Boundary conditions', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(0, _positionAtTopEdge), isNull);
+ expect(behaviorUnderTest.findSettledPosition(0, _positionAtBottomEdge),
+ isNull);
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, _positionAtTopEdge),
+ _referenceSheetMetrics.minPosition,
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(50, _positionAtBottomEdge),
+ _referenceSheetMetrics.maxPosition,
+ );
+ });
+ });
+ group('$SnapToNearest', () {
+ late SnapToNearest behaviorUnderTest;
+
+ setUp(() {
+ behaviorUnderTest = SnapToNearest(
+ minFlingSpeed: 50,
+ anchors: [
+ SheetAnchor.pixels(_positionAtBottomEdge.pixels),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ SheetAnchor.pixels(_positionAtTopEdge.pixels),
+ ],
+ );
+ });
+
+ test('snaps to nearest edge if velocity is small enough', () {
+ final positionAtNearTopEdge = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels - 50,
+ );
+ final positionAtNearMiddle = _referenceSheetMetrics.copyWith(
+ pixels: _positionAtMiddle.pixels + 50,
+ );
+ final positionAtNearBottomEdge = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels + 50,
+ );
+
+ expect(
+ behaviorUnderTest.findSettledPosition(0, positionAtNearTopEdge),
+ SheetAnchor.pixels(_referenceSheetMetrics.maxPixels),
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(0, positionAtNearMiddle),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(0, positionAtNearBottomEdge),
+ SheetAnchor.pixels(_referenceSheetMetrics.minPixels),
+ );
+ });
+
+ test('is aware of fling gesture direction', () {
+ final positionAtAboveMiddle = _positionAtMiddle.copyWith(
+ pixels: _positionAtMiddle.pixels + 10,
+ );
+ final positionAtBelowMiddle = _positionAtMiddle.copyWith(
+ pixels: _positionAtMiddle.pixels - 10,
+ );
+ // Flings up at the bottom edge
+ expect(
+ behaviorUnderTest.findSettledPosition(50, _positionAtBottomEdge),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ );
+ // Flings up at the slightly above the middle position
+ expect(
+ behaviorUnderTest.findSettledPosition(50, positionAtAboveMiddle),
+ SheetAnchor.pixels(_positionAtTopEdge.pixels),
+ );
+ // Flings down at the top edge
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, _positionAtTopEdge),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ );
+ // Flings down at the slightly below the middle position
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, positionAtBelowMiddle),
+ SheetAnchor.pixels(_positionAtBottomEdge.pixels),
+ );
+ });
+
+ test('is disabled if position is out of bounds', () {
+ final overDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels + 10,
+ );
+ final underDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels - 10,
+ );
+
+ expect(
+ behaviorUnderTest.findSettledPosition(0, overDraggedPosition),
+ isNull,
+ );
+ expect(
+ behaviorUnderTest.findSettledPosition(0, underDraggedPosition),
+ isNull,
+ );
+ });
+
+ test('Boundary condition: a drag ends exactly at the top detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(0, _positionAtTopEdge),
+ isNull,
+ );
+ });
+
+ test('Boundary condition: flings up exactly at the top detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(50, _positionAtTopEdge),
+ SheetAnchor.pixels(_positionAtTopEdge.pixels),
+ );
+ });
+
+ test('Boundary condition: flings down exactly at the top detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, _positionAtTopEdge),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ );
+ });
+
+ test('Boundary condition: a drag ends exactly at the middle detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(0, _positionAtMiddle),
+ isNull,
+ );
+ });
+
+ test('Boundary condition: flings up exactly at the middle detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(50, _positionAtMiddle),
+ SheetAnchor.pixels(_positionAtTopEdge.pixels),
+ );
+ });
+
+ test('Boundary condition: flings down exactly at the middle detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, _positionAtMiddle),
+ SheetAnchor.pixels(_positionAtBottomEdge.pixels),
+ );
+ });
+
+ test('Boundary condition: a drag ends exactly at the bottom detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(0, _positionAtBottomEdge),
+ isNull,
+ );
+ });
+
+ test('Boundary condition: flings up exactly at the bottom detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(50, _positionAtBottomEdge),
+ SheetAnchor.pixels(_positionAtMiddle.pixels),
+ );
+ });
+
+ test('Boundary condition: flings down exactly at the bottom detent', () {
+ expect(
+ behaviorUnderTest.findSettledPosition(-50, _positionAtBottomEdge),
+ SheetAnchor.pixels(_positionAtBottomEdge.pixels),
+ );
+ });
+ });
+
+ test('FixedBouncingBehavior returns same value for same input metrics', () {
+ expect(
+ const FixedBouncingBehavior(SheetAnchor.pixels(100))
+ .computeBounceablePixels(50, _referenceSheetMetrics),
+ 100,
+ );
+ expect(
+ const FixedBouncingBehavior(SheetAnchor.proportional(0.5))
+ .computeBounceablePixels(50, _referenceSheetMetrics),
+ 300,
+ );
+ });
+
+ test('DirectionAwareBouncingBehavior respects gesture direction', () {
+ const behavior = DirectionAwareBouncingBehavior(
+ upward: SheetAnchor.pixels(100),
+ downward: SheetAnchor.pixels(0),
+ );
+ expect(behavior.computeBounceablePixels(50, _referenceSheetMetrics), 100);
+ expect(behavior.computeBounceablePixels(-50, _referenceSheetMetrics), 0);
+ });
+
+ group('BouncingSheetPhysics', () {
+ test('progressively applies friction if position is out of bounds', () {
+ const physics = BouncingSheetPhysics(
+ behavior: FixedBouncingBehavior(SheetAnchor.pixels(50)),
+ frictionCurve: Curves.linear,
+ );
+
+ final overDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels + 10,
+ );
+ final moreOverDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels + 20,
+ );
+
+ expect(physics.applyPhysicsToOffset(10, overDraggedPosition), 6);
+ expect(physics.applyPhysicsToOffset(10, moreOverDraggedPosition), 4);
+ });
+
+ test('does not allow to go beyond bounceable bounds', () {
+ const physics = BouncingSheetPhysics(
+ behavior: FixedBouncingBehavior(SheetAnchor.pixels(30)),
+ frictionCurve: Curves.linear,
+ );
+
+ final overDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.maxPixels + 20,
+ );
+ final underDraggedPosition = _referenceSheetMetrics.copyWith(
+ pixels: _referenceSheetMetrics.minPixels - 20,
+ );
+
+ expect(
+ physics.applyPhysicsToOffset(20, overDraggedPosition),
+ moreOrLessEquals(0.53, epsilon: 0.01),
+ );
+ expect(
+ physics.applyPhysicsToOffset(-20, underDraggedPosition),
+ moreOrLessEquals(-0.53, epsilon: 0.01),
+ );
+ });
+
+ test('applies friction even if position is on boundary', () {
+ const physics = BouncingSheetPhysics(
+ behavior: FixedBouncingBehavior(SheetAnchor.pixels(50)),
+ frictionCurve: Curves.linear,
+ );
+
+ expect(physics.applyPhysicsToOffset(10, _positionAtTopEdge), 8);
+ expect(physics.applyPhysicsToOffset(-10, _positionAtBottomEdge), -8);
+ });
+
+ test('can apply a reasonable friction to extremely large offset', () {
+ const physics = BouncingSheetPhysics(
+ behavior: FixedBouncingBehavior(SheetAnchor.pixels(50)),
+ frictionCurve: Curves.linear,
+ );
+
+ expect(
+ physics.applyPhysicsToOffset(300, _positionAtTopEdge),
+ moreOrLessEquals(33.42, epsilon: 0.01),
+ );
+ expect(
+ physics.applyPhysicsToOffset(-300, _positionAtBottomEdge),
+ moreOrLessEquals(-33.42, epsilon: 0.01),
+ );
+ });
+ });
+
+ group('sortPositionsAndFindNearest', () {
+ test('with two positions', () {
+ final (sortedPositions, nearestIndex) = sortPositionsAndFindNearest(
+ const [SheetAnchor.proportional(1), SheetAnchor.pixels(0)],
+ 250,
+ const Size(400, 600),
+ );
+ expect(sortedPositions, const [
+ (position: SheetAnchor.pixels(0), resolved: 0),
+ (position: SheetAnchor.proportional(1), resolved: 600),
+ ]);
+ expect(nearestIndex, 0);
+ });
+
+ test('with three positions', () {
+ final (sortedPositions, nearestIndex) = sortPositionsAndFindNearest(
+ const [
+ SheetAnchor.proportional(1),
+ SheetAnchor.proportional(0.5),
+ SheetAnchor.pixels(0),
+ ],
+ 250,
+ const Size(400, 600),
+ );
+ expect(sortedPositions, const [
+ (position: SheetAnchor.pixels(0), resolved: 0),
+ (position: SheetAnchor.proportional(0.5), resolved: 300),
+ (position: SheetAnchor.proportional(1), resolved: 600),
+ ]);
+ expect(nearestIndex, 1);
+ });
+
+ test('with more than three positions', () {
+ final (sortedPositions, nearestIndex) = sortPositionsAndFindNearest(
+ const [
+ SheetAnchor.proportional(0.25),
+ SheetAnchor.proportional(0.5),
+ SheetAnchor.proportional(0.75),
+ SheetAnchor.pixels(0),
+ SheetAnchor.proportional(1),
+ ],
+ 500,
+ const Size(400, 600),
+ );
+ expect(sortedPositions, const [
+ (position: SheetAnchor.pixels(0), resolved: 0),
+ (position: SheetAnchor.proportional(0.25), resolved: 150),
+ (position: SheetAnchor.proportional(0.5), resolved: 300),
+ (position: SheetAnchor.proportional(0.75), resolved: 450),
+ (position: SheetAnchor.proportional(1), resolved: 600),
+ ]);
+ expect(nearestIndex, 3);
+ });
+ });
+}
diff --git a/test/foundation/sheet_activity_test.dart b/test/foundation/sheet_activity_test.dart
new file mode 100644
index 00000000..021c77d1
--- /dev/null
+++ b/test/foundation/sheet_activity_test.dart
@@ -0,0 +1,353 @@
+import 'package:flutter/animation.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:smooth_sheets/smooth_sheets.dart';
+import 'package:smooth_sheets/src/foundation/sheet_activity.dart';
+
+import '../src/matchers.dart';
+import '../src/stubbing.dart';
+
+class _TestAnimatedSheetActivity extends AnimatedSheetActivity {
+ _TestAnimatedSheetActivity({
+ required AnimationController controller,
+ required super.destination,
+ required super.duration,
+ required super.curve,
+ }) : _controller = controller;
+
+ final AnimationController _controller;
+
+ @override
+ AnimationController createAnimationController() {
+ return _controller;
+ }
+}
+
+void main() {
+ group('AnimatedSheetActivity', () {
+ late MockAnimationController controller;
+
+ setUp(() {
+ controller = MockAnimationController();
+ when(
+ controller.animateTo(
+ any,
+ duration: anyNamed('duration'),
+ curve: anyNamed('curve'),
+ ),
+ ).thenAnswer((_) => MockTickerFuture());
+ });
+
+ test('should animate to the destination', () {
+ final (ownerMetrics, owner) = createMockSheetPosition(
+ pixels: 300,
+ minPosition: const SheetAnchor.pixels(300),
+ maxPosition: const SheetAnchor.pixels(700),
+ contentSize: const Size(400, 700),
+ viewportSize: const Size(400, 900),
+ viewportInsets: EdgeInsets.zero,
+ devicePixelRatio: 1,
+ );
+
+ final activity = _TestAnimatedSheetActivity(
+ controller: controller,
+ destination: const SheetAnchor.pixels(700),
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.linear,
+ )..init(owner);
+
+ verify(
+ controller.animateTo(
+ 1,
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.linear,
+ ),
+ );
+
+ when(controller.value).thenReturn(0.0);
+ activity.onAnimationTick();
+ expect(ownerMetrics.pixels, 300);
+
+ when(controller.value).thenReturn(0.5);
+ activity.onAnimationTick();
+ expect(ownerMetrics.pixels, 500);
+
+ when(controller.value).thenReturn(1.0);
+ activity.onAnimationTick();
+ expect(ownerMetrics.pixels, 700);
+ });
+
+ test('should absorb viewport changes', () {
+ final (ownerMetrics, owner) = createMockSheetPosition(
+ pixels: 300,
+ minPosition: const SheetAnchor.pixels(300),
+ maxPosition: const SheetAnchor.proportional(1),
+ contentSize: const Size(400, 900),
+ viewportSize: const Size(400, 900),
+ viewportInsets: EdgeInsets.zero,
+ devicePixelRatio: 1,
+ );
+
+ final activity = _TestAnimatedSheetActivity(
+ controller: controller,
+ destination: const SheetAnchor.proportional(1),
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.linear,
+ )..init(owner);
+
+ when(controller.value).thenReturn(0.0);
+ activity.onAnimationTick();
+ expect(ownerMetrics.pixels, 300);
+
+ when(controller.value).thenReturn(0.25);
+ when(controller.lastElapsedDuration)
+ .thenReturn(const Duration(milliseconds: 75));
+ activity.onAnimationTick();
+ expect(ownerMetrics.pixels, 450);
+
+ // The following lines simulate a viewport change, in which:
+ // 1. The viewport's bottom inset increases, simulating the
+ // appearance of an on-screen keyboard.
+ // 2. The content size then reduces to avoid overlapping with the
+ // increased bottom inset.
+ // This scenario mimics the behavior when opening a keyboard
+ // on a sheet that uses SheetContentScaffold.
+ final oldViewportInsets = ownerMetrics.viewportInsets;
+ final oldContentSize = ownerMetrics.contentSize;
+ ownerMetrics
+ ..maybeViewportInsets = const EdgeInsets.only(bottom: 50)
+ ..maybeContentSize = const Size(400, 850);
+
+ activity.didChangeViewportDimensions(null, oldViewportInsets);
+ activity.didChangeContentSize(oldContentSize);
+ activity.didFinalizeDimensions(oldContentSize, null, oldViewportInsets);
+ expect(ownerMetrics.pixels, 400);
+ expect(ownerMetrics.viewPixels, 450,
+ reason: 'Visual position should not change when viewport changes.');
+ verify(owner.settleTo(
+ const SheetAnchor.proportional(1),
+ const Duration(milliseconds: 225),
+ ));
+ });
+ });
+
+ group('SettlingSheetActivity', () {
+ late MockSheetPosition owner;
+ late MutableSheetMetrics ownerMetrics;
+ late MockTicker internalTicker;
+ late TickerCallback? internalOnTickCallback;
+
+ setUp(() {
+ (ownerMetrics, owner) = createMockSheetPosition(
+ pixels: 300,
+ minPosition: const SheetAnchor.proportional(0.5),
+ maxPosition: const SheetAnchor.proportional(1),
+ contentSize: const Size(400, 600),
+ viewportSize: const Size(400, 900),
+ viewportInsets: EdgeInsets.zero,
+ devicePixelRatio: 1,
+ );
+ internalTicker = MockTicker();
+ final tickerProvider = MockTickerProvider();
+ final context = MockSheetContext();
+ when(context.vsync).thenReturn(tickerProvider);
+ when(tickerProvider.createTicker(any)).thenAnswer((invocation) {
+ internalOnTickCallback =
+ invocation.positionalArguments[0] as TickerCallback;
+ return internalTicker;
+ });
+ when(owner.context).thenReturn(context);
+ });
+
+ tearDown(() {
+ internalOnTickCallback = null;
+ });
+
+ test('Create with velocity', () {
+ final activity = SettlingSheetActivity(
+ destination: const SheetAnchor.pixels(0),
+ velocity: 100,
+ );
+
+ expect(activity.destination, const SheetAnchor.pixels(0));
+ expect(activity.duration, isNull);
+ expect(activity.velocity, 100);
+ expect(activity.shouldIgnorePointer, isFalse);
+ });
+
+ test('Create with duration', () {
+ final activity = SettlingSheetActivity.withDuration(
+ const Duration(milliseconds: 300),
+ destination: const SheetAnchor.pixels(0),
+ );
+
+ expect(activity.destination, const SheetAnchor.pixels(0));
+ expect(activity.duration, const Duration(milliseconds: 300));
+ expect(activity.shouldIgnorePointer, isFalse);
+ expect(() => activity.velocity, isNotInitialized);
+ });
+
+ test(
+ 'Velocity should be set when the activity is initialized',
+ () {
+ final activity = SettlingSheetActivity.withDuration(
+ const Duration(milliseconds: 300),
+ destination: const SheetAnchor.proportional(1),
+ );
+ expect(() => activity.velocity, isNotInitialized);
+
+ activity.init(owner);
+ expect(activity.velocity, 1000); // (300pixels / 300ms) = 1000 pixels/s
+ },
+ );
+
+ test('Progressively updates current position toward destination', () {
+ final activity = SettlingSheetActivity(
+ destination: const SheetAnchor.proportional(1),
+ velocity: 300,
+ );
+
+ activity.init(owner);
+ verify(internalTicker.start());
+
+ internalOnTickCallback!(const Duration(milliseconds: 200));
+ expect(ownerMetrics.pixels, 360); // 300 * 0.2 = 60 pixels in 200ms
+
+ internalOnTickCallback!(const Duration(milliseconds: 400));
+ expect(ownerMetrics.pixels, 420); // 300 * 0.2 = 60 pixels in 200ms
+
+ internalOnTickCallback!(const Duration(milliseconds: 500));
+ expect(ownerMetrics.pixels, 450); // 300 * 0.1 = 30 pixels in 100ms
+
+ internalOnTickCallback!(const Duration(milliseconds: 800));
+ expect(ownerMetrics.pixels, 540); // 300 * 0.3 = 90 pixels in 300ms
+
+ internalOnTickCallback!(const Duration(milliseconds: 1000));
+ expect(ownerMetrics.pixels, 600); // 300 * 0.2 = 60 pixels in 200ms
+ });
+
+ test(
+ 'Should start an idle activity when it reaches destination',
+ () {
+ final _ = SettlingSheetActivity(
+ destination: const SheetAnchor.proportional(1),
+ velocity: 300,
+ )..init(owner);
+
+ ownerMetrics.maybePixels = 540;
+ internalOnTickCallback!(const Duration(milliseconds: 1000));
+ verify(owner.goIdle());
+ },
+ );
+
+ test('Should absorb viewport changes', () {
+ final activity = SettlingSheetActivity.withDuration(
+ const Duration(milliseconds: 300),
+ destination: const SheetAnchor.proportional(1),
+ )..init(owner);
+
+ expect(activity.velocity, 1000); // (300 pixels / 0.3s) = 1000 pixels/s
+
+ internalOnTickCallback!(const Duration(milliseconds: 50));
+ expect(ownerMetrics.pixels, 350); // 1000 * 0.05 = 50 pixels in 50ms
+
+ final oldViewportInsets = ownerMetrics.viewportInsets;
+ final oldContentSize = ownerMetrics.contentSize;
+ // Show the on-screen keyboard.
+ ownerMetrics.maybeViewportInsets = const EdgeInsets.only(bottom: 30);
+ activity.didChangeViewportDimensions(null, oldViewportInsets);
+ activity.didChangeContentSize(oldContentSize);
+ activity.didFinalizeDimensions(oldContentSize, null, oldViewportInsets);
+ expect(ownerMetrics.pixels, 320,
+ reason: 'Visual position should not change when viewport changes.');
+ expect(activity.velocity, 1120, // 280 pixels / 0.25s = 1120 pixels/s
+ reason: 'Velocity should be updated when viewport changes.');
+
+ internalOnTickCallback!(const Duration(milliseconds: 100));
+ expect(ownerMetrics.pixels, 376); // 1120 * 0.05 = 56 pixels in 50ms
+ });
+ });
+
+ group('IdleSheetActivity', () {
+ test('should maintain previous position when keyboard appears', () {
+ final (ownerMetrics, owner) = createMockSheetPosition(
+ pixels: 450,
+ minPosition: const SheetAnchor.proportional(0.5),
+ maxPosition: const SheetAnchor.proportional(1),
+ contentSize: const Size(400, 850),
+ viewportSize: const Size(400, 900),
+ viewportInsets: const EdgeInsets.only(bottom: 50),
+ devicePixelRatio: 1,
+ physics: kDefaultSheetPhysics,
+ );
+
+ final activity = IdleSheetActivity()..init(owner);
+ const oldContentSize = Size(400, 900);
+ const oldViewportInsets = EdgeInsets.zero;
+ activity
+ ..didChangeContentSize(oldContentSize)
+ ..didChangeViewportDimensions(oldContentSize, oldViewportInsets)
+ ..didFinalizeDimensions(oldContentSize, null, oldViewportInsets);
+ expect(ownerMetrics.pixels, 425);
+ });
+
+ test(
+ 'should maintain previous position when content size changes, '
+ 'without animation if gap is small',
+ () {
+ final (ownerMetrics, owner) = createMockSheetPosition(
+ pixels: 300,
+ minPosition: const SheetAnchor.proportional(0.5),
+ maxPosition: const SheetAnchor.proportional(1),
+ contentSize: const Size(400, 580),
+ viewportSize: const Size(400, 900),
+ viewportInsets: EdgeInsets.zero,
+ devicePixelRatio: 1,
+ physics: kDefaultSheetPhysics,
+ );
+
+ final activity = IdleSheetActivity()..init(owner);
+ const oldContentSize = Size(400, 600);
+ activity
+ ..didChangeContentSize(oldContentSize)
+ ..didFinalizeDimensions(oldContentSize, null, null);
+ expect(ownerMetrics.pixels, 290);
+ // Still in the idle activity.
+ verifyNever(owner.beginActivity(any));
+ },
+ );
+
+ test(
+ 'should maintain previous position when content size changes, '
+ 'with animation if gap is large',
+ () {
+ final (ownerMetrics, owner) = createMockSheetPosition(
+ pixels: 300,
+ minPosition: const SheetAnchor.proportional(0.5),
+ maxPosition: const SheetAnchor.proportional(1),
+ contentSize: const Size(400, 500),
+ viewportSize: const Size(400, 900),
+ viewportInsets: EdgeInsets.zero,
+ devicePixelRatio: 1,
+ physics: kDefaultSheetPhysics,
+ );
+
+ final activity = IdleSheetActivity()..init(owner);
+ const oldContentSize = Size(400, 600);
+ activity
+ ..didChangeContentSize(oldContentSize)
+ ..didFinalizeDimensions(oldContentSize, null, null);
+ expect(ownerMetrics.pixels, 300);
+ verify(
+ owner.animateTo(
+ const SheetAnchor.proportional(0.5),
+ duration: anyNamed('duration'),
+ curve: anyNamed('curve'),
+ ),
+ );
+ },
+ );
+ });
+}
diff --git a/test/foundation/sheet_notification_test.dart b/test/foundation/sheet_notification_test.dart
new file mode 100644
index 00000000..a260afa7
--- /dev/null
+++ b/test/foundation/sheet_notification_test.dart
@@ -0,0 +1,362 @@
+import 'dart:async';
+
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:smooth_sheets/src/draggable/draggable_sheet.dart';
+import 'package:smooth_sheets/src/foundation/sheet_controller.dart';
+import 'package:smooth_sheets/src/foundation/sheet_notification.dart';
+import 'package:smooth_sheets/src/foundation/sheet_physics.dart';
+import 'package:smooth_sheets/src/foundation/sheet_position.dart';
+import 'package:smooth_sheets/src/foundation/sheet_status.dart';
+
+void main() {
+ testWidgets(
+ 'Drag gesture should dispatch drag start/update/end notifications in sequence',
+ (tester) async {
+ final reportedNotifications = [];
+ const targetKey = Key('target');
+
+ await tester.pumpWidget(
+ NotificationListener(
+ onNotification: (notification) {
+ reportedNotifications.add(notification);
+ return false;
+ },
+ child: DraggableSheet(
+ minPosition: const SheetAnchor.pixels(0),
+ // Disable the snapping effect
+ physics: const ClampingSheetPhysics(),
+ child: Container(
+ key: targetKey,
+ color: Colors.white,
+ width: double.infinity,
+ height: 500,
+ ),
+ ),
+ ),
+ );
+
+ final gesturePointer = await tester.press(find.byKey(targetKey));
+ await gesturePointer.moveBy(const Offset(0, 20));
+ await tester.pump();
+ expect(reportedNotifications, hasLength(2));
+ expect(
+ reportedNotifications[0],
+ isA()
+ .having((e) => e.metrics.maybePixels, 'pixels', 500)
+ .having((e) => e.status, 'status', SheetStatus.dragging)
+ .having((e) => e.dragDetails.kind, 'kind', PointerDeviceKind.touch)
+ .having(
+ (e) => e.dragDetails.localPosition,
+ 'localPosition',
+ const Offset(400, 250),
+ )
+ .having(
+ (e) => e.dragDetails.globalPosition,
+ 'globalPosition',
+ const Offset(400, 350),
+ ),
+ );
+ expect(
+ reportedNotifications[1],
+ isA()
+ .having((e) => e.metrics.maybePixels, 'pixels', 480)
+ .having((e) => e.status, 'status', SheetStatus.dragging)
+ .having(
+ (e) => e.dragDetails.axisDirection,
+ 'axisDirection',
+ VerticalDirection.up,
+ )
+ .having(
+ (e) => e.dragDetails.localPosition,
+ 'localPosition',
+ const Offset(400, 270),
+ )
+ .having(
+ (e) => e.dragDetails.globalPosition,
+ 'globalPosition',
+ const Offset(400, 370),
+ ),
+ );
+
+ reportedNotifications.clear();
+ await gesturePointer.moveBy(const Offset(0, 20));
+ await tester.pump();
+ expect(
+ reportedNotifications.single,
+ isA()
+ .having((e) => e.metrics.maybePixels, 'pixels', 460)
+ .having((e) => e.status, 'status', SheetStatus.dragging)
+ .having(
+ (e) => e.dragDetails.axisDirection,
+ 'axisDirection',
+ VerticalDirection.up,
+ )
+ .having(
+ (e) => e.dragDetails.localPosition,
+ 'localPosition',
+ const Offset(400, 290),
+ )
+ .having(
+ (e) => e.dragDetails.globalPosition,
+ 'globalPosition',
+ const Offset(400, 390),
+ ),
+ );
+
+ reportedNotifications.clear();
+ await gesturePointer.moveBy(const Offset(0, -20));
+ await tester.pump();
+ expect(
+ reportedNotifications.single,
+ isA()
+ .having((e) => e.metrics.maybePixels, 'pixels', 480)
+ .having((e) => e.status, 'status', SheetStatus.dragging)
+ .having(
+ (e) => e.dragDetails.axisDirection,
+ 'axisDirection',
+ VerticalDirection.up,
+ )
+ .having(
+ (e) => e.dragDetails.localPosition,
+ 'localPosition',
+ const Offset(400, 270),
+ )
+ .having(
+ (e) => e.dragDetails.globalPosition,
+ 'globalPosition',
+ const Offset(400, 370),
+ ),
+ );
+
+ reportedNotifications.clear();
+ await gesturePointer.up();
+ await tester.pump();
+ expect(
+ reportedNotifications.single,
+ isA()
+ .having((e) => e.metrics.maybePixels, 'pixels', 480)
+ .having((e) => e.status, 'status', SheetStatus.dragging)
+ .having((e) => e.dragDetails.velocity, 'velocity', Velocity.zero)
+ .having(
+ (e) => e.dragDetails.axisDirection,
+ 'axisDirection',
+ VerticalDirection.up,
+ ),
+ );
+
+ reportedNotifications.clear();
+ await tester.pumpAndSettle();
+ expect(reportedNotifications, isEmpty,
+ reason: 'Once the drag is ended, '
+ 'no notification should be dispatched.');
+ },
+ );
+
+ testWidgets(
+ 'Sheet animation should dispatch metrics update notifications',
+ (tester) async {
+ final reportedNotifications = [];
+ final controller = SheetController();
+
+ await tester.pumpWidget(
+ NotificationListener(
+ onNotification: (notification) {
+ reportedNotifications.add(notification);
+ return false;
+ },
+ child: DraggableSheet(
+ controller: controller,
+ minPosition: const SheetAnchor.pixels(0),
+ // Disable the snapping effect
+ physics: const ClampingSheetPhysics(),
+ child: Container(
+ color: Colors.white,
+ width: double.infinity,
+ height: 600,
+ ),
+ ),
+ ),
+ );
+
+ unawaited(
+ controller.animateTo(
+ const SheetAnchor.pixels(0),
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.linear,
+ ),
+ );
+ await tester.pump(Duration.zero);
+ expect(
+ reportedNotifications.single,
+ isA()
+ .having((e) => e.metrics.pixels, 'pixels', moreOrLessEquals(600))
+ .having((e) => e.status, 'status', SheetStatus.animating),
+ );
+
+ reportedNotifications.clear();
+ await tester.pump(const Duration(milliseconds: 100));
+ expect(
+ reportedNotifications.single,
+ isA()
+ .having((e) => e.metrics.pixels, 'pixels', moreOrLessEquals(400))
+ .having((e) => e.status, 'status', SheetStatus.animating),
+ );
+
+ reportedNotifications.clear();
+ await tester.pump(const Duration(milliseconds: 100));
+ expect(
+ reportedNotifications.single,
+ isA()
+ .having((e) => e.metrics.pixels, 'pixels', moreOrLessEquals(200))
+ .having((e) => e.status, 'status', SheetStatus.animating),
+ );
+
+ reportedNotifications.clear();
+ await tester.pump(const Duration(seconds: 100));
+ expect(
+ reportedNotifications.single,
+ isA