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

Added transition handling between the side drawer and chats #2221

Merged
merged 48 commits into from
Apr 8, 2021

Conversation

kidroca
Copy link
Contributor

@kidroca kidroca commented Apr 3, 2021

@marcaaron

Details

Add a loading transition when chat is selected and the side drawer closes

  1. Added a fullscreen loading component - just something for the time being - it needs to be revisited with proper design
  2. Added withDrawerState HOC
  3. Disable the comose input component when the drawer is extended - this fixes some issues with the keyboard staying open
  • doesn't work great on Android but has no side effects, the keyboard can still stay open sometimes. It works on iOS and mWeb the keyboard would always close when you open the drawer

Fixed Issues

Fixes #2154 fixes #2150

Tests

  1. From the home screen
  2. Have a few conversation
  3. Select a conversation
  4. The conversation should open with a brief loading
  • there should be a fullscreen loading indicator (centered)
  • on physical device sometimes loading happens so fast you might not see the loader. The transition should be smooth though
  1. Go back and select another conversation
  • There's shouldn't be any glimpse of the previous conversation
  • The transition should feel as you are opening a new page

The keyboard should be dismissed when you go back to the LHN

QA Steps

Same as the Tests above

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

Screenshots

Web

Expensify.cash.-.Google.Chrome.2021-04-08.22-20-51.mp4

Mobile Web

Screen.Recording.2021-04-08.at.22.37.58.mov

Desktop

Screen.Recording.2021-04-08.at.23.14.44.mov

iOS (Simulator)

Screen.Recording.2021-04-08.at.22.19.36.mov

Android

Android.Emulator.-.Pixel_2_API_29_5554.2021-04-08.22-10-54.mp4

@kidroca kidroca requested a review from a team as a code owner April 3, 2021 00:21
@MelvinBot MelvinBot requested review from pecanoro and removed request for a team April 3, 2021 00:22
@kidroca
Copy link
Contributor Author

kidroca commented Apr 3, 2021

@marcaaron This is my rough sketch of how things can work
I want to share it early on to address any concerns

This is a draft - Please don't pay attention to stuff that needs to be cleaned up. There are some lint errors and I'm yet to move styles, add documentation etc...

There are a few items I want to ask:

  • I've read you don't want to use promises - is the usage here OK - I can replace it with check in componentDidUpdate, but it won't be as elegant as now
  • I need to implement optimistic response - e.g. don't wait for fetchActions if the component has some data available to render immediately. I need to think about this but if you have an idea
  • The fullscreen loader might actual not be needed at all. I've tried without it and the transition is still very good. I replaced it with null 😄. I can add a sample video next week
    • I think the loader would need further work but that can be moved to another PR
    • you can skip reviewing it for the moment, until I'm ready with the draft

So all in all I've still got some work to polish this but what do you say?

@kidroca kidroca force-pushed the kidroca/report-view-loading-state branch from 2a68e47 to 4cf35fd Compare April 3, 2021 08:39
`reportID` comes with a delay when the prop comes from onyx
The user first selects a route in the drawer
The route immediately hides the drawer and reviews the report screen
`reportID` have not yet updated and the old report is visible for a moment

Switching to route params changes the `reportID` the moment the user selects
one of the sidebar chat links
Don't trigger a navigation event as this causes a few unintended re-renders

We don't need to trigger a navigation when can review the drawer from any
chat screen we're currently on
Keep the view mounted when the report doesn't change
Unmount the input when the drawer covers the view
Set autoFocus only for bigger screens as example
…itching

This is now handled by re-mounting
Remove action sorting performed on each render - apply only when actions change
This prop no longer exists
The calculation seems to be handled differently and this is leftover
This is no longer needed - functionality extracted outside
@kidroca kidroca force-pushed the kidroca/report-view-loading-state branch from 32c3c77 to 199fb11 Compare April 5, 2021 10:36
src/pages/home/report/ReportActionsView.js Outdated Show resolved Hide resolved
src/pages/home/report/ReportActionsView.js Show resolved Hide resolved
src/pages/home/report/ReportActionsView.js Show resolved Hide resolved
src/pages/home/report/ReportActionsView.js Show resolved Hide resolved
src/pages/home/report/ReportActionsView.js Show resolved Hide resolved
src/pages/home/ReportScreen.js Outdated Show resolved Hide resolved
@kidroca kidroca changed the title Draft: Added transition handling between the side drawer and chats Added transition handling between the side drawer and chats Apr 8, 2021
@kidroca
Copy link
Contributor Author

kidroca commented Apr 8, 2021

Updated
Added videos and tested on all Platforms
Check the desktop/web transitions - maybe we should skip transition on big screens, as they currently work well.

@kidroca kidroca requested a review from marcaaron April 8, 2021 20:20
@marcaaron
Copy link
Contributor

Thanks, totally missed the addition of the key. Makes sense now.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

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

Love the code changes here. Thanks!

The full page loader is slightly strange on web/desktop/larger screens, but I think we can address that in a follow up if anyone has major issues with it.

@marcaaron marcaaron merged commit e0a559c into Expensify:master Apr 8, 2021
@laurenreidexpensify
Copy link
Contributor

Assuming no regressions in next week @kidroca this will be paid on 15 April 👍🏽

@parasharrajat
Copy link
Member

Opening the Url expensify.cash directly. shows this screen infinitely.
image

@marcaaron
Copy link
Contributor

marcaaron commented Apr 9, 2021

@kidroca I think this is because we've switched from using the currentlyViewedReportID to the route.params.reportID. I missed that change in the PR. So, if we want to do it this way then we'll need to implement the solution discussed here:

https://expensify.slack.com/archives/C01GTK53T8Q/p1617908395456000

We can either:

  1. Implement this fix now and not create a new issue for it
  2. Revert this change by going back to using the currentlyViewedReportID temporarily and then create a new issue to switch things to route params.

@kidroca
Copy link
Contributor Author

kidroca commented Apr 9, 2021

The proper fix is to address the navigation it's related to other issues like the URL changing on mWeb, but I can't do it in the scope of the current ticket. If you want me to work on that I'll need a new ticket.

I can revert to using currentlyViewedReportID like it did before, though this will degrade the experience on Android. Will post a video

@kidroca
Copy link
Contributor Author

kidroca commented Apr 9, 2021

Here's how it would look like on Android when I revert to currentlyViewedReportID - the prop only updates after a delay.

Android.Report.Switch.mp4

This is an issue with slow AsyncStorage access speed. I don't know how exactly ONYX works, but it might be possible to add a small proxy layer that persists data but keeps in in memory as well, so when it is accessed it can come instantly and don't have to be read from the file system first.
I think the iOS implementation of AsyncStorage probably does something like this as it works instantly there, so this might be a feature that can be implemented for AsyncStorage - configurable in memory proxy for fast access

@marcaaron
Copy link
Contributor

I leave that up to you @kidroca. It's a regression from what we had before so we must do something for now :) I agree that switching the reportID source was probably not in scope so the fix for it doesn't need to be either.

p.s. I think maybe the video shared is to show some other behavior than what you are describing.

@kidroca
Copy link
Contributor Author

kidroca commented Apr 9, 2021

p.s. I think maybe the video shared is to show some other behavior than what you are describing.

Yep, sorry, updated the comment

Btw I thought of another patch fix that would better address this at the moment, I'll open a new PR. Should I copy the current PR description there?

@marcaaron
Copy link
Contributor

Should I copy the current PR description there?

Ah, no need, linking back to this PR should be enough.

@isagoico
Copy link

isagoico commented Apr 9, 2021

We found this issue that looks related to this PR #2327. Not completely sure since the issue is reproducible in production.

if (!_.isEqual(nextProps.reportActions, this.props.reportActions)) {
this.updateSortedReportActions(nextProps.reportActions);
Copy link
Contributor

Choose a reason for hiding this comment

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

More than a year ago that this was added now, but wondering if there was any particular reason this side-effect was put in shouldComponentUpdate instead of componentDidUpdate?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it because you were hoping to avoid having to evaluate _.isEqual(nextProps.reportAction, this.props.reportActions) in both shouldComponentUpdate and componentDidUpdate`?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not exactly... componentDidUpdate() happens after the render() so (as things are now) if we sorted the actions there then we'd also need to trigger an re-render somehow.

Using shouldComponentUpdate() with a class property is a way to re-sort actions before the render() method is called.

I guess a more current approach could be to recalculate the options in the render with a dependency on props.reportActions, but my hook fu is pretty weak tbh 😂

DZ-97vzW4AAbcZj

Copy link
Contributor Author

Choose a reason for hiding this comment

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

More than a year ago that this was added now, but wondering if there was any particular reason this side-effect was put in shouldComponentUpdate instead of componentDidUpdate?

Marc already answered, but yes, to capture sortedActions before rendering and without triggering a re-render

I guess a more current approach could be to recalculate the options in the render with a dependency on props.reportActions

If your talking about hooks it would be as simple as

const sortedActions = useMemo(() => util.sortActions(props.reportActions), [props.reportActions])

But then the component would need to be converted from a class to a function based component (to use hooks)

For a class component the only way to sort actions, (only) when the prop changed, is to use shouldComponentUpdate


Another alternative is to subscribe the view to reportId actions, and sort them in the connection configuration, so the component always receives sorted actions (they'd still be sorted only if they change)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, that's sort of what I had in mind. But, I think _.isEqual() is doing an optimized deep comparison whereas useMemo() would only run if the reportActions failed a simple object comparison? (so even better)

Another alternative is to subscribe the view to reportId actions, and sort them in the connection configuration, so the component always receives sorted actions (they'd still be sorted only if they change)

That's pretty interesting. If we are doing a keyChanged() then we know the subscriber is getting new data and it's an opportunity to run some logic without having to do the deep compare.

That said, I think we are maybe always creating new objects when merging or setting changes so when calling keysChanged() the simple compare in useMemo() might be all that we'd need. When keyChanged is called the object reference for something like props.reportActions would be pointing to a new object.

Copy link
Contributor

Choose a reason for hiding this comment

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

This conversation here is slightly related -> Expensify/react-native-onyx#185

Copy link
Contributor

Choose a reason for hiding this comment

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

Another alternative is to subscribe the view to reportId actions, and sort them in the connection configuration, so the component always receives sorted actions (they'd still be sorted only if they change)

This is pretty interesting, but I think overall the hook-based approach might be simpler/more accessible to more people in the long term?

Anyways, thanks for the thorough answers here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants