-
Notifications
You must be signed in to change notification settings - Fork 3k
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
[$1000] Android - Copy / Paste / Cut menu is not displayed when selecting text in compose box - Reported by: @parasharrajat #6876
Comments
Triggered auto assignment to @neil-marcellini ( |
Triggered auto assignment to @Christinadobrzyn ( |
Upwork job created! Internal: https://www.upwork.com/ab/applicants/1473877014903103488/job-details Hired @parasharrajat for reporting this. Waiting on proposals for the fix. |
Triggered auto assignment to Contributor-plus team member for initial proposal review - @parasharrajat ( |
Triggered auto assignment to @stitesExpensify ( |
React native's TextInput re-renders when its selection property is controlled. There is an open issue (facebook/react-native#29063) about this. I'm not sure how convenient is this but we did it for ios, so I'm proposing to omit selection prop at render() {
const propsToPass = _.omit(this.props, 'selection');
return (
<TextInput
placeholderTextColor={themeColors.placeholderText}
ref={el => this.textInput = el}
maxHeight={CONST.COMPOSER_MAX_HEIGHT}
rejectResponderTermination={false}
editable={!this.props.isDisabled}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...propsToPass}
/>
);
} |
Will it affect our app's functionality? @kursat |
@parasharrajat, sorry to say but yes it will. At first glance I noticed this: When we replace a selected text with an emoji, we will not be able to move cursor at the end of emoji App/src/pages/home/report/ReportActionCompose.js Lines 418 to 433 in d6a6073
I tried to replace it like below, but setting selection to a js object like this, fixes position of cursor and you literally write backwards after than that. this.textInput.setNativeProps({
selection: {
start: this.state.selection.start + emoji.length,
end: this.state.selection.start + emoji.length,
},
}); We are also passing selection prop from a couple files, but for now I am not sure about the impact yet. I'll work on this one more day and see if I can eliminate all possible side effects. |
Any update @kursat |
Hey @parasharrajat, I was dealing some personal problems, I totally forgot about this. So sorry about the absurd delay. The only component that sets selection is ReportActionCompose. To keep selection setting feature we can manage selection state in src/components/TextInputFocusable/index.android.js: class TextInputFocusable extends React.Component {
constructor(props) {
super(props);
this.state = {
selection: null,
};
}
// ...
setSelection = (selection) => {
this.setState({
selection,
});
}
render() {
const propsToPass = _.omit(this.props, 'selection');
return (
<TextInput
placeholderTextColor={themeColors.placeholderText}
ref={el => this.textInput = el}
maxHeight={CONST.COMPOSER_MAX_HEIGHT}
rejectResponderTermination={false}
editable={!this.props.isDisabled}
selection={this.state.selection}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...propsToPass}
/>
);
}
}
// ...
export default React.forwardRef((props, ref) => (
/* eslint-disable-next-line react/jsx-props-no-spreading */
<TextInputFocusable {...props} ref={props.ownRef} forwardedRef={ref} />
)); src/pages/home/report/ReportActionCompose.js: constructor(props) {
super(props);
// ...
this.setTextInputFocusableRef = this.setTextInputFocusableRef.bind(this);
}
setTextInputFocusableRef(el) {
this.textInputFocusable = el;
}
addEmojiToTextBox(emoji, emojiObject) {
// ...
if (this.textInputFocusable) {
this.textInputFocusable.setSelection({
start: this.state.selection.start + emoji.length,
end: this.state.selection.start + emoji.length,
});
}
// ...
}
render() {
<TextInputFocusable
autoFocus={this.shouldFocusInputOnScreenFocus || _.size(this.props.reportActions) === 1}
multiline
ref={this.setTextInputRef}
ownRef={this.setTextInputFocusableRef}
{ /* ... */ }
/>
// ...
}
} This way we don't brake anything. |
Great thanks. I see. If we are able to migrate from selection prop to |
Happy to help! You can experiment on this fork if it helps: https://github.com/kursat/App/tree/proposal-6876 |
Would like to clarify some things about this proposal to be sure I understand. Is this a good description of the problem and solution?
If so, this seems like a workaround and ideally, we would get it fixed at the But also wondering if we can move the logic so that a re-render is forced when the props change as a side effect instead of programmatically triggering them with Would something like this work in componentDidUpdate(prevProps) {
if (_.isEqual(prevProps.selection, this.props.selection)) {
return;
}
this.setState({selection: this.props.selection});
} |
I was thinking that it could be a good opportunity for us to drop the selection prop which makes it controlled. We only require setting the selection when we want to place the emoji so if we can do it directly via API then I think that is better. I have seen a few side effects. Sometimes cursor jump between positions when we move the cursor placement manually on Android. I believe its the selection prop. |
Got it. Just to make sure I understand the point of view, why is this better?
Are there bug reports for these with reproductions? Is there a known connection between passing a |
Using selection prop will make it controlled and if use API it will remain uncontrolled. I think that would be better performance-wise.
I haven't reported it yet. I will do that.
It is just a hunch. I haven't debugged this yet. Due to the jump that happens when we move the cursor and cursor positioning is directly related to selection, I believe that is the cause. I will report it and then we can get more eyes on it. Update: I can't reproduce the same on PROD. It may be some glitch on dev. So please ignore above. |
Oh right, because the same can be observed with a controlled |
Raised the Upwork job price to $500. It looks like we're still reviewing proposals correct @parasharrajat? |
Added |
I've submitted a proposal some time ago, but a contributor was already working on the ticket, maybe you can consider it now: #6876 (comment) The PR from the slack thread: #7815 |
@parasharrajat , can you review @kidroca 's proposal and PR above? |
I will @mallenexpensify. Reviewing others ATM. |
@kidroca has a clean start but there are a few problems that we faced during implementation.
Also, PR is outdated. |
Yes, the change solves the issue on all platforms Making This change is covering web/desktop:
We can move the web/desktop specific change here: https://github.com/Expensify/App/blob/main/src/components/TextInput/index.js Instead of passing a Everything depends on what we want to cover
|
Thanks for the explanation. I retested your PR and it has issues on IOS which I was expecting. The cursor is placed at the end of Text whenever we add an emoji. Another issue is that when you select a bunch of text on Android and replace it with an emoji, the selection is changed abruptly covering other text on the editor.
This is what we are looking to solve on this issue for now. |
Sorry, I guess I've only tested popping the context menu on ios and not inserting an emoji. Tested it again and added a fix to preserve caret position after inserting an emoji, by using the interaction manager
addEmojiToTextBox(emoji) {
const newComment = this.comment.slice(0, this.currentSelection.start)
+ emoji + this.comment.slice(this.currentSelection.end, this.comment.length);
this.updateComment(newComment);
const caret = this.currentSelection.start + emoji.length;
InteractionManager.runAfterInteractions(() => {
this.textInput.setNativeProps({selection: {start: caret, end: caret}});
});
if (this.textInput.setSelectionRange) {
this.textInput.setSelectionRange(caret, caret);
}
} Screen.Recording.2022-03-24.at.20.24.19.movSeeing that we have access to |
Did you test it on Android as well? I think it will have some problems there too. in the previous PR we have to loose the selection after immediately setting it. |
Yeah there's an issue on Android: Here's a summary:
InteractionManager.runAfterInteractions(() => {
this.textInput.setNativeProps({selection: {start: caret, end: caret}});
global.requestAnimationFrame(() => this.textInput.setNativeProps({selection: null}));
}); But setting In light of this, Seeing that we have Web/Desktop: setSelection(start, end) {
this.textInput.setSelectionRange(start, end);
} iOS: setSelection(start, end) {
InteractionManager.runAfterInteractions(() => {
this.textInput.setNativeProps({selection: {start, end});
});
} Android: setSelection(start, end) {
InteractionManager.runAfterInteractions(() => {
this.textInput.setNativeProps({selection: {start, end});
global.requestAnimationFrame(() => this.textInput.setNativeProps({selection: null}));
});
} And in addEmojiToTextBox(emoji) {
const newComment = this.comment.slice(0, this.currentSelection.start)
+ emoji + this.comment.slice(this.currentSelection.end, this.comment.length);
const caret = this.currentSelection.start + emoji.length;
this.updateComment(newComment);
this.textInput.setSelection(caret, caret);
} |
Alternatively we can find/submit a bug report to |
@stitesExpensify so we kind of ended with the same solution as in the previous PR. I think that there are a few bugs in RN. RN suggests using controlled props to prevent cursor issues but on Android, the menu is not opening. so my suggestion would be we just file a report on RN for the native Copy-paste menu and keep the same composer implementation. What do you say? |
Yep, a fix to RN is always preferable over a workaround in our codebase. We can file a report, but also I believe that if a contributor creates a PR to fix RN we still pay out the upwork issue even though it's not in our codebase. Is that correct @mallenexpensify ? |
Controlled The reason we're not setting Selection.re-render.mp4You can also observe how words sometime get selected while you type - that's another side effect of controlled selection A problem with controlled props in general is that a component doesn't know we're echoing back it's own value. And echoing can be delayed resulting in bugs like selecting previous words while you type It's seems, on Android, setting |
How do we decide that? App/src/pages/home/report/ReportActionCompose.js Lines 272 to 276 in c04fca7
What I'm suggesting is similar, it also uses the What I'm proposing is a fix of about 30 line changes, that also addresses the re-render per keystroke issue vs finding/waiting for someone to identify the issue in Android and submit a fix to react-native We can apply my fix, and all we have to do would be to remove one line - |
I have noticed that controlled props perform more badly on dev builds than PROD. I think ReactActionComposer is also lacking optimizations. A single render takes a bit of time. If optimize this. We can get better results. it has many things to render
Also, inline Children is making it worse.
|
I'm taking that from our design philosophy here Section 5.2
I think the main difference is that the maintainer of that package is not willing to update it whereas in this case the problem is that the platforms are not in sync because of react native itself. I think that in general, we should avoid workarounds. IMO any solution that involves a timeout or similar mechanism is not ideal, including the code that you linked. |
Thanks for the answers @stitesExpensify, sounds fair As far as I see there's no way to address this issue on the javascript side then. We should make that clear when summoning other people to solve the problem, or at least let them know what have been tried so far, so they don't waste as much time ending on the same crossroad Expensify has a fork of |
Great, sounds good. Do you think that we should keep this issue open, or create a new one specifying the solution should be in the fork? |
I lean toward creating a new ticket and summarizing what have been found out here |
Missed this last week
Yes, if someone fixes a bug anywhere that helps with the issue, they'll be compensated. If we close this issue and open another, lets make sure to keep @parasharrajat on as C+. |
@kidroca can you create the new issue, count the time as part of your hourly work, include a link back to this issue and cc all active participants in this convo in the OP of the new issue? (I'll assign myself as CM and others once done) |
Yes, I'll post back when ready |
Created a new ticket of the same name here: #8349 |
Thanks @kidroca , should we close this one now? |
Closing, doubled price of the other one |
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:
Expected Result:
Native menu to cut / copy / paste should be displayed
Actual Result:
Native menu to cut / copy / paste is displayed for a second and then it's hidden
Workaround:
User is unable to cut / copy / paste text in the compose box.
Platform:
Where is this issue occurring?
Version Number: 1.1.23-0
Reproducible in staging?: yes
Reproducible in production?: yes
Logs: https://stackoverflow.com/c/expensify/questions/4856
Notes/Photos/Videos: Any additional supporting documentation
WhatsApp.Video.2021-12-22.at.11.37.59.AM.mp4
Expensify/Expensify Issue URL:
Issue reported by: @parasharrajat
Slack conversation: https://expensify.slack.com/archives/C01GTK53T8Q/p1640179428463500
View all open jobs on GitHub
The text was updated successfully, but these errors were encountered: