Skip to content

Conversation

@MitchellGoodwin
Copy link
Contributor

@MitchellGoodwin MitchellGoodwin commented Oct 25, 2024

Fixes #42560

Updated 12/04/24

Adds a CupertinoSheetRoute which displays an iOS style sheet, while giving the previous route a delegated transition to sync up correctly.

There's a lot of sample files in this draft showing different use cases for the sake of reviewing the PR. We'll want to probably go down to about two before this PR lands.

TODO in this PR

  • Write actual documentation
  • Write tests

TODO in a feature branch, and will be merged with this PR

  • Add swipe down to dismiss

TODO in a follow up PR

  • Stretching on swipe up
  • Add a fullscreen version
  • Dark mode theming

How it currently compares, with the simple case and nested navigation:

Flutter Native
Flutter-Simple Native-Simple
Flutter-Nested Native-stacked

Update:
Flutter-Status-Bar
Now the status bar will transition from dark to light text so that it's visible after the transition.

cupertino_sheet.0.dart has an example of what it looks like to do a simple case. It's fairly straightforward.

cupertino_sheet.1.dart shows one with nested navigation. It's necessary to add a Navigator if showing page navigation within the sheet is wanted. I'm thinking adding an API option that auto sets this up may be needed as there are some pitfalls.

cupertino_sheet.2.dart functionally does the same as above but uses the showCupertinoSheet to reduce boilerplate.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Oct 25, 2024
@MaherSafadii
Copy link

I just tried it locally and it matches the native one animation wise, but on the native one when you drag upwards, it stretches a bit, I hope you can also implement this behaviour too.

@MitchellGoodwin
Copy link
Contributor Author

I just tried it locally and it matches the native one animation wise, but on the native one when you drag upwards, it stretches a bit, I hope you can also implement this behaviour too.

Thanks for taking a look! Good callout on the drag upwards stretch. We're going to probably add the drag controls in a feature branch off of this one. We might not focus on getting the drag upwards behavior correct for initially landing it in master, and instead have that in a follow up issue for the sake of iteration. Same with the fullscreen version. I'll add it to the todos though so it's tracked somewhere.

@MaherSafadii
Copy link

another notice, the status bar should automatically turn the brightness to light when the sheet is opened.

@hls-app
Copy link

hls-app commented Oct 26, 2024

This is awesome! Been using https://github.com/jamesblasco/modal_bottom_sheet and would definitely prefer a native solution!

@MitchellGoodwin
Copy link
Contributor Author

another notice, the status bar should automatically turn the brightness to light when the sheet is opened.

Thanks for the callout. We can change the status bar brightness when the sheet is opened, but it is sudden and we can't currently get the gradual transition native has with our APIs. I talked to the iOS team about that and they are aware of it. I'll open an iterative issue on that when this lands.

@MaherSafadii
Copy link

MaherSafadii commented Oct 31, 2024

Another thing to consider is that is in dark mode the sheet in the back should lighten as the opposite of what happens in light mode it darkens, it makes the sheet in the background become visible if its black:

ScreenRecording_10-31-2024.18-21-02_1.mp4

@MitchellGoodwin
Copy link
Contributor Author

Another thing to consider is that is in dark mode the sheet in the back should lighten as the opposite of what happens in light mode it darkens, it makes the sheet in the background become visible if its black:

It looks to me like the top sheet itself has a brighter background as well so it stands out. That's probably default behavior. I'll add dark mode to the to-dos.

@MaherSafadii
Copy link

Another thing to consider is that is in dark mode the sheet in the back should lighten as the opposite of what happens in light mode it darkens, it makes the sheet in the background become visible if its black:

It looks to me like the top sheet itself has a brighter background as well so it stands out. That's probably default behavior. I'll add dark mode to the to-dos.

Yeah it does, on ios it detects if a sheet is open and automatically sets the sheet in the fronts background color to secondaryBackground.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it took me so long to review this. I played around with it locally and it seems to feel really good to my Android-user eyes. The API and examples also seem straightforward and well-integrated with Flutter's way of doing navigation.

