Expensify / App

Welcome to New Expensify: a complete re-imagination of financial collaboration, centered around chat. Help us build the next generation of Expensify by sharing feedback and contributing to the code.
https://new.expensify.com
MIT License
3.5k stars 2.85k forks source link

[HOLD for payment 2023-10-13] [$1000] mWeb - LHN - Click and long pressing a user report with lot of messages opens mark as unread popup inside report #25460

Closed izarutskaya closed 12 months ago

izarutskaya commented 1 year ago

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Action Performed:

  1. Open the app in android chrome
  2. Click on user report which has many messages in it
  3. Quickly long press to observe that it displays 'mark as unread' popup inside the report

Expected Result:

App should not display mark as unread popup inside report

Actual Result:

On opening user report with lots of messages and quickly long press, mark as unread popup gets opened inside the report

Workaround:

Unknown

Platforms:

Which of our officially supported platforms is this issue occurring on?

Version Number: v1.3.55-4

Reproducible in staging?: Y

Reproducible in production?: Y

If this was caught during regression testing, add the test name, ID and link from TestRail:

Email or phone of affected tester (no customers):

Logs: https://stackoverflow.com/c/expensify/questions/4856

Notes/Photos/Videos: Any additional supporting documentation

https://github.com/Expensify/App/assets/115492554/ca751249-5f02-4bf5-bba9-c13376b48ef0

https://github.com/Expensify/App/assets/115492554/9fc1cea1-9fe0-4014-b605-2bac67ab14ce

Expensify/Expensify Issue URL:

Issue reported by: @Dhanashree-Sawant

Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1691495713340339

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~01404d8d81934e3635
  • Upwork Job ID: 1693977568321503232
  • Last Price Increase: 2023-08-22
  • Automatic offers:
    • Dhanashree-Sawant | Reporter | 26646326
melvin-bot[bot] commented 1 year ago

Triggered auto assignment to @bfitzexpensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details.

melvin-bot[bot] commented 1 year ago

Bug0 Triage Checklist (Main S/O)

bfitzexpensify commented 1 year ago

OK, let's get some proposals for this one.

melvin-bot[bot] commented 1 year ago

Job added to Upwork: https://www.upwork.com/jobs/~01404d8d81934e3635

melvin-bot[bot] commented 1 year ago

Triggered auto assignment to @muttmuure (External), see https://stackoverflow.com/c/expensify/questions/8582 for more details.

melvin-bot[bot] commented 1 year ago

Triggered auto assignment to Contributor-plus team member for initial proposal review - @sobitneupane (External)

ishpaul777 commented 1 year ago

Proposal

Problem

when navigation is in progress if user long press the report mark as unread" popup opens inside report

What is the root cause of that problem?

The root cause is long press execution is not prevented if the navigation in progess resulting in opening of popover inside report.

What changes do you think we should make in order to solve the problem?

Outdated

In PR we are introducing a new hook `useSingleExecution` we can use the same in [OptionRowLHN](https://github.com/Expensify/App/blob/main/src/components/LHNOptionsList/OptionRowLHN.js) ```diff + const {isExecuting, singleExecution} = useSingleExecution(); + const showPopover = (event) => { + if (isExecuting) { + return; + } ``` ```diff { if (e) { e.preventDefault(); } + singleExecution(() => props.onSelectRow(optionItem, popoverAnchor))(); - props.onSelectRow(optionItem, popoverAnchor); }} ```

2 alternative solutions proposed - https://github.com/Expensify/App/issues/25460#issuecomment-1699553916

anukcr7 commented 1 year ago

Where should I send initial proposal?

melvin-bot[bot] commented 1 year ago

📣 @anukcr7! 📣 Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork. Please follow these steps:

  1. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
  2. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
  3. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details. Screen Shot 2022-11-16 at 4 42 54 PM Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
redpanda-bit commented 1 year ago

Proposal

Please re-state the problem that we are trying to solve in this issue.

Prevent long press action to take place if user navigated to another screen.

What is the root cause of that problem?

The code that shows the pop up is here:

https://github.com/Expensify/App/blob/329cac3b49fc47da8eac1b7a74a511171fe22345/src/components/LHNOptionsList/OptionRowLHN.js#L108-L125

There is no debouce, throttle, or any sort of mutex lock in the function that prevents the pop up to be shown when the screen is already navigating away from LHN.

What changes do you think we should make in order to solve the problem?

The event that occurs after a short press is a navigation to the Report page. Therefore we can use navigation to determine if the user has already short pressed the OptionRow, and if so prevent the pop up to appear despite the long press. This change would use useIsFocused hook from react-navigation or the withNavigationFocus HOC already in the codebase.

Here is the change if using useIsFocused:

On small screen devices, do not show popover if active route is the report screen .

// ...
import {Dimensions} from 'react-native'; // add this - using this instead of withDimensions prevents rerenders of list items when resizing screen
import Navigation from '../../libs/Navigation/Navigation'; // add this
import variables from '../../styles/variables'; // add this

function OptionRowLHN(props) {
    const popoverAnchor = useRef(null);
    const {translate} = useLocalize();

    const showPopover = (event) => {
        // add begin
        const isSmallScreenWidth = Dimensions.get('window').width <= variables.mobileResponsiveWidthBreakpoint;
        const isReportRoute = !!Navigation.getActiveRoute().match(CONST.REPORT_ROUTE_REGEX);
        if (isSmallScreenWidth && isReportRoute) {
            return;
        }
        // add end
        setIsContextMenuActive(true);
        ReportActionContextMenu.showContextMenu(
            ContextMenuActions.CONTEXT_MENU_TYPES.REPORT,
// ...

~A slightly more complex change if the withNavigationFocus HOC is used, including a prop rename since the OptionRowLHN already makes use of an isFocused prop which is different from the usage in this proposal.~

~### However, I would like to know if the changes I am proposing would cause the entire list to be re-rendered when navigating since useIsFocused() is updated when navigation occurs.~

What alternative solutions did you explore? (Optional)

  1. Dismissing the pop up in the next screen, this is undesirable given that it may result in a flicker.
  2. Using a flag in OptioRowLHN timed for the duration of the navigation to prevent further user interactions.

Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.

Anmolverma19 commented 1 year ago

Contributor details Your Expensify account email: annieemporium16@gmail.com Upwork Profile Link: (https://www.upwork.com/freelancers/anmolv3)

melvin-bot[bot] commented 1 year ago

✅ Contributor details stored successfully. Thank you for contributing to Expensify!

Anmolverma19 commented 1 year ago

I have submitted the proposal on Upwork already

redpanda-bit commented 1 year ago

@anukcr7 and @Anmolverma19 information on how to submit proposals is in this document contributing guidelines.

Anmolverma19 commented 1 year ago

Proposal

Please re-state the problem that we are trying to solve in this issue. Prevent long press action to take place if user navigated to another screen.

What is the root cause of that problem? According to me, Root cause lies in how touch and gesture events are handled and how UI/UX elements are hovered or triggered on certain user movement or usage

What changes do you think we should make in order to solve the problem? Here's what I initially plan to do to resolve it: To fix the issue where the "Mark as Unread" option appears when a user performs a long press on the left side of the screen while inside a report, I will need to make changes to the React Native codebase. Below are the steps to address this problem: Step 1: Locate the Code Responsible for the Long Press Gesture

  1. Open the Expensify/App GitHub repository.
  2. Navigate to the specific file or component that handles the long press gesture within a report. This can often be found in the component responsible for rendering reports or in a separate gesture handler component.

Step 2: Identify the Event Handling Logic

  1. Inspect the code to find the event handling logic for the long press gesture on the left side of the screen. Look for code related to touch or gesture recognition that triggers the "Mark as Unread" option.

Step 3: Modify or Remove the Event Handling Logic

  1. Once located the event handling logic, one can either modify it to prevent the "Mark as Unread" option from appearing or remove it entirely, depending on project's requirements.
    • To modify: If there is a condition that checks for the long press event and then displays the "Mark as Unread" option, I can update this condition to prevent the option from appearing when a long press occurs on the left side of the screen.
    • To remove: If the event handling code is unnecessary for the left side of the screen long press, I can delete or comment out the relevant code block.

Step 4: Test Extensively

  1. After making changes, thoroughly test the application on all relevant platforms to ensure that the "Mark as Unread" option no longer appears when doing a long press on the left side of the screen within a report.

Step 5: Document Changes

  1. It's essential to document the changes I will make to the codebase. Update comments or documentation within the code to explain the purpose of my modifications. This documentation will help other developers understand changes in the future.

Step 6: Create a Pull Request

  1. Once confident that code changes have resolved the issue, create a pull request in the Expensify/App GitHub repository to merge the changes into the main codebase. Include a clear description of the problem and my solution in the pull request.

What alternative solutions did you explore? (Optional) Step 1: Identify the UI Element

  1. Inspect the user interface (UI) of the report screen in the application to locate the “Mark as Unread” option or the element that triggers it.

Step 2: Apply CSS or Styling Changes

  1. one can use CSS or styling changes to hide the “Mark as Unread” option when certain conditions are met. Specifically, want to target this option when it appears as a result of a long press on the left side of the screen.
  2. Use CSS to hide the element or set its visibility to “hidden” or “display: none;” within the report screen’s styling.
  3. To target the left side of the screen specifically, may need to apply CSS based on the screen’s layout or structure. For instance, could use CSS classes, element IDs, or parent-child relationships to identify the left side of the screen.

Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.

redpanda-bit commented 1 year ago

Does anyone here know how to run the web app on an android emulator? I'm getting the screen below. Screen Shot 2023-08-22 at 2 28 33 PM

redpanda-bit commented 1 year ago

Here is how it looks with my proposed solution.

https://github.com/Expensify/App/assets/25206487/9f4e9ed3-5a37-404c-8019-581a9a3c0c66

ishpaul777 commented 1 year ago

@carlosalmonte04 Only raise a PR once your proposal is selected by C+ reviewer.

ishpaul777 commented 1 year ago

we are implementing useSingleExecution hook in this PR - https://github.com/Expensify/App/pull/24173, we can use the same in this case once PR is merged

sobitneupane commented 1 year ago

Hello everyone. Thanks for your proposals. I would suggest everyone here to go through existing proposals in other issues (like: https://github.com/Expensify/App/issues/24420) to get idea of expected proposals.

We expect your proposal to include the root cause and link the permalink for the code where issue exists. Also, in your solution you are expected to link the code/file where you are suggesting the change and what sort of change are you suggesting.

Will wait for your updates.

Guideline cc: @carlosalmonte04 @Anmolverma19 @ishpaul777

ishpaul777 commented 1 year ago

The expected behaviour is unclear do we want to disregard the long press if navigation is in progress?

if yes, I guess this issue will be resolved when https://github.com/Expensify/App/pull/24173 is merged thats why i didn't added a proposal. In PR we are introducing a new hook useSingleExecution which is used in PressableWithFeedback, which might resolve the issue incase issue still persist we can use useSingleExecution in OptionRowLHN

+ const {isExecuting, singleExecution} = useSingleExecution();

+ const showPopover = (event) => {
+         if (isExecuting) {
+            return;
+         }
<PressableWithSecondaryInteraction
      ref={popoverAnchor}
      onPress={(e) => {
            if (e) {
               e.preventDefault();
            }
+           singleExecution(() => props.onSelectRow(optionItem, popoverAnchor))();
-            props.onSelectRow(optionItem, popoverAnchor);
}}

cc- @sobitneupane

redpanda-bit commented 1 year ago

Proposal

Updated

redpanda-bit commented 1 year ago

I tested the changes from https://github.com/Expensify/App/pull/24173 and it fixed this bug on my end.

ishpaul777 commented 1 year ago

tested #24173 on physical device this issue is still reproducable

https://github.com/Expensify/App/assets/104348397/d1200157-fae1-44d7-a70a-8978f547c99c

test with changes https://github.com/Expensify/App/issues/25460#issuecomment-1691339233 issue resolved -

https://github.com/Expensify/App/assets/104348397/561a9866-c051-4316-a4e6-769fe2349d9d

ishpaul777 commented 1 year ago

@sobitneupane Updated the comment to a formal propsal, let me know you thoughts on it once you get a chance.

redpanda-bit commented 1 year ago

I take my last comment back, I tested again and the changes from the useSingleExecution PR do not fix this bug on its own. Also, the new useSingleExecution hook changes the flag (isExecuting) too fast. By the time the long press triggers the flag is already false so it proceeds to show the pop up.

redpanda-bit commented 1 year ago

I think useSingleExecution should accept an idleDuration argument and change the flag (isExecuting) to false after the given idleDuration.

sobitneupane commented 1 year ago

Will review the proposals today.

ishpaul777 commented 1 year ago

any updates @sobitneupane ?

sobitneupane commented 1 year ago

Thanks for the proposal everyone.

We have recently implemented a PR introducing a hook for single execution as mentioned by @ishpaul777. I believe we can make use of the hook here as well. Proposal from @ishpaul777 looks good to me.

🎀 👀 🎀 C+ reviewed

melvin-bot[bot] commented 1 year ago

Triggered auto assignment to @Gonals, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

ishpaul777 commented 1 year ago

Hi @sobitneupane, I was testing my proposal on other platflorms and found it does not work as expected and issue still persisted. I came up with other solution to use useFocusEffect hook - https://reactnavigation.org/docs/navigation-lifecycle/

implementation in OptionRowLHN -

const isNavigating = useRef(false)
    useFocusEffect(
        React.useCallback(() => {
           isNavigating.current = false

            return () => {
                isNavigating.current = true
            };
        }, [])
    );
 const showPopover = (event) => {
        if (isNavigating.current) {
            return;
        }

Result -

Before changes https://github.com/Expensify/App/assets/104348397/43e899c2-95f7-4179-bff8-7e181b9a6edc
After changes https://github.com/Expensify/App/assets/104348397/14292cae-7920-49ae-b8e5-6829c3a446e3
redpanda-bit commented 1 year ago

Hey @ishpaul777 that will trigger a re-render on every OptionRowLHN when navigation changes focus. I'd use Navigation.getActiveRoute to compare routes if you must use navigation.

// ...
const showPopover = (event) => {
        const route = Navigation.getActiveRoute();
        if (route !== '/') {
            return;
        }
// ...
ishpaul777 commented 1 year ago

@carlosalmonte04 I don't think there's a rerender of optionRowLHN useFoucussedEffect sets isNavigating to true as soon as navigation starts (screen loose focus) and setIsNavigating to false when screen is focussed, can you link me to docs where it says it rerender component.

redpanda-bit commented 1 year ago

"The set function returned by useState lets you update the state to a different value and trigger a re-render.". Let me know if that helps clear it up, I could be missing something.

ishpaul777 commented 1 year ago

As far as i know, using setState dont have any performance regression in the case it rerenders the part of dom that is concerned with the state. i'll love to know @sobitneupane thoughts here. I have 2 alternative solutions here

  1. https://github.com/Expensify/App/issues/25460#issuecomment-1698131395
  2. handling this the same way we are preventing clicking on multiple LHN reports on mobile screen devices here.
    const showPopover = (event) => {
        if(isSmallScreenWidth && Navigation.getTopmostReportId()) {
            return;
        }
ishpaul777 commented 1 year ago

@sobitneupane @Gonals Can i get a review on updated changes

melvin-bot[bot] commented 1 year ago

@Gonals @sobitneupane @bfitzexpensify @muttmuure this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!

melvin-bot[bot] commented 1 year ago

@Gonals, @sobitneupane, @bfitzexpensify, @muttmuure Whoops! This issue is 2 days overdue. Let's get this updated quick!

sobitneupane commented 1 year ago

@ishpaul777 what were the issue you found with your proposal? In which platform and browser? Can you share the screen recording?

I cannot regularly reproduce the issue. Is there any way to reproduce the issue regularly?

ishpaul777 commented 1 year ago

Using useSingleExecution hook was not working at all. Issue is Easily reproduce able on mweb safari, high traffic account and a report that has a lot of messages.

It also tried using useWaitforNaviagation implemented in the same PR as useSingleExecution like this

singleExecution(() => (
  waitForNavigate(() => {
    props.onSelectRow(optionItem, popoverAnchor);
  })()
))()

The issue still persists I might be missing something here? @sobitneupane

https://github.com/Expensify/App/assets/104348397/7bfac80d-3f0c-4510-a493-2ab75bd013ee

sobitneupane commented 1 year ago

Thanks for the update @ishpaul777. I tested it and it didn't work for me as well. It is supposed to work for same user action. But we are dealing with different user actions (press and long press). So, it should be dealt uniquely.

@ishpaul777 Your proposal is similar to this propsoal.

ishpaul777 commented 1 year ago

I have other working solution too. https://github.com/Expensify/App/issues/25460#issuecomment-1699553916 @sobitneupane

const showPopover = (event) => {
        if(isSmallScreenWidth && Navigation.getTopmostReportId()) {
            return;
        }
ishpaul777 commented 1 year ago

Gentle bump @sobitneupane

sobitneupane commented 1 year ago

We might go with this proposal. I am yet to test it.

sobitneupane commented 1 year ago

I tested the proposal from @redpanda-bit.

However, I would like to know if the changes I am proposing would cause the entire list to be re-rendered when navigating since useIsFocused() is updated when navigation occurs.

It turns out the above suspicion of re-rendering was correct and the entire list re-renders.

redpanda-bit commented 1 year ago

Proposal

Updated

melvin-bot[bot] commented 1 year ago

📣 @redpanda-bit! 📣 Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork. Please follow these steps:

  1. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
  2. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
  3. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details. Screen Shot 2022-11-16 at 4 42 54 PM Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
ishpaul777 commented 1 year ago

~~@redpanda-bit You proposal implemented the useFocusEffect in a wrong way and doesn't solve the issue. I updated my changes https://github.com/Expensify/App/issues/25460#issuecomment-1698131395, Indeed i used your idea to useRef for preventing rerender, but using useFocusEffect was my idea in first place, hope you dont have any objection here?~~

Taking my words back your is correct also, but still i'd say useFocusEffect was my idea in first place and your implementation is still hard to read.

    useFocusEffect(
        React.useCallback(() => {
        // do something on focus screen so better and readable would be to set isFocusedRef/isNavigating to  false
            return () => {
                // do something  on blur screen so better and readable would be to set  isFocusedRef/isNavigating to true
            };
        }, [])
    );
bfitzexpensify commented 1 year ago

@sobitneupane is there a proposal here we can move forward with?