Skip to content
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

Merged
merged 18 commits into from Sep 21, 2017

Conversation

xster
Copy link
Member

@xster xster commented Sep 16, 2017

Fixes #10466
Diffbases #12106.

Also create the web of documentation between all the cupertino navigation and scaffold widgets.

@xster
Copy link
Member Author

xster commented Sep 16, 2017

Added test galore

/// Content can slide under the [navigationBar] when they're translucent.
final Widget child;


Copy link
Contributor

Choose a reason for hiding this comment

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

nit: extraneous blank line

Copy link
Member Author

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;
Copy link
Contributor

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).

Copy link
Member Author

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,
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member Author

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,
Copy link
Contributor

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.

Copy link
Member Author

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]
Copy link
Contributor

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.

Copy link
Member Author

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]
Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Member Author

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?

Copy link
Contributor

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. :-)

Copy link
Member Author

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.
Copy link
Contributor

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

Copy link
Member Author

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;
}
Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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: ...
Copy link
Contributor

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

Copy link
Member Author

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: ...
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link
Member Author

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,
Copy link
Contributor

Choose a reason for hiding this comment

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

extraneous indentation

Copy link
Member Author

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),
Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Contributor

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?

Copy link
Contributor

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)

Copy link
Contributor

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)

Copy link
Member Author

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?

Copy link
Member Author

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.

Copy link
Contributor

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. :-)

Copy link
Member Author

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,
Copy link
Contributor

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

Copy link
Member Author

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

@Hixie
Copy link
Contributor

Hixie commented Sep 20, 2017

LGTM modulo comments

@xster
Copy link
Member Author

xster commented Sep 20, 2017

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
@xster xster merged commit f9e8da6 into flutter:master Sep 21, 2017
@xster xster deleted the cupertino-tab branch September 21, 2017 04:01
@smkhalsa
Copy link

@xster Any chance we can a Material version of parallel tabbed navigation?

@xster
Copy link
Member Author

xster commented Mar 23, 2018

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.

@smkhalsa
Copy link

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.

@HansMuller
Copy link
Contributor

@smkhalsa Would the BottomNaviationBar work for your app?

@xster
Copy link
Member Author

xster commented Mar 23, 2018

@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.

@smkhalsa
Copy link

@xster correct. I need independent navigation history stacks. The approach you mentioned above seems to be working well.

@smkhalsa
Copy link

@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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create a CupertinoScaffold
5 participants