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.43k stars 2.8k forks source link

[HOLD for payment 2024-06-20] [$250] iOS - Chat - The cursor moves one space backward when inserting text after an emoji #42664

Closed kbecciv closed 3 months ago

kbecciv commented 4 months 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!


Version Number: v1.4.76-1 Reproducible in staging?: y Reproducible in production?: y If this was caught during regression testing, add the test name, ID and link from TestRail: https://expensify.testrail.io/index.php?/tests/view/4577924 Issue reported by: Applause - Internal Team

Action Performed:

Pre-requisite: the user must be logged in.

  1. Go to any chat.
  2. Tap on the compose box.
  3. Enter any text and an emoji.
  4. After you have entered the emoji, enter any character again.
  5. Verify the cursor moves one space backward.

Expected Result:

The cursor should remain at the end of the compose box content.

Actual Result:

The cursor moves one space backward when inserting text after an emoji.

Workaround:

n/a

Platforms:

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

Screenshots/Videos

Add any screenshot/video evidence

https://github.com/Expensify/App/assets/93399543/b44491bc-2ce6-45cb-91f4-07e68ab4d95c

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~011bc1604b6420e233
  • Upwork Job ID: 1795296506360057856
  • Last Price Increase: 2024-05-28
  • Automatic offers:
    • eh2077 | Reviewer | 102571526
    • bernhardoj | Contributor | 102571528
Issue OwnerCurrent Issue Owner: @kadiealexander
melvin-bot[bot] commented 4 months ago