Comparison with Swift UI

In Swift UI this is done something like this right:

.sheet(isPresented: $isSheetOpen)

How about to add another sheet? How about to add another page to the current sheet?

I'm wondering what the main flows are there so that we can make them easy in Flutter too.

Other stuff

Pushing a CupertinoSheetRoute starts showing the "MBS" style navigation, then:

  • Pushing another CuperinoSheetRoute appears like another tab within the MBS navigation.
  • Pushing any other route does it's normal transition, as if moving away from the MBS.
  • If you want to push something inside of the topmost MBS tab, you have to do that with nested navigation.

In Swift UI is it possible to do the second bullet point above?

Also, I think you mentioned it briefly before, but I wonder how you plan on implementing the feature where you can drag down to dismiss the sheet?

return CupertinoPageRoute<void>(
builder: (BuildContext context) {
return PopScope(
canPop: settings.name != '/',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was your main motivation for adding the PopScope because of what happens when using a back gesture on iOS?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking more closely, it seems like the main purpose is so that your nested routes can call Navigator.of(context).pop() and have it pop the root Navigator instead of the empty nested Navigator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, If you call pop() programmatically I want on the initial route in the sheet, I want it to pop the sheet. Originally it was going to an inbetween state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you also need NavigatorPopHandler?

bool useRootNavigator = true,
bool useNestedNavigation = false,
}) {
final BuildContext topLevelSheetContext = CupertinoSheetController.maybeOf(context)?.topLevelContext ?? context;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of nested MBSs?

}

/// Docs placeholder
class CupertinoSheetTransition extends StatelessWidget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit that some of these classes could be broken up into different files, though I know this is still a draft.

});

/// Docs placeholder
final WidgetBuilder pageBuilder;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CupertinoPageRoute etc. I think this is just called builder. Would it be better to use that name everywhere in this PR (like also showCuperitnoSheet)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pageBuilder is used for some dialog routes. builder is more common though, so we'll go with that.

children: <Widget>[
CupertinoButton.filled(
onPressed: () {
showCupertinoSheet<void>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in the final PR we should include an example that shows getting a return value from this.


/// The primary delegated transition. Will slide a non [CupertinoSheetRoute] page down.
///
/// If a [CupertinoSheetController] already exists in the stack, then it will
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the stack

Actually, if there is a regular route between the existing CupertinoSheetRoute and the new one, then it should it do the normal slide down animation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now if a regular route gets pushed to the root navigator after the sheet than the normal delegate logic will happen. I'm not 100% sure what the correct behavior should be. We may want to not play any outgoing transition at all on the sheet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it won't really be possible to push a normal route on top of a MBS and have it display as a normal route (instead it displays as an additional sheet in the MBS). Can you confirm that we're ok not supporting that? If it's not possible on native iOS or recommended against then this is probably the right call.

@MitchellGoodwin
Copy link
Contributor Author

Also, I think you mentioned it briefly before, but I wonder how you plan on implementing the feature where you can drag down to dismiss the sheet?

I'm working on that locally. There will be another draft PR branched off of this one for that.

}) {
final BuildContext topLevelSheetContext = CupertinoSheetController.maybeOf(context)?.topLevelContext ?? context;

final WidgetBuilder builder = switch (useNestedNavigation) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think a

final WidgetBuilder builder;
if (!useNestedNavigation) {
  builder = pageBuilder;
} else {
  builder = ...
}

is more readable than pattern match on a bool

return CupertinoPageRoute<void>(
builder: (BuildContext context) {
return PopScope(
canPop: settings.name != '/',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you also need NavigatorPopHandler?

Navigator.of(topLevelSheetContext).pop();
},
child: Semantics(
scopesRoute: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? I thought the CupertinoPageRoute would already have these

}

/// Docs placeholder
static CupertinoSheetController? maybeOf(BuildContext context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems weird to return a CupertinoSheetController from CupertinoSheetRoute.of. Do we really need these duplicate method?

}

/// Docs placeholder
class CupertinoSheetController extends InheritedWidget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a better name. Controller is usually a changeNotifier object in Flutter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of a inherited widget that people can access different level of context, have you thought about have a controller object that provide API to pop or push navigator route at different level?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this is that holding a context as widget parameter is can be error prompt as it can go stale at anytime. it will be more dangerous if this is a public api

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place that the context parameter is used is in popSheet, right? Maybe that could just take BuildContext as a parameter so that CupertinoSheetController doesn't need to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of a inherited widget that people can access different level of context, have you thought about have a controller object that provide API to pop or push navigator route at different level?

@chunhtai Are you talking about a generic controller to control nested navigation, or something specifically for this modal bottom sheet?

Copy link
Contributor

@chunhtai chunhtai Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this modal bottom sheet. For general nested navigation one can already use NavigatorState if they possess global key

Comment on lines 53 to 58
return Navigator(
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (BuildContext context) {
return PopScope(
Copy link
Contributor

@justinmc justinmc Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider trying this out with

  • go_router
  • deep linking, and/or specific web-style URL requirements.
  • state restoration
  • Navigator.pages

I wonder what kind of requirements you'll run into to get those working. Maybe you will need some kind of builder parameter instead of just the plain boolean.

@kerberjg
Copy link
Contributor

kerberjg commented Nov 6, 2024

@MitchellGoodwin Great work so far! Looking forward for this to land 💖

@MitchellGoodwin MitchellGoodwin force-pushed the cupertino-sheet branch 2 times, most recently from 46b2133 to 7f1a2c6 Compare November 14, 2024 17:53
@MitchellGoodwin
Copy link
Contributor Author

Added a sample that has both state restoration and nested navigation in one file. It works locally, but I'm unsure if I set it up in the ideal way.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts about the nested Navigator situation below.


/// The primary delegated transition. Will slide a non [CupertinoSheetRoute] page down.
///
/// If a [CupertinoSheetController] already exists in the stack, then it will
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it won't really be possible to push a normal route on top of a MBS and have it display as a normal route (instead it displays as an additional sheet in the MBS). Can you confirm that we're ok not supporting that? If it's not possible on native iOS or recommended against then this is probably the right call.

@MitchellGoodwin
Copy link
Contributor Author

So it won't really be possible to push a normal route on top of a MBS and have it display as a normal route (instead it displays as an additional sheet in the MBS).

It will be possible. The previous route, which is the MBS, just wouldn't transition out and would be static. So a Cupertino page would slide in from the right, but the previous route would not slide left.

On native iOS, it is possible, just a little awkward. If you navigate to a new page from within the sheet, it will be nested within the sheet. To navigate directly from the sheet to new page outside of the sheet, you have to define something like a callback in the parent view and pass it down to the sheet.

I don't believe this is recommended (it's difficult to find direct guidance on this). The sheet is supposed to give options related to the context of the screen it's covering, then always go back to the original page when it's done.

My opinion is that we should leave nested navigation up to the user completely

I think I'm at that opinion as well. I've gotten a nested navigation example with the pages API working in several different ways, none of which make for an short sample. I think it would be pretty bloated to give a FULL sample in the PR, but could make a good blogpost/ article for the website.

Again on native, I believe the way it works is the .sheet adds a NavigationView or NavigationStack with a lot of defaults turned off to the sheet by default. Both of which are very similar to our own Navigator. However, the recommendation seems to be that if you want to have nested navigation in your app it's better to add your own NavigationStack to the body of the sheet to control that.

So I think removing useNestedNavigation is the best idea. I was a thinking of providing a navigatorBuilder could be an option, but again, it's probably better to just let the developer handle setting that up in what ways work for them. If a common boiler plate becomes needed I'm sure people will let us know/ a package will be created.

I do not think we need or should add a Navigator by default the way SwiftUI does. That's not very "Flutter"y.

cc @chunhtai

@justinmc
Copy link
Contributor

It will be possible. The previous route, which is the MBS, just wouldn't transition out and would be static. So a Cupertino page would slide in from the right, but the previous route would not slide left.

Can you explain how to do that in Flutter code? If that's so, and as you explain it's hard to do in Swift UI, then I think I'm on board with the current approach 👍 .

@MitchellGoodwin
Copy link
Contributor Author

Can you explain how to do that in Flutter code? If that's so, and as you explain it's hard to do in Swift UI, then I think I'm on board with the current approach 👍 .

If there is no nested Navigator, than you just push a route as normal. If there is a nested Navigator, than you need to push the route to the root Navigator. So Navigator.push(context, rootNavigator: true), or if using named routes, whatever name gets you to the root Navigator.

@chunhtai
Copy link
Contributor

So I think removing useNestedNavigation is the best idea. I was a thinking of providing a navigatorBuilder could be an option, but again, it's probably better to just let the developer handle setting that up in what ways work for them. If a common boiler plate becomes needed I'm sure people will let us know/ a package will be created.

I do not think we need or should add a Navigator by default the way SwiftUI does. That's not very "Flutter"y.

I have not looked at the latest change yet, so I may be under false assumption.

In general, It depends on how hard the set up will be, and usually asking developer to create Navigator will require additional work like wrapping it in PopScope or navigatorPopHandler. If it does indeed have this kind of caveats AND it is a common use case, I would suggest adding a parameter to create navigator for them. Imagine we do the opposite, the developers make a bunch of mistakes and create apps with bad user experience. That is not something we want

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 13, 2025
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Jan 13, 2025
flutter/flutter@864d4f5...72db8f6

2025-01-13 [email protected] Roll Packages from 6554751 to 3c3bc68 (16 revisions) (flutter/flutter#161515)
2025-01-13 [email protected] Update error message for when leading/trailing width exceeds `ListTile` width and add missing test (flutter/flutter#161091)
2025-01-13 [email protected] Deprecate unused `ButtonStyleButton.iconAlignment` property (flutter/flutter#160023)
2025-01-12 [email protected] Provide monitor information. (flutter/flutter#161359)
2025-01-11 [email protected] [android_engine_test] Remove background/foreground from surface texture trampoline test. (flutter/flutter#161441)
2025-01-11 [email protected] Remove some miscellaneous references to Cirrus. (flutter/flutter#161390)
2025-01-11 [email protected] add semantics role and tab (flutter/flutter#161260)
2025-01-10 [email protected] CupertinoSheetRoute (flutter/flutter#157568)
2025-01-10 [email protected] Update `TextEditingController.text` documentation to recommend against using it in production code (flutter/flutter#157769)
2025-01-10 [email protected] ð��� [tool] Add a wirelessly connected device name as `displayName` (flutter/flutter#160497)
2025-01-10 [email protected] FixForward: method was renamed (flutter/flutter#161431)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
github-merge-queue bot pushed a commit that referenced this pull request Jan 14, 2025
Adds route settings to the sheet route. From [this
comment.](#157568 (comment))

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
@parker-sherrill
Copy link

parker-sherrill commented Jan 14, 2025

Screenshot 2025-01-14 at 2 38 30 PM

When is nested scrolling going to be added? I have tried all scrolling hacks. In the provided picture, I should be able to scroll to a third color.

I think everything else is working great at this point.

Also-- I had to create my own appbar to exist in a stack above the modal sheet. When using the typical navigation bar there is way too much padding from the top of the page + it disables gestures in widgets that are used in a Column or ListView towards the top; I could not figure out a solution other than a stack.

@MitchellGoodwin
Copy link
Contributor Author

When is nested scrolling going to be added? I have tried all scrolling hacks. In the provided picture, I should be able to scroll to a third color.

Created an issue: #161623. It's a quick fix to enable nested scrolling. Getting the nested scrolling to smoothly interact with the drag dismiss gesture will be a longer effort.

Also-- I had to create my own appbar to exist in a stack above the modal sheet.

We don't currently have anything on the backlog for adding a sheet specific app bar. Could you create an issue?

In general we're looking for issues for improvements to be made to the sheet.

@MaherSafadii
Copy link

When is nested scrolling going to be added? I have tried all scrolling hacks. In the provided picture, I should be able to scroll to a third color.

Created an issue: #161623. It's a quick fix to enable nested scrolling. Getting the nested scrolling to smoothly interact with the drag dismiss gesture will be a longer effort.

Also-- I had to create my own appbar to exist in a stack above the modal sheet.

We don't currently have anything on the backlog for adding a sheet specific app bar. Could you create an issue?

In general we're looking for issues for improvements to be made to the sheet.

I dont think new appbar is needed, the current CupertinoNavigationBar & CupertinoSliverNavigationBar are fine, all that's needed is change to their code to check if they're in a sheets context, if yes, change padding values and other small tweaks that make it match native iOS.

@MaherSafadii
Copy link

MaherSafadii commented Jan 15, 2025

When is nested scrolling going to be added? I have tried all scrolling hacks. In the provided picture, I should be able to scroll to a third color.

Created an issue: #161623. It's a quick fix to enable nested scrolling. Getting the nested scrolling to smoothly interact with the drag dismiss gesture will be a longer effort.

Also-- I had to create my own appbar to exist in a stack above the modal sheet.

We don't currently have anything on the backlog for adding a sheet specific app bar. Could you create an issue?
In general we're looking for issues for improvements to be made to the sheet.

I dont think new appbar is needed, the current CupertinoNavigationBar & CupertinoSliverNavigationBar are fine, all that's needed is change to their code to check if they're in a sheets context, if yes, change padding values and other small tweaks that make it match native iOS.

for example, I tried modifying Flutter's CupertinoNavigationBar to check if the current context in in a sheet or not, if yes it should remove the top SaveArea padding, here are pics of the before and after, also the transition on push glitches the nav bar, you have to explicitly set the set transitionBetweenRoutes to false in the sheets nav bar, it should automatically tell if its in a sheet and disable it.

Screenshot 2025-01-15 at 1 48 51 PM Screenshot 2025-01-15 at 1 49 05 PM

maheshj01 pushed a commit to maheshj01/flutter that referenced this pull request Jan 15, 2025
Fixes flutter#42560

### Updated 12/04/24

Adds a CupertinoSheetRoute which displays an iOS style sheet, while
giving the previous route a delegated transition to sync up correctly.

There's a lot of sample files in this draft showing different use cases
for the sake of reviewing the PR. We'll want to probably go down to
about two before this PR lands.

**TODO in this PR**

- [x] Write actual documentation
- [x] Write tests

**TODO in a feature branch, and will be merged with this PR**

- [ ] Add swipe down to dismiss

**TODO in a follow up PR**

- [ ] Stretching on swipe up
- [ ] Add a fullscreen version
- [x] Dark mode theming

How it currently compares, with the simple case and nested navigation:
| Flutter  | Native|
| ------------- | ------------- |
|
![Flutter-Simple](https://github.com/user-attachments/assets/2a1f277e-91c9-48e0-b894-5fad71ef6a21)
|![Native-Simple](https://github.com/user-attachments/assets/6960252a-f762-4ad8-8d68-5c3d7cf8d4e7)
|
|
![Flutter-Nested](https://github.com/user-attachments/assets/bacb3e35-9d11-4113-9331-75daaded67e7)
|
![Native-stacked](https://github.com/user-attachments/assets/70ccbbbb-24c2-40b5-b838-4f8412828b9b)
|

Update:

![Flutter-Status-Bar](https://github.com/user-attachments/assets/c738cc5c-7176-4df2-9422-6b3fa608c943)
Now the status bar will transition from dark to light text so that it's
visible after the transition.

`cupertino_sheet.0.dart` has an example of what it looks like to do a
simple case. It's fairly straightforward.

`cupertino_sheet.1.dart` shows one with nested navigation. It's
necessary to add a `Navigator` if showing page navigation within the
sheet is wanted. I'm thinking adding an API option that auto sets this
up may be needed as there are some pitfalls.

`cupertino_sheet.2.dart` functionally does the same as above but uses
the `showCupertinoSheet` to reduce boilerplate.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
maheshj01 pushed a commit to maheshj01/flutter that referenced this pull request Jan 15, 2025
Adds route settings to the sheet route. From [this
comment.](flutter#157568 (comment))

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
@masal9pse
Copy link
Contributor

From which version of Flutter will CupertinoSheetRoute be available?

@MaherSafadii
Copy link

From which version of Flutter will CupertinoSheetRoute be available?

The next stable version I think, you can try it now by switching to the main channel, if you want to try it without switching channels, you can copy the sheet code from this PR and directly add it to your flutter project which I did and works fine.

github-merge-queue bot pushed a commit that referenced this pull request Feb 5, 2025
…162181)

Working on the cupertino nav bars recently gave me some context on those
widgets, so when I saw this
[comment](#157568 (comment))
I was inspired to add a fix :)

Thanks @MaherSafadii for [starting the
exploration](#157568 (comment))
and also for the very helpful
[screenshots](#162021 (comment)).

Removes the following when
CupertinoNavigationBar/CupertinoSliverNavigationBar is used in a
CupertinoSheetRoute:
- Unneeded back button
- Superfluous top padding in CupertinoNavigationBar
- Full page route transitions

## Before:


https://github.com/user-attachments/assets/a6da3957-0cff-4491-9380-bbc676ac799d


## After:


https://github.com/user-attachments/assets/37cd1628-a47e-44aa-85c7-abceda6e7944

<details>
<summary>Sample code</summary>

```dart
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';

/// Flutter code sample for [CupertinoSheetRoute].

class CupertinoSheetApp extends StatelessWidget {
  const CupertinoSheetApp({super.key});

  @OverRide
  Widget build(BuildContext context) {
    return const CupertinoApp(title: 'Cupertino Sheet', home: HomePage());
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @OverRide
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Sheet Example'),
        automaticBackgroundVisibility: false,
      ),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            CupertinoButton.filled(
              onPressed: () {
                Navigator.of(context).push(
                  CupertinoSheetRoute<void>(
                    builder: (BuildContext context) => const _SheetScaffold(),
                  ),
                );
              },
              child: const Text('Open Bottom Sheet'),
            ),
          ],
        ),
      ),
    );
  }
}

class _SheetScaffold extends StatelessWidget {
  const _SheetScaffold();

  @OverRide
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5),
        middle: const Text('CupertinoNavigationBar Sample'),
        automaticBackgroundVisibility: false,
      ),
      child: Column(
        children: <Widget>[
          Container(height: 50, color: CupertinoColors.systemRed),
          Container(height: 50, color: CupertinoColors.systemGreen),
          Container(height: 50, color: CupertinoColors.systemBlue),
          Container(height: 50, color: CupertinoColors.systemYellow),
          Center(
            child: CupertinoButton.filled(
              onPressed: () {
                Navigator.of(context).push(
                  CupertinoSheetRoute<void>(
                    builder: (BuildContext context) =>
                        const SliverNavBarExample(),
                  ),
                );
              },
              child: const Text('Open Bottom Sheet'),
            ),
          )
        ],
      ),
    );
  }
}

class SliverNavBarExample extends StatelessWidget {
  const SliverNavBarExample({super.key});

  @OverRide
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: CustomScrollView(
        slivers: <Widget>[
          const CupertinoSliverNavigationBar(
            leading: Icon(CupertinoIcons.person_2),
            largeTitle: Text('Contacts'),
            trailing: Icon(CupertinoIcons.add_circled),
          ),
          SliverFillRemaining(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 10.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  const Text('Drag me up', textAlign: TextAlign.center),
                  CupertinoButton.filled(
                    onPressed: () {},
                    child: const Text('Bottom Automatic mode'),
                  ),
                  CupertinoButton.filled(
                    onPressed: () {},
                    child: const Text('Bottom Always mode'),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

```
</details>


Fixes [Cupertino navbars apply too much top padding within a
sheet](#162021).

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
auto-submit bot pushed a commit that referenced this pull request Feb 6, 2025
)

CupertinoSheetRoute is a new widget added in 3.29 as a part of #157568. #161696 fixes #161623, which was an issue where you could not put scrollable content in the sheet, which is expected behavior. CupertinoSheetRoute has been a highly requested widget so is expected to get a lot of use.

Impacted users: anyone using the CupertinoSheetRoute with scrollable content, which should be a common use case.

Impact description: not being able to scroll within the sheet would create a very bad first impression and leave the sheet unusable for a lot of apps.

Workaround: None.

Risk: This is a brand new widget, so none. 
Test Coverage (Are you confident that your fix is well-tested by automated tests?): Yes, the fix has regression tests.
Validation Steps (What are the steps to validate that this fix works?): Create a CupertinoSheetRoute with scrollable content that expands past the size of the sheet. #161623 has a code sample.
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 12, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 6, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 7, 2025
androidseb pushed a commit to androidseb/packages that referenced this pull request Jun 8, 2025
flutter/flutter@864d4f5...72db8f6

2025-01-13 [email protected] Roll Packages from 6554751 to 3c3bc68 (16 revisions) (flutter/flutter#161515)
2025-01-13 [email protected] Update error message for when leading/trailing width exceeds `ListTile` width and add missing test (flutter/flutter#161091)
2025-01-13 [email protected] Deprecate unused `ButtonStyleButton.iconAlignment` property (flutter/flutter#160023)
2025-01-12 [email protected] Provide monitor information. (flutter/flutter#161359)
2025-01-11 [email protected] [android_engine_test] Remove background/foreground from surface texture trampoline test. (flutter/flutter#161441)
2025-01-11 [email protected] Remove some miscellaneous references to Cirrus. (flutter/flutter#161390)
2025-01-11 [email protected] add semantics role and tab (flutter/flutter#161260)
2025-01-10 [email protected] CupertinoSheetRoute (flutter/flutter#157568)
2025-01-10 [email protected] Update `TextEditingController.text` documentation to recommend against using it in production code (flutter/flutter#157769)
2025-01-10 [email protected] ð��� [tool] Add a wirelessly connected device name as `displayName` (flutter/flutter#160497)
2025-01-10 [email protected] FixForward: method was renamed (flutter/flutter#161431)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
FMorschel pushed a commit to FMorschel/packages that referenced this pull request Jun 9, 2025
flutter/flutter@864d4f5...72db8f6

2025-01-13 [email protected] Roll Packages from 6554751 to 3c3bc68 (16 revisions) (flutter/flutter#161515)
2025-01-13 [email protected] Update error message for when leading/trailing width exceeds `ListTile` width and add missing test (flutter/flutter#161091)
2025-01-13 [email protected] Deprecate unused `ButtonStyleButton.iconAlignment` property (flutter/flutter#160023)
2025-01-12 [email protected] Provide monitor information. (flutter/flutter#161359)
2025-01-11 [email protected] [android_engine_test] Remove background/foreground from surface texture trampoline test. (flutter/flutter#161441)
2025-01-11 [email protected] Remove some miscellaneous references to Cirrus. (flutter/flutter#161390)
2025-01-11 [email protected] add semantics role and tab (flutter/flutter#161260)
2025-01-10 [email protected] CupertinoSheetRoute (flutter/flutter#157568)
2025-01-10 [email protected] Update `TextEditingController.text` documentation to recommend against using it in production code (flutter/flutter#157769)
2025-01-10 [email protected] ð��� [tool] Add a wirelessly connected device name as `displayName` (flutter/flutter#160497)
2025-01-10 [email protected] FixForward: method was renamed (flutter/flutter#161431)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for sheet presentation style