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

Clean up navigation logic (2/x). #4440

Merged
merged 14 commits into from
Jan 28, 2021
Merged

Conversation

chrisbobbe
Copy link
Contributor

@chrisbobbe chrisbobbe commented Jan 27, 2021

This is the second PR in the series that began with #4428.

It converts some more components to Hooks-based components, to prepare for future work, and it also makes some preparatory changes to ZulipStatusBar, which I'd like to focus on in the next PR in this series. (ZulipStatusBar, I've found, isn't the best place to put logic that handles the safe-area view at the top; that PR will relocate that logic.)

I found that I could reasonably include a fix for #4267 in this PR, to help it land sooner. The status bar still appears in the lightbox after this PR, despite passing hidden to the lightbox's ZulipStatusBar. I'll get to the bottom of that, later, but it's orthogonal to what's really needed for fixing #4267:

  1. having the lightbox header clear the unsafe area at the top (which contains the status bar, if present) when it slides down
  2. having the footer clear the unsafe area at the bottom, when it slides up
  3. padding the contents of the header and footer horizontally, so they don't appear in the horizontal unsafe areas while the device is in landscape mode.

(I neglected 2 and 3 in my #4268; so, I'm closing that.)

After those fixes, the lightbox looks like this on a newer iPhone (showing the image in this message). These screenshots show the state when the header and footer are visible; tapping the screen makes them slide cleanly off the screen. I've also tested on the office Android device, and on my own, older, iPhone; all works as expected.

Screen Shot 2021-01-27 at 12 14 19 PM Screen Shot 2021-01-27 at 12 14 59 PM

@gnprice
Copy link
Member

gnprice commented Jan 27, 2021

Thanks!

I think you mean #4267 as the issue this will fix.

I'll look at the code shortly, but first a couple of thoughts on those helpful screenshots:

  • It looks like this causes the header and footer to get separated from the top and bottom of the screen. That seems like an odd effect. Better would be for the background of the header and footer to extend all the way to the top and bottom, respectively.
    • This is what Apple's Photos app does, for example. They have a nav bar at top and a tab bar at bottom, and they extend all the way up and down (but keep their actual controls within the safe area.)
    • Actually, more than that: when the header is in view, we should be showing the actual status bar. (With a background coordinated with our header: see https://developer.apple.com/design/human-interface-guidelines/ios/bars/status-bars/. In the Photos app, the nav bar / header is visually continuous with the status bar.) We should hide the status bar just when we're hiding the header and footer, and showing purely the image.
  • This part is maybe a separate issue from Unable to close image preview dialog on iphone #4267 but I think is also part of Safe areas are not handled correctly on iPhone X #3066: it looks like we're trying to show the image, the one the user is trying to look at, all the way out to the edges of the full screen, and as a result trying to show it in places like the sensor notch where nothing can show. Instead, we should be scaling the image so it fits in the area you can see.
    • ... Well, I wrote that and then I went and looked at Apple's Photos app, viewing some screenshots (which conveniently, like I guess the image in your example message, have exactly the screen's size as their natural size) -- and they show them across the full screen, just like we do.
    • Ah, but if I rotate the device into landscape mode, then the screenshot gets scaled down to fit -- it gets "pillarboxed". We should do the same, I think. It looks like we're instead doing some unhelpful cropping.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

OK, and read through the code! All looks good modulo one comment below, and my comment above about the UI.

One more thought as I reread your PR description:

The status bar still appears in the lightbox after this PR, despite passing hidden to the lightbox's ZulipStatusBar. I'll get to the bottom of that, later, but it's orthogonal to what's really needed to fix #4267:

Hmm, does the status bar really appear in the lightbox? In your screenshot above in portrait mode, there's a status bar... but I think it is the status bar from the image being shown (which is itself a screenshot from a similar phone.) The time in it is "1:04", and that's the same as I see in that image.

As discussed above, I think the desired behavior is:

  • We should show the status bar -- the real live one from the device right now 🙂 -- when we're showing the header and footer. I think we currently don't, and don't in your screenshots from after this PR.
  • We should hide it when we're hiding the header and footer. I think we currently get this right (and from the code in the PR, I think it doesn't change that.)

Comment on lines 35 to 37
ModalSearchNavBar.defaultProps = {
canGoBack: true,
};
Copy link
Member

@gnprice gnprice Jan 27, 2021

Choose a reason for hiding this comment

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

It looks like this is our first Hooks conversion involving defaultProps!

What do you think of handling this in one of the normal ways for just a function that takes some arguments? That seems like it'd be most in the spirit of Hooks. I think it'd also help make it clearer what's going on. As is, there's actually a bit of a regression in the readability of this code, I think, because the default (and the fact that this one has a default, and parent components aren't required to pass it) gets moved down here, far from the Props definition and the top of the function.

For example, we could say

  const { autoFocus, searchBarOnChange, canGoBack = true } = props;

and then the Props type could say

  canGoBack?: boolean,

That style would also have the added benefit that the Props type would now reflect the actual external interface. In the class-component world, we need to define a Props type that's inward-facing, reflecting the defaults already being applied, because the component's implementation may have a lot of entry points (the various methods that use this.props), and we need to express that they can all count on those props already being filled in, even if they're optional for callers. So we've accepted not actually having the interface written down anywhere, because the Props type is mostly close enough and the defaultProps are right there (at the top of the class, a few lines below the Props definition) and the duplication of writing both would be worse. But with a function component, there's just the one entry point (the top of the function), and we can handle filling in the defaults explicitly and do so just once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you!

Comment on lines 40 to 47
<SafeAreaView
mode="padding"
// Why we don't do anything about the unsafe area at the
// bottom here: `Lightbox` ensures that the footer clears it
// when it slides in.
edges={['right', 'left']}
style={[styles.wrapper, style]}
>
Copy link
Member

Choose a reason for hiding this comment

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

Ah, so perhaps adding 'bottom' here would be the way to get the footer's padding to extend through to the bottom?

return (
<ActionSheetProvider>
<View style={[componentStyles.screen, { backgroundColor }]}>
<KeyboardAvoider style={styles.flexed} behavior="padding">
<ZulipStatusBar narrow={narrow} />
<ZulipStatusBar backgroundColor={titleBackgroundColor} />
Copy link
Member

Choose a reason for hiding this comment

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

Ah, this is much nicer!

@chrisbobbe
Copy link
Contributor Author

I think I'll tackle #4267 in a separate PR after this one—I've just pushed a revision that excludes that part. 🙂

@gnprice gnprice merged commit 80cbcc1 into zulip:master Jan 28, 2021
@gnprice
Copy link
Member

gnprice commented Jan 28, 2021

Looks good, thanks! Merged.

I edited the PR description first to replace "what's really needed to fix #4267" with "what's really needed for fixing #4267", so it wouldn't accidentally cause that issue to be closed 🙂

Comment on lines +98 to +100
style={[styles.overlay, { width: windowWidth, bottom: windowHeight - 44 }]}
from={windowHeight}
to={windowHeight - 44}
Copy link
Member

Choose a reason for hiding this comment

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

I also notice, thanks to this cleanup calling my attention to it, that this longstanding bit of code is all unnecessarily complicated -- if you subtract windowHeight from all three of these vertical values, it gets a lot simpler. Maybe simpler still if you subtract windowHeight - 44, so that it's:

  • bottom: 0
  • from={44}
  • to={0}

But that might all be made moot by the changes you're already working on to replace this translateY animation entirely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've got a commit that does exactly that simplification; about to send the PR. 😄

@karanb1
Copy link

karanb1 commented Jan 28, 2021

@chrisbobbe Can I help you with the cleanup and in conversion of component to hooks based components?
I can start with subscriptions and user status screens.

@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Jan 29, 2021

Sure, @karanb1, thanks! Several of our components don't have any lifecycle methods beyond render, so they're low-hanging fruit for Hooks conversions. For one that's even a little bit complicated, it would be helpful to handle it in multiple commits, if there are clear steps to be done along the way, as you can see I've done in this PR with Lightbox. I'd stay away from the really complicated components like ComposeBox, for now; there's a good chance that some subtlety of how we've got them working isn't clearly documented, which makes it unfortunately easy to introduce bugs in a refactor like this. If you'd like to follow or join the discussion about converting to Hooks, it's here. 🙂

@chrisbobbe
Copy link
Contributor Author

(Just split off some work on the lightbox in #4442.)

@gnprice gnprice mentioned this pull request Apr 28, 2021
gnprice added a commit that referenced this pull request Apr 28, 2021
Originally discussed here:
  #4440 (comment)
(with a bit of additional rationale) when this question first came up.

It's come up a couple more times since then, as we've continued
converting a lot of our components to functions with Hooks:
  #4555 (comment)
  #4699 (comment)
so it's definitely time to have it written down in a proper place.
@chrisbobbe chrisbobbe deleted the pr-nav-cleanup-2 branch November 4, 2021 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants