-
Notifications
You must be signed in to change notification settings - Fork 26.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create a CupertinoTab to support parallel navigation trees in iOS #12130
Conversation
Added test galore |
/// Content can slide under the [navigationBar] when they're translucent. | ||
final Widget child; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extraneous blank line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
if (navigationBar != null) { | ||
topPadding += navigationBar.preferredSize.height; | ||
if (topPadding > 0.0) | ||
topPadding += MediaQuery.of(context).padding.top; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment here (and maybe in the docs too) explaining what this is doing and why (e.g., why is a zero-height navigation bar so substantially different than a one-pixel navigation bar).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
if (navigationBar != null) { | ||
stacked.add(new Align( | ||
alignment: FractionalOffset.topCenter, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why Align rather than Positioned?
Also, why in the center rather than forced to the width of the box?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Positioned would mean the navbar doesn't affect the scaffold dimensions, which seems reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ummm, it was from the other file but not sure why I did it originally. Positioned definitely makes sense. Done
// The main content being at the bottom is added to the stack first. | ||
stacked.add(new Padding( | ||
padding: new EdgeInsets.only(top: topPadding), | ||
child: child, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what size do you want to give the child?
Consider the StackFit options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the default loose matches the Material scaffold's behaviour. Leaving as is
|
||
/// A single tab with its own [Navigator] state and history. | ||
/// | ||
/// A typical tab used as the content of each tab in a [CupertinoTabScaffold] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to confuse two meaning of "tab". @HansMuller might have suggestions for names to contrast the actual tab control that brings up the page, and the view that is associated with that tab.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, the names do collide...
Well I think the material widgets have precedence. Wholesale renamed CupertinoTab to CupertinoTabView to match.
So tab bar is tab bar
tab bar item is just 'tab'
and the tab pages are tab views.
/// | ||
/// 4. Finally if all else fails [onUnknownRoute] is called. | ||
/// | ||
/// These navigation properties is not shared with any sibling [CupertinoTab] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are
/// | ||
/// Be sure to acquire a [BuildContext] that belongs to the [CupertinoTab] (such | ||
/// as by using a [Builder]) to ensure that [Navigator.of] calls uses this | ||
/// [CupertinoTab]'s [Navigator] rather than that of its parent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this sentence doesn't say when this is important. Be sure to do this... when?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually on more thoughts, this thing might be a trap waiting to spring. Maybe I should just let the API be a Builder instead of a Widget. Any objections?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should consider it a rule that any time you allow a widget to have a child, it should accept any Widget at all. The way that one of the widgets here requires a CupertinoTabBar is already sketchy.
Flutter is built on the idea of unopinionated composition, meaning you can build things out of other things, and they just all fit together. The least satisfying lego bricks are the ones that only fit one other kind of lego piece. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we crossed threads somewhere.
For the whole thing about making sure to use a builder to reference the right navigator, I just avoided that trap and made the user give a builder instead of a widget. (the builder will take any widget returned if that's what you meant)
For being able to specify any widgets, I agree. Will leave #12078 open to let the tabBar property be anything.
return new Navigator( | ||
onGenerateRoute: _onGenerateRoute, | ||
onUnknownRoute: _onUnknownRoute, | ||
observers: navigatorObservers ?? const <NavigatorObserver>[], // Can't be null. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this default should be in the constructor, not here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return true; | ||
}); | ||
return result; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this code duplication is unfortunate. We'll have to clean this up when we do the routes refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please file a bug with the routes
label to remind me to do this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #12108 but didn't really come up with a good way of doing it.
The key part is that I want public fields to have customizable dartdocs which was hard to do.
Mixins just makes all the fields getters and not settable in the mixer's constructor. It could be a class by composition. Though it makes the MaterialApp WidgetsApp interface a bit awkward this shared logic is split between 2 classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole API is going to be refactored in a few months anyway. I'll clean it up then. No worries.
/// ```dart | ||
/// new CupertinoTabScaffold( | ||
/// tabBar: new CupertinoTabBar( | ||
/// items: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code in "Sample code" sections must be valid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh that's what the analyzer was complaining about. I couldn't figure it out. Thanks
/// navigationBar: new CupertinoNavigationBar( | ||
/// middle: new Text('Page 2 of tab $index'), | ||
/// ), | ||
/// child: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
// our own listener to update the _currentPage on top of a possibly user | ||
// provided callback. | ||
child: widget.tabBar.copyWith( | ||
currentIndex: _currentPage, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extraneous indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
|
||
// The main content being at the bottom is added to the stack first. | ||
stacked.add(new Padding( | ||
padding: new EdgeInsets.only(top: topPadding), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you apply the MediaQuery top padding here, you should make sure to introduce a child MediaQuery that removes that padding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if there's a padding here, I'd expect its descendants to never interact with window top paddings again kinda like material scaffold's body
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the descendant is a material Scaffold? or an AppBar? Or a PageScaffold?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably have a constructor on MediaQuery that takes a context
and can be told to take out one of the paddings...
like
new MediaQuery.removePadding(context, removeTop: true)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(it would be a factory constructor that grabs the ambient MediaQuery, and builds a new one that's identical except with the relevant padding(s) set to 0.0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean if there were a page scaffold with an app bar in the app bar position and another app bar in the body position? Ya, that would be problematic.
Will make the change.
Change to zero seems like a pretty specific API though. Why not just MediaQuery.of(context).copyWith?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually my thing is a bit cumbersome, it'd have to kinda be
new MediaQuery(
data: MediaQuery.of(context).data.copyWith(
padding: MediaQuery.of(context).data.padding.copyWith(
top: ...
Though @cbracken had a nice idea for a nice API that wraps all this stuff in a widget. I'll just make a MediaQuery the normal way and let Chris do the nicer API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SafeArea widget that @cbracken and I were talking about would also introduce padding, and would need to do the removing of the padding in a secondary MediaQuery as well. So it's actually a superset of the functionality you need here, and therefore would be easily handled by the new MediaQuery.removePadding(context, removeTop: true)
-style API. But whatever floats your boat. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have to get on plane :S. Manually created a MediaQuery for now
} | ||
|
||
return new Stack( | ||
children: stacked, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider if you need to specify stackFit here, too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks. Expand makes sense here
This PR diffbases another PR #12106 that creates the 2 scaffolds. For sake of clearer history readability, I'm still gonna try to apply the review notes on the source PR and land them separately. |
# Conflicts: # packages/flutter/lib/cupertino.dart # packages/flutter/lib/src/cupertino/nav_bar.dart # packages/flutter/lib/src/cupertino/page_scaffold.dart # packages/flutter/lib/src/cupertino/tab_scaffold.dart # packages/flutter/test/cupertino/scaffold_test.dart
# Conflicts: # packages/flutter/lib/cupertino.dart # packages/flutter/lib/src/cupertino/page_scaffold.dart # packages/flutter/lib/src/cupertino/tab_scaffold.dart # packages/flutter/test/cupertino/scaffold_test.dart
05a64f4
to
8b28ec2
Compare
@xster Any chance we can a Material version of parallel tabbed navigation? |
Unlikely since it's not part of the Material Design specs (and generally doesn't create good UX patterns with Android back buttons). It's however not too hard to create that pattern in your own code if you replicate the CupertinoTabScaffold and use a material bottom tab bar. |
Thanks @xster for the quick response. In case anyone else is trying to do this, per @xster's suggestion, I was able to get this working by pulling out the TabSwitchingView from the CupertinoTabScaffold source code and using it as the tab view with a Material TabBar. |
@smkhalsa Would the BottomNaviationBar work for your app? |
@smkhalsa just to be very sure, when you say parallel tabbed navigation, you meant having multiple navigators with their own navigation history stack states maintained independently right? If you just want different tabs with the same navigation history stack, just use BottomNavigationBar as is. |
@xster correct. I need independent navigation history stacks. The approach you mentioned above seems to be working well. |
@HansMuller I may use a BottomNavigationBar as the tab widget. However, that alone doesn't allow me to maintain separate navigation stacks for each tab and keep each tab rendered, with inactive tabs moved offscreen. This is what Cupertino's TabSwitchingView does, and I was able to successfully repurpose it to use in Material-style tab navigation. |
Fixes #10466
Diffbases #12106.
Also create the web of documentation between all the cupertino navigation and scaffold widgets.