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

Fix keyboard issues in add attachment flow #15337

Conversation

priyeshshah11
Copy link
Contributor

@priyeshshah11 priyeshshah11 commented Feb 22, 2023

Details

A. For iOS: When opening the PopoverMenu, the keyboard will be closed (but the state of the keyboard opening is remembered by the OS). Then clicking Add attachment, the PopoverMenu is closed but the OS will reopen the keyboard. So when the AttachmentPicker is opened, due to this the keyboard is immediately closed, causing the glitch. We should only open the Modal component after the keyboard is completely hidden and when the AttachmentPicker, AttachmentModal, and PopoverMenu is closed/hidden, the keyboard will be reopened as expected.

B. For mWeb: When opening the PopoverMenu and then clicking Add attachment, the PopoverMenu will be closed. But the input is then focused and the keyboard is pushed up. After that, the native file picker appears and dismisses the keyboard immediately, causing the flickering. We should add a new state to indicate that we're opening the attachment picker and we do not refocus the input here if this state is true.

This PR does those

Fixed Issues

$ #13922
PROPOSAL: #13922 (comment)

Tests

  1. Click & focus on composer to open the keyboard on mobile.
  2. Click on '+' in composer, verify the keyboard closes smoothly & then the modal appears.
  3. Click add attachment in this modal & verify this modal closes smoothly before opening the second modal.
  4. Complete each attachment flow (photos, camera, files) one by one & the composer should be focused & keyboard should reopen after completing each flow.
    Note: Keyboard won't open back on mWeb iOS which is known issue & out of scope of this PR
  • Verify that no errors appear in the JS console

Offline tests

Same as above

QA Steps

Same as Tests above

Additional step: Verify the keyboard is focused back on cancelling/exiting out of the add attachment flow at every/any step except for mWeb iOS.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
web.mp4
Mobile Web - Chrome
mWeb.Chrome.mov
Mobile Web - Safari
Mweb.-.Safari.mp4
Desktop
desktop.mp4
iOS
ios.mp4
Android
Android.mov

@priyeshshah11 priyeshshah11 requested a review from a team as a code owner February 22, 2023 01:30
@melvin-bot melvin-bot bot requested review from joelbettner and thesahindia and removed request for a team February 22, 2023 01:30
@MelvinBot
Copy link