Triggered auto assignment to @kadiealexander (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

kbecciv commented 4 months ago

We think that this bug might be related to #vip-vsb

kbecciv commented 4 months ago

@kadiealexander FYI I haven't added the External label as I wasn't 100% sure about this issue. Please take a look and add the label if you agree it's a bug and can be handled by external contributors.

melvin-bot[bot] commented 4 months ago

Job added to Upwork: https://www.upwork.com/jobs/~011bc1604b6420e233

melvin-bot[bot] commented 4 months ago

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

bernhardoj commented 4 months ago

Proposal

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

The cursor selection moves to the left by one character when adding a character after inserting an emoji.

What is the root cause of that problem?

When we add a text to the composer, it will save the new selection position to a ref if the commentValue is not equal to newComment https://github.com/Expensify/App/blob/2514f2988eb26858d30fa501c0040b1f8fc2e239/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx#L439-L444

commentValue is the text that we type while the newComment is the commentValue that has emoji code replaced to emoji character (for example :smile: to πŸ˜„).

If they are different, we want to manually correct the selection, however, on iOS, the selection prop is buggy so we use the ref sync solution that imperatively updates the selection that is introduced in https://github.com/Expensify/App/pull/30835.

It will update/sync the selection in onChangeText.

https://github.com/Expensify/App/blob/2514f2988eb26858d30fa501c0040b1f8fc2e239/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx#L553-L563

However, there are a few things to notice here.

First, when we add an emoji from the emoji picker, commentValue is not equal to newComment even though there is no emoji code converted to an emoji character. The reason behind this is that we add a whitespace to the text

https://github.com/Expensify/App/blob/2514f2988eb26858d30fa501c0040b1f8fc2e239/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx#L417

So, commentValue doesn't have the whitespace while the newComment has the whitespace. Previously, commentValue already contained the whitespace every time we added an emoji from the emoji picker, but this PR changes it so the whitespace is also added when we add the emoji from the device keyboard emoji picker.

This will then set the selection to the ref (syncSelectionWithOnChangeTextRef).

Next, if we add an emoji from the emoji picker, onChangeText won't be triggered. onChangeText will only be triggered if we input the text manually from keyboard.

So, when we add a character, it will run the selection sync logic which contains the old selection.

https://github.com/Expensify/App/blob/2514f2988eb26858d30fa501c0040b1f8fc2e239/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx#L553-L563

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

We need to compare newComment with the commentValue with whitespace appended because the manual selection correction is only used when we add emoji through emoji code.

const commentWithSpaceInserted = isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue;
...
if (commentWithSpaceInserted !== newComment) {
...

What alternative solutions did you explore? (Optional)

Clear the ref every time we call updateComment. This is to invalidate any old selection sync ref. https://github.com/Expensify/App/blob/2514f2988eb26858d30fa501c0040b1f8fc2e239/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx#L408-L411

const updateComment = useCallback(
    (commentValue: string, shouldDebounceSaveComment?: boolean) => {
        if (isIOSNative) {
            syncSelectionWithOnChangeTextRef.current = null;
        }
kaushiktd commented 4 months ago

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

iOS - Chat - The cursor moves one space backward when inserting text after an emoji

What is the root cause of that problem?

When inputting text into the composer, the system saves the new selection position to a reference (ref) if the commentValue doesn't match the newComment. This disparity occurs due to the presence of an extra whitespace added to newComment during emoji insertion, which is absent in commentValue.

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

Instead of comparing commentValue and newComment, directly track the insertion point to determine cursor positioning. Calculate the newCursorPosition dynamically based on the difference in length between the old and new comments.

https://github.com/Expensify/App/blob/2514f2988eb26858d30fa501c0040b1f8fc2e239/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx#L439-L444

// Calculate the new cursor position based on the difference in text length
const currentLength = commentRef.current.length;
const newLength = newCommentConverted.length;
let newCursorPosition = cursorPosition ?? 0;

// Determine if an emoji was inserted and adjust cursor position accordingly
if (isEmojiInserted && newLength > currentLength) {
    newCursorPosition = endIndex + 1; // Move the cursor after the inserted emoji
} else if (currentLength > newLength) {
    newCursorPosition = Math.max(selection.end - (currentLength - newLength), 0); // Adjust cursor for deleted characters
} else {
    newCursorPosition = selection.end + (newLength - currentLength); // Adjust cursor for added characters
}

// Update the selection position and clear the ref if necessary
setSelection({ start: newCursorPosition, end: newCursorPosition });
if (isIOSNative) {
    syncSelectionWithOnChangeTextRef.current = { position: newCursorPosition, value: newComment };
}

This approach ensures accurate cursor positioning by directly tracking insertion points and dynamically adjusting cursor positions, thereby resolving the cursor issue caused by indirect comparisons and ensuring correct placement after inserting emojis or additional text.

video:-https://drive.google.com/file/d/19PDhzOkps6aXYRAWwyeWr3qyrc_xXzAP/view?usp=sharing

eh2077 commented 4 months ago

@bernhardoj 's proposal looks good to me. Nice write up to unwrap the root cause! I also agree with the idea of fixing it by narrowing the scope of the manual-selection-correction-specific code.

πŸŽ€πŸ‘€πŸŽ€ C+ reviewed

melvin-bot[bot] commented 4 months ago

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

eh2077 commented 4 months ago

@MonilBhavsar Friendly bump, wdyt https://github.com/Expensify/App/issues/42664#issuecomment-2137553685

MonilBhavsar commented 4 months ago

Yes, looking at this...

MonilBhavsar commented 4 months ago

@bernhardoj proposal's looks good and simple. Is there any specific reason to clear syncSelectionWithOnChangeTextRef?

melvin-bot[bot] commented 4 months ago

πŸ“£ @eh2077 πŸŽ‰ An offer has been automatically sent to your Upwork account for the Reviewer role πŸŽ‰ Thanks for contributing to the Expensify app!

Offer link Upwork job

melvin-bot[bot] commented 4 months ago

πŸ“£ @bernhardoj πŸŽ‰ An offer has been automatically sent to your Upwork account for the Contributor role πŸŽ‰ Thanks for contributing to the Expensify app!

Offer link Upwork job Please accept the offer and leave a comment on the Github issue letting us know when we can expect a PR to be ready for review πŸ§‘β€πŸ’» Keep in mind: Code of Conduct | Contributing πŸ“–

bernhardoj commented 4 months ago

Is there any specific reason to clear syncSelectionWithOnChangeTextRef?

Do you mean the alternative solution? syncSelectionWithOnChangeTextRef is supposed to be consumed (in onChangeText) every time we type something (updateComment). But as explained in my proposal, onChangeText will only be called when we type from the keyboard, not from adding an emoji from the emoji picker. So, I suggest an alternative solution to always clear it before creating a new syncSelectionWithOnChangeTextRef so each syncSelectionWithOnChangeTextRef corresponds to each updateComment.

Btw, after retesting my main solution, it causes a regression, the cursor is before the emoji when adding an emoji

https://github.com/Expensify/App/assets/50919443/110ec416-3e5c-4739-8b18-5eb769c352ed

so I use the alternative instead.

PR is ready.

cc: @eh2077

MonilBhavsar commented 4 months ago

Makes sense, thanks!

melvin-bot[bot] commented 3 months ago

Reviewing label has been removed, please complete the "BugZero Checklist".

melvin-bot[bot] commented 3 months ago

The solution for this issue has been :rocket: deployed to production :rocket: in version 1.4.82-4 and is now subject to a 7-day regression period :calendar:. Here is the list of pull requests that resolve this issue:

If no regressions arise, payment will be issued on 2024-06-20. :confetti_ball:

For reference, here are some details about the assignees on this issue:

melvin-bot[bot] commented 3 months ago

BugZero Checklist: The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed:

eh2077 commented 3 months ago

Checklist

Regression test

  1. Open any chat
  2. Type anything
  3. Add an emoji from emoji picker
  4. Type a character
  5. Verify the cursor/selection stays at its place

Do we agree πŸ‘ or πŸ‘Ž

kadiealexander commented 3 months ago

Payouts due: