Closed roryabraham closed 4 days ago
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ahmedGaber93 (External
)
Triggered auto assignment to @sonialiap (NewFeature
), see https://stackoverflowteams.com/c/expensify/questions/14418#:~:text=BugZero%20process%20steps%20for%20feature%20requests for more details. Please add this Feature request to a GH project, as outlined in the SO.
:warning: It looks like this issue is labelled as a New Feature but not tied to any GitHub Project. Keep in mind that all new features should be tied to GitHub Projects in order to properly track external CAP software time :warning:
Triggered auto assignment to Design team member for new feature review - @dubielzyk-expensify (NewFeature
)
I don't think we need any design review here
Add images copy/paste in iOS.
New feature.
Add a new option "Paste image" in the + menu of chats, which will show only when there's an image copied in the clipboard.
Clicking on this option will open the attachment modal with the image.
const [imageToBePasted, setImageToBePasted] = useState('')
useEffect(() => {
Clipboard.getString().then((data) => {
if (/\.(jpg|png|jpeg|svg)(\?.*)?$/.test(data)) {
setImageToBePasted(data);
}
});
})
Like we have displayFileInModal, we'll add displayFileInModalUsingUrl
which will have url
of the copied image as input.
New option in attachment picker:
if (imageToBePasted) {
menuItems.push({
icon: Expensicons.Paperclip,
text: 'Paste image',
onSelected: () => {
if (Browser.isSafari()) {
return;
}
displayFileInModalUsingUrl(imageToBePasted)
}})
}
https://github.com/Expensify/App/assets/35566748/c89ba805-db4e-4aba-8b85-45c0a2347eb6
Note that the above code is not extensive, I had to do more changes locally to make it work.
Love this ticket. As for the solution, I kinda find it weird to trigger the "paste" permission on (+)
click. Does this only work when you have copied a picture? What happens if you copy text?
In other apps, and even for Expensify Web and MacOS, you can paste a picture into the actual compose bar text input which to me feels more natural than to click the (+)
button. Thoughts? cc @Expensify/design
Does this only work when you have copied a picture? What happens if you copy text?
This option would only show when we have an image URL in the clipboard.
"paste" permission
That is needed because we want to get the clipboard content.
you can paste a picture into the actual compose bar text input
That might not be feasible in iOS. Maybe we can modify the above shown flow instead?
π€ Also, what about Android to maintain cross-platform consistency given @Beamanator's recent discovery here on Rory's OG issue for this:
I did figure out that on an android device, you can go to chrome, search for an image, press & hold, and copy to the clipboard -> so shouldn't we make sure this is possible on our android app too? π
100% agree, I find it super bizarre to override the default +
behavior here. We should definitely not do that.
Why can't you just paste into the compose box as if you were pasting text, and then that triggers the paste dialog?
When someone adds an image link in the input box, we can open the attachment modal with the image. But what if the user just wants to send the link itself? Should we show a modal with the option to add the image as an attachment or just keep it as a link?
Well if a link was on your clipboard, I would think you would just paste the link. But if an image was on your clipboard, then it makes sense to trigger the modal window.
When I check the clipboard content after copying an image on iOS, it just shows the link.
@ShridharGoel Thanks for the proposal.
I think pasting the image from the URL in the clipboard seem a workaround, because when we copied the image, we click "copy image" not "copy image address", so we may need to handle the image as fileObject or base64 string.
Also, your proposal doesn't have how we will detect onPasteFile
on the normal case by long click on the input then click paste.
The paste to compose bar is how most apps on iOS works. It works like this on Messages, Telegram, and Signal. So I suggest we do that. Like Shawn said, let's trigger the modal window like we do on web/mac os.
The paste to compose bar is how most apps on iOS works. It works like this on Messages, Telegram, and Signal. So I suggest we do that. Like Shawn said, let's trigger the modal window like we do on web/mac os.
Totally agree.
Yes, paste should appear on the native context menu that appears when you double-tap on the composer input.
@sonialiap, @ahmedGaber93, @roryabraham Whoops! This issue is 2 days overdue. Let's get this updated quick!
@sonialiap, @ahmedGaber93, @roryabraham Still overdue 6 days?! Let's take care of this!
@roryabraham should this issue be external or internal? Are we looking for contributors to build this or is thing something you're working on?
It should be external - not sure why it appears no upwork job has been created...
Job added to Upwork: https://www.upwork.com/jobs/~0195bbb40518505764
Current assignee @ahmedGaber93 is eligible for the External assigner, not assigning anyone new.
Upwork job price has been updated to $500
bumping to $500 to try and get some fresh proposals
Waiting on proposals
Waiting on proposals
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
With our high focus on reliability over new features, this is kind of taking a back-seat. However, I'd say this is reasonably high priority still because it has a high impact on UX, particularly on iOS for P2P / chat uses.
So I'm going to bump the bounty.
Upwork job price has been updated to $1000
Hi @roryabraham , we had solid discussions and POC from an old issue which was closed without moving forward at that time.
I think we can continue with previous discussions and bring the feature described in this issue to the App. That said, I'm happy to continue work on it with @kacper-mikolajczak if possible.
Cc @kacper-mikolajczak @mountiny
Add support for copy/pasting images on iOS
Create new feature in IOS
We can add a new option in the attachment picker, maybe inside the moneyRequestOptions like:
if (Platform.OS === 'ios') {
const newOptions = {
[CONST.IOU.TYPE.SUBMIT]: {
icon: Expensicons.Copy,
text: 'Paste image',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onSelected: () => checkClipboardForImage(),
},
};
options = {
...options,
...newOptions,
};
}
Then we should install two new components/libraries (react-native-fs and react-native-clipboard-plus), these are needed to make the copy and pasted in Native Platforms.
To handle the paste method we can create the next method:
const checkClipboardForImage = async () => {
if (Platform.OS !== 'ios') {
return;
}
const clipboardContent = await ClipboardPlus.paste();
if (clipboardContent) {
if (clipboardContent.image) {
const imageBase64 = `data:image/png;base64,${clipboardContent.image.toString()}`;
const imageFile = await FileUtilsIOS.base64ToFileNative(imageBase64, 'image.png');
displayFileInModal(imageFile, imageBase64);
}
}
};
This last one use a new library created by us called FileUtilsIOS (see draft PR for details).
Finally we shoud modify the validateAndDisplayFileToUpload method to accept the base64Image like:
const validateAndDisplayFileToUpload = useCallback(
(data: FileObject, imageBase64 = '') => {
if (!data || !isDirectoryCheck(data)) {
return;
}
let fileObject = data;
if ('getAsFile' in data && typeof data.getAsFile === 'function') {
fileObject = data.getAsFile();
}
if (!fileObject) {
return;
}
if (!isValidFile(fileObject)) {
return;
}
if (imageBase64) {
setIsModalOpen(true);
setSourceState(imageBase64);
setFile(fileObject);
setModalType('centered_unswipeable');
} else if (fileObject instanceof File) {
.
.
.//same code
So this will be the result:
https://github.com/Expensify/App/assets/5216036/b1442da2-be1d-496f-9f82-26a6f2b98f32
Add support for copy/pasting images on iOS
New feature: Copy and paste images on iOS.
When copy image on webview content has copied to clipboard as base64. When pasting image from clipboard we should handle paste content has image data and convert it to file then call props.onPasteFile at Composer/index.native.tsx
same solution with Composer/index.tsx but different implementation for native, we don't need install new package for this feature.
At RNMarkdownTextInput for native we will add handleTextChange
function to handle paste image if content has base64ImageRegex
else keep current behavior
const handleTextChange = useCallback(async (text: string) => {
console.log('handleTextChange', text);
const base64ImageRegex = /data:image\/(?:jpeg|png|gif|svg\+xml|bmp|webp|x-icon);base64,([A-Za-z0-9+/=]+)/g;
const match = text.match(base64ImageRegex);
if(!match) {
console.log('No base64 string found');
props.onChangeText?.(text);
return;
}
// Step 1: Remove base64-encoded image data from the text
const textWithoutImages = text.replace(base64ImageRegex, '');
// Step 2: Extract removed base64 data
const extractedBase64Data = text.match(base64ImageRegex);
// Step 3: Assign the extracted base64 data to a variable
const image = extractedBase64Data?.[0] as string; // Assuming there is only one image in the text
console.log({textWithoutImages}); // Output the text without images
console.log(image); // Output the extracted base64 data of the image
const fileType = parseImageFileType(image) as string;
const fileName = `${Date.now()}.${fileType}`;
console.log({fileType, fileName});
const file = await convertBase64ToFile(image, fileName, fileType);
console.log({file});
props.onPasteFile?.(file);
}, []);
POC
https://github.com/Expensify/App/assets/11959869/c1bc74d0-ab1b-412d-8b20-12e7ed385f0a
User copy image from share button in this case format is blob so we can check Clipboard.hasImage
then request Clipboard.getImage
and keep imageRef prepare for paste image when user tap paste button.
We have some strategy to handle paste blob image
Option 1. when user longpress to input we can get imageRef and set clipboard message ST like EXP_IMAGE
to trigger show native paste when user tap paste button if content has EXP_IMAGE
trigger process the same use-case 1
const handleTextChange = useCallback(async (_text: string) => {
let text = _text;
const base64ImageRegex = /data:image\/(?:jpeg|png|gif|svg\+xml|bmp|webp|x-icon);base64,([A-Za-z0-9+/=]+)/g;
const match = text.match(base64ImageRegex);
if(!match) {
+ if(!text.includes('EXP_IMAGE')) {
console.log('No base64 string found');
props.onChangeText?.(text);
return;
}
console.log('Found base64 string:', imageRef.slice(0, 100));
text = text.replace('EXP_IMAGE', imageRef);
console.log(text);
}
........
......
}, []);
const [isPressing, setIsPressing] = useState(false);
let longPressTimer: NodeJS.Timeout | null = null;
const handleLongPress = async (e) => {
e.persist();
longPressTimer = setTimeout(async () => {
setIsPressing(true);
console.log('Long press detected!');
const hasImage = await Clipboard.hasImage();
console.log({hasImage});
if(hasImage) {
Clipboard.getImageJPG().then((image) => {
console.log({ image: image.slice(0, 100)});
imageRef = image;
});
Clipboard.setString(`EXP_IMAGE`);
}
const hasString = await Clipboard.hasString();
if(hasString) {
const text = await Clipboard.getString();
console.log({text});
}
}, 500);
};
const handlePressOut = (e) => {
e.persist();
if (isPressing) {
setIsPressing(false);
clearTimeout(longPressTimer as NodeJS.Timeout);
}
};
return (
<RNMarkdownTextInput
.....
onPressIn={handleLongPress}
onPressOut={handlePressOut}
onChangeText={handleTextChange}
POC
https://github.com/Expensify/App/assets/11959869/e35cc9f3-2d46-4c39-8a1f-4922ad60c512
Option 2. when user focus to input if Clipboard.hasImage
we will show a button paste image (or confirm dialog) when press to paste image trigger flow Clipboard.getImageJPG
=> convertBase64ToFile
=> onPasteFile
Due to react-native-live-markdown
has @implementation RCTBaseTextInputView so we can update RCTBaseTextInputView+Markdown.mm
to implement handlePasteImage
and introduce new method onPasteImage
for TextInput.
After that update Composer/index.native.tsx
to handle onPasteImage
event and call props.onPasteFile
with image file.
return (
<RNMarkdownTextInput
.....
onPressIn={handleLongPress}
onPressOut={handlePressOut}
onChangeText={handleTextChange}
+ onPasteImage={props.onPasteFile}
@samilabud Thanks for the proposal.
We can add a new option in the attachment picker, maybe inside the moneyRequestOptions like:
I think we don't need this behavior, we need to handle the normal case when long/double click on the input then click paste. Also, react-native-clipboard-plus seem low popularity and maintenance
@suneox Thanks for the proposal.
I see your proposal try to handle base64 and JPG images, did you test it in other formats? And can you share a test branch and the formats you tested it?
I see your proposal try to handle base64 and JPG images, did you test it in other formats?
@ahmedGaber93 We have a limitation on package @react-native-clipboard/clipboard
only support JPG
and PNG
on ios so we can create another issue to handle another mime type on upstream or use expo-clipboard
And can you share a test branch and the formats you tested it?
I'll clean up code and provide tested branch tomorrow
Hi @ahmedGaber93,
we need to handle the normal case when long/double click on the input then click paste
As far I can see in React Native the TextInput component does not directly support pasting images, I mean there are not event that we can use to handle it at least the pasted content be a text/string.
Please let me know if this makes sense to you, if so I'll change my proposal using another library to handle the clipboard in IOS that has more popularity.
Please let me know if this makes sense to you
@samilabud it recommended by design team here https://github.com/Expensify/App/issues/41239#issuecomment-2084636648 to paste it in composer directly
I see your proposal try to handle base64 and JPG images, did you test it in other formats? And can you share a test branch and the formats you tested it?
@ahmedGaber93 Here is a tested branch when the proposal works I can patch library to support another image type at Clipboard.getImage
function and cleanup redundant code. I also add the alternative solution for my proposal
@suneox related to this problem and your fix for it
When user copy image from iPhone photos app, and try to paste it in composer, the paste option will not be available, and the only option available was AutoFill.
listen to composer onPressIn
and check if Clipboard.hasImage(), and if it has, catch it as PNG in base64 by Clipboard.getImagePNG() and save it in react.ref. Use Clipboard.setString(EXP_IMAGE)
to copy random text EXP_IMAGE
to clipboard which make the paste option appear.
listen to composer onChangeText
and when user paste, if the text includes the copied text EXP_IMAGE
that's mean he pasted image. Use image ref and handle paste file.
calling getImagePNG
on onPressIn
will trigger iOS to request paste permission, and this seems weird because the user just press on the composer not click paste item yet.
If we try to paste JPG image from iPhone photos app it will work, but from Google images it not works and instead pasted link
Also, I am wonder why hasImage
and getImagePNG
are not documented on Clipboard lib docs.
@suneox for the alternative solution, what is the test result of it? for example, and not as a limitation
calling getImagePNG on onPressIn will trigger iOS to request paste permission, and this seems weird because the user just press on the composer not click paste item yet.
We can update to use NSTextAttachment to avoid request permission when focus in
If we try to paste JPG image from iPhone photos app it will work, but from Google images it not works and instead pasted link
It based on image type and ios version when copy image from web browser in my proposal has introduced 2 cases
https://github.com/Expensify/App/assets/11959869/5eea6fda-e05b-47e3-9c34-df3787cd9b35
In this case the IOS copy the URL is expected result
https://github.com/Expensify/App/assets/11959869/f6330d84-dd57-4cba-9a97-d546062ff2ab
Also, I am wonder why hasImage and getImagePNG are not documented on Clipboard lib docs.
I think the library is missing update document
@suneox for the alternative solution, what is the test result of it? for example, and not as a limitation
I will update feed back asap
In this case the IOS copy the URL is expected result
I also thought that, but when I tested it on Messages app, it works fine.
https://github.com/Expensify/App/assets/41129870/04b77ba3-3c49-42d4-8200-1fd00d0dd581
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
@suneox for the alternative solution, what is the test result of it? for example, and not as a limitation
@ahmedGaber93 I have wrap up prototype at this branch and i'd like to confirm on my test
[x] confirm paste option is appeared when you copy image from local (Photo app)
[x] confirm you are able to paste image from local (Photo app)
[x] confirm you are able to paste image from Google images/web
[x] confirm you are able to paste base64 images (handle on my main proposal)
Formats we can handle
[X] png
[x] jpg/jpeg
[x] gif
[ ] tiff (will be support as soon)
POC
https://github.com/Expensify/App/assets/11959869/dc040874-7316-43b3-8e6f-9c5f7287fa23
Not overdue, It's the weekend.
@suneox I still can't paste image from web, see video here https://github.com/Expensify/App/issues/41239#issuecomment-2138673462
@suneox I still can't paste image from web, see video here #41239 (comment)
Ah i see we have to handle this case with image type is com.apple.uikit.image
slack proposal: https://expensify.slack.com/archives/C01GTK53T8Q/p1714239125412939
Feature Request: Add support for pasting images on iOS.
Problem
One of the most common flows for sharing images on iOS is to copy the image somewhere and paste it somewhere else, and NewDot does not support that flow. For example, I wanted to share an image I found on Google in New Expensify. The first thing I did was copy the image to my clipboard, but when I long-pressed on the composer to try and paste that image, the only option available was AutoFill. This left me with two more less desirable options:
This was a very basic flow that did not work very well at all, and it was a frustrating experience. It's table-stakes for any chat app.
Solution
Implement support for pasting images on iOS.
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @Issue Owner
Current Issue Owner: @sonialiap