@joelbettner @thesahindia One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@@ -613,8 +626,10 @@ class ReportActionCompose extends React.Component {
icon: Expensicons.Paperclip,
text: this.props.translate('reportActionCompose.addAttachment'),
onSelected: () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

@priyeshshah11 we need to add the this.focus(true); inside onClose of PopoverMenu as well, otherwise if the user clicks + and then click outside, it will not focus the input

@priyeshshah11
Copy link
Contributor Author

@tienifr @thesahindia @joelbettner All videos have been uploaded & PR is ready for review

@thesahindia
Copy link
Member

Reviewing in a few hours.

@priyeshshah11
Copy link
Contributor Author

Noting here - We have an edge case bug where we won't get the focus back on mWeb iOS if the user cancels the native file picker as there is no way to detect that in mWeb iOS because the focus never goes away from the screen nor there is any event listener for that https://stackoverflow.com/questions/74861458/how-to-tell-if-user-cancels-html-file-input-on-ios

@thesahindia
Copy link
Member

Hey @priyeshshah11, it looks like the keyboard isn't opening in android when I open actions menu and close it. It works on emulator but not on a real device.

@priyeshshah11
Copy link
Contributor Author

Hey @priyeshshah11, it looks like the keyboard isn't opening in android when I open actions menu and close it. It works on emulator but not on a real device.

Ohh, unfortunately I don't have an android device to debug. @tienifr do you happen to have an android device? I'll still try to figure out by looking at the code, it could be the textInput.blur() that we added to fix the issue that you were seeing on Android, maybe that was just a simulator bug?

@priyeshshah11
Copy link
Contributor Author

@thesahindia Meanwhile for quick testing, could I please request you to test it with commenting this line? Normally I wouldn't ask, sorry 😢

@thesahindia
Copy link
Member

Still no luck

Screenrecording_20230223_222212.mp4

@priyeshshah11
Copy link
Contributor Author

Passing in true here should fix it, testing on other platforms.

@thesahindia
Copy link
Member

It's still the same.

@priyeshshah11
Copy link
Contributor Author

priyeshshah11 commented Feb 23, 2023

Thanks for trying that @thesahindia and yes you're right that wouldn't have made any difference on native as that condition is always false on native and it looks like we are actually doing that on purpose to avoid some jarring UI when keyboard opens on native

@tienifr
Copy link
Contributor

tienifr commented Feb 24, 2023

@thesahindia @priyeshshah11 I don't encounter the Android keyboard not opening issue mentioned above, on my physical Android device

Screenrecorder-2023-02-24-13-17-55-409.mp4

@priyeshshah11
Copy link
Contributor Author

@thesahindia @priyeshshah11 I don't encounter the Android keyboard not opening issue mentioned above, on my physical Android device

@tienifr can you please try in a new chat/empty room like @thesahindia's video?

@tienifr
Copy link
Contributor

tienifr commented Feb 24, 2023

But I suspect the issue on your device might be due to the timeout set here

setTimeout(() => this.textInput.focus(), 100);

It might be too low for some scenarios and it's been increased before (like in here) to make sure the timeout is enough.

@thesahindia can you try increasing that timeout value to see if the issue still persists?

@tienifr
Copy link
Contributor

tienifr commented Feb 24, 2023

@priyeshshah11 I tried again just now on a physical device on the same announce chat which is empty, could not see the issue either.

Screenrecorder-2023-02-24-13-44-29-1.mp4

@priyeshshah11
Copy link
Contributor Author

Thanks @tienifr, Your comment above makes sense & looks like a potential cause.

@thesahindia
Copy link
Member

I tested this on two physical android devices. I had to increase the delay to 250 for one device and 400 for the other and I was still able to repro the issue after 2-3 tries (at chats with long message history). With emoji picker modal I didn't experience any problem, so I think we should check why the same issue can't be reproduced when we close emoji picker modal.

});
}

render() {
return (
<>
<Popover
onClose={this.close}
onClose={() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

@priyeshshah11 We're using onClose here to trigger the focus which will face the same issue on Android where it fails to open the keyboard. We might want to use use onModalHide here instead.

@Julesssss
Copy link
Contributor

Build in progress

@rushatgabhane
Copy link
Member

Keyboard doesn't reopen on Pixel 6.

screen-20230428-234356_2.mp4

@priyeshshah11
Copy link
Contributor Author

Ok so my conclusion is that this is an issue with the react native modal lib where it calls the onModalHide callback too soon for which there are already several issues without any clear solution.
react-native-modal/react-native-modal#534
react-native-modal/react-native-modal#484 (comment) #2719

We have seen this same issue multiple times before in our repo itself, again without any permanent solution. However, @s77rt has this PR open for almost 4 months now without any activity from the maintainers or on the repo itself since almost a year now.

I believe we have 2 options to move forward:

  1. To wait for @s77rt's PR to be merged & holding this PR until then.
  2. Or to proceed with the setTimeout fix just like how we have did in the past due to the dead ends.

cc: @trjExpensify @thesahindia

@thesahindia
Copy link
Member

thesahindia commented May 1, 2023

@rushatgabhane, thanks for the testing!

@thesahindia
Copy link
Member

Ok so my conclusion is that this is an issue with the react native modal lib where it calls the onModalHide callback too soon for which there are already several issues without any clear solution.
react-native-modal/react-native-modal#534
react-native-modal/react-native-modal#484 (comment) #2719

Thanks for the links! I will check this in few hours. After completing other pending tasks

@s77rt
Copy link
Contributor

s77rt commented May 1, 2023

Hi, this PR react-native-modal/react-native-modal#733 is mostly for Web. I don't know if it will fix the bug on Native too.

@priyeshshah11
Copy link
Contributor Author

priyeshshah11 commented May 1, 2023

Hi, this PR react-native-modal/react-native-modal#733 is mostly for Web. I don't know if it will fix the bug on Native too.

any particular reason why it might not if it's kind of the same issue? the solution doesn't really seem platform specific.

I do understand that they all behave differently under the hood but just asking if there are any obvious reasons.

@s77rt
Copy link
Contributor

s77rt commented May 1, 2023

@priyeshshah11 The solution was based on another root cause which is the focus trap that is enforced by <Modal /> on RNW. The solution may or may not fix the bug on Native.

@thesahindia
Copy link
Member

The solution may or may not fix the bug on Native.

@tienifr @priyeshshah11 can you guys check if the fix will solve our problem?

@priyeshshah11
Copy link
Contributor Author

The solution may or may not fix the bug on Native.

@tienifr @priyeshshah11 can you guys check if the fix will solve our problem?

I don't think we will be able to as neither of us can reproduce that bug but I feel it will fix it for native if it fixes for web but I can't say for sure.

@joelbettner
Copy link
Contributor

How are things going here @priyeshshah11 @thesahindia @tienifr?

I don't think we will be able to as neither of us can reproduce that bug but I feel it will fix it for native if it fixes for web but I can't say for sure.

Do we have a solution that we know works for most platforms, but are just unable to test/reproduce it on certain platforms? If that is the case, I think we should go with what we have if we know it fixes it for some platforms. Your thoughts @trjExpensify , @Julesssss?

@priyeshshah11
Copy link
Contributor Author

Do we have a solution that we know works for most platforms, but are just unable to test/reproduce it on certain platforms? If that is the case, I think we should go with what we have if we know it fixes it for some platforms. Your thoughts @trjExpensify , @Julesssss?

@joelbettner The current code of this PR fixes the main bug but causes a minor regression on some android devices where it doesn't open the keyboard after dismissing a modal (it does focus the TextInput but just doesn't open the keyboard). I do have a fix for this, which is to use a setTimeout without any delay for the focus/keyboard opening code and it is not a "workaround".

I know no one likes setTimeout here but this might be a good use case for it. Using setTimeout without any delay means that it executes the callback immediately as soon as the JS thread is not busy or is free. This SO might help understand this better.

Also, I would really like to understand exactly what's wrong in using setTimeout here?

cc: @thesahindia @trjExpensify

@thesahindia
Copy link
Member

Our approach is to address the problem at its core, which is why we refrain from using setTimeout. In this case we are already using setTimeout with a delay of 100ms which isn't working so we should definitely research more into it instead of adding another setTimeout.

I was looking into #9252, it's a similar issue (not the same though), and looks like it will be fixed by #11684. So, I think we should wait for #11684 and test this again.

@priyeshshah11
Copy link
Contributor Author

Our approach is to address the problem at its core, which is why we refrain from using setTimeout. In this case we are already using setTimeout with a delay of 100ms which isn't working so we should definitely research more into it instead of adding another setTimeout.

I understand that & I have already given more than sufficient explanation for the cause & the fix several times above & in the issue. @joelbettner let me know if the explanation above makes sense?

I was looking into #9252, it's a similar issue (not the same though), and looks like it will be fixed by #11684. So, I think we should wait for #11684 and test this again.

well, we have been waiting for far too long on this issue expecting other things to fix it or to try & find the perfect solution even when we have a 99% working solution & we are holding this PR for a seperate known bug that has appeared in several other parts of the app as linked above. I don't think that's fair from a contributor's POV as we don't have unlimited time to be spent on each issue, this should also been taken into consideration.

cc: @trjExpensify

@Julesssss
Copy link
Contributor

Hey @priyeshshah11, apologies but I think we dropped this one. QA tested the build again yesterday but it looks like the PR is out of date. If you can resolve the conflicts I'll generate a new build for QA to verify and we can hopefully get this merged.

@OSBotify
Copy link
Contributor

@joelbettner
Copy link
Contributor

I'm going to close this for now. We can re-open in the future if needed.

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

Successfully merging this pull request may close these issues.

10 participants