Skip to content

Commit 455571d

Browse files
Merge pull request #229 from GetStream/feature/async-image-upload
Feature/async image upload
2 parents b97097a + 896655d commit 455571d

File tree

2 files changed

+131
-47
lines changed

2 files changed

+131
-47
lines changed

examples/NativeMessaging/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,4 @@ SPEC CHECKSUMS:
208208

209209
PODFILE CHECKSUM: 065a6a4c98f1b2dac9e014ba39d9e216c45e149f
210210

211-
COCOAPODS: 1.8.4
211+
COCOAPODS: 1.9.3

src/components/MessageInput.js

Lines changed: 130 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,12 @@ class MessageInput extends PureComponent {
128128
props.editing,
129129
props.initialValue,
130130
);
131-
this.state = { ...state, sending: false };
131+
this.state = {
132+
...state,
133+
asyncIds: Immutable([]), // saves data for images that resolve after hitting send
134+
asyncUploads: Immutable({}), // saves data for images that resolve after hitting send
135+
};
136+
this.sending = false;
132137
}
133138

134139
static themePath = 'messageInput';
@@ -223,12 +228,17 @@ class MessageInput extends PureComponent {
223228
]),
224229
/** Disables the child MessageInput component */
225230
disabled: PropTypes.bool,
231+
/**
232+
* For images still in uploading state when user hits send, send text immediately and send image as follow-up message once uploaded
233+
*/
234+
sendImageAsync: PropTypes.bool,
226235
};
227236

228237
static defaultProps = {
229238
hasImagePicker: true,
230239
hasFilePicker: true,
231240
disabled: false,
241+
sendImageAsync: false,
232242
SendButton,
233243
AttachButton,
234244
};
@@ -328,6 +338,16 @@ class MessageInput extends PureComponent {
328338
}
329339

330340
componentDidUpdate(prevProps) {
341+
if (Object.keys(this.state.asyncUploads).length) {
342+
/**
343+
* When successful image upload response occurs after hitting send,
344+
* send a follow up message with the image
345+
*/
346+
this.sending = true;
347+
this.state.asyncIds.forEach((id) => this.sendMessageAsync(id));
348+
this.sending = false;
349+
}
350+
331351
if (this.props.editing) this.inputBox.focus();
332352
if (
333353
this.props.editing &&
@@ -405,29 +425,78 @@ class MessageInput extends PureComponent {
405425
return false;
406426
};
407427

428+
sendMessageAsync = (id) => {
429+
const image = this.state.asyncUploads[id];
430+
if (!image || image.state === FileState.UPLOAD_FAILED) return;
431+
432+
if (image.state === FileState.UPLOADED) {
433+
const attachments = [
434+
{
435+
type: 'image',
436+
image_url: image.url,
437+
},
438+
];
439+
440+
try {
441+
this.props.sendMessage({
442+
text: '',
443+
parent: this.props.parent,
444+
mentioned_users: [],
445+
attachments,
446+
});
447+
448+
this.setState((prevState) => ({
449+
numberOfUploads: prevState.numberOfUploads - 1,
450+
asyncIds: prevState.asyncIds.splice(
451+
prevState.asyncIds.indexOf(id),
452+
1,
453+
),
454+
asyncUploads: prevState.asyncUploads.without([id]),
455+
}));
456+
} catch (err) {
457+
console.log('Failed');
458+
}
459+
}
460+
};
461+
408462
sendMessage = async () => {
409-
if (this.state.sending) return;
463+
if (this.sending) return;
464+
this.sending = true;
410465

411466
const { text } = this.state;
412-
await this.setState({ sending: true, text: '' }, () =>
413-
this.inputBox.clear(),
414-
);
467+
await this.setState({ text: '' }, () => this.inputBox.clear());
415468

416469
const attachments = [];
417470
for (const id of this.state.imageOrder) {
418471
const image = this.state.imageUploads[id];
472+
419473
if (!image || image.state === FileState.UPLOAD_FAILED) {
420474
continue;
421475
}
476+
422477
if (image.state === FileState.UPLOADING) {
423478
// TODO: show error to user that they should wait until image is uploaded
424-
return this.setState({ sending: false });
479+
if (this.props.sendImageAsync) {
480+
/**
481+
* If user hit send before image uploaded, push ID into a queue to later
482+
* be matched with the successful CDN response
483+
*/
484+
this.setState((prevState) => ({
485+
asyncIds: [...prevState.asyncIds, id],
486+
}));
487+
} else {
488+
this.sending = false;
489+
return this.setState({ text });
490+
}
491+
}
492+
493+
if (image.state === FileState.UPLOADED) {
494+
attachments.push({
495+
type: 'image',
496+
image_url: image.url,
497+
fallback: image.file.name,
498+
});
425499
}
426-
attachments.push({
427-
type: 'image',
428-
image_url: image.url,
429-
fallback: image.file.name,
430-
});
431500
}
432501

433502
for (const id of this.state.fileOrder) {
@@ -437,20 +506,22 @@ class MessageInput extends PureComponent {
437506
}
438507
if (upload.state === FileState.UPLOADING) {
439508
// TODO: show error to user that they should wait until image is uploaded
440-
return this.setState({ sending: false });
509+
return (this.sending = false);
510+
}
511+
if (upload.state === FileState.UPLOADED) {
512+
attachments.push({
513+
type: 'file',
514+
asset_url: upload.url,
515+
title: upload.file.name,
516+
mime_type: upload.file.type,
517+
file_size: upload.file.size,
518+
});
441519
}
442-
attachments.push({
443-
type: 'file',
444-
asset_url: upload.url,
445-
title: upload.file.name,
446-
mime_type: upload.file.type,
447-
file_size: upload.file.size,
448-
});
449520
}
450521

451522
// Disallow sending message if its empty.
452523
if (!text && attachments.length === 0) {
453-
return this.setState({ sending: false });
524+
return (this.sending = false);
454525
}
455526

456527
if (this.props.editing) {
@@ -469,7 +540,7 @@ class MessageInput extends PureComponent {
469540
.then(this.props.clearEditingState);
470541
logChatPromiseExecution(updateMessagePromise, 'update message');
471542

472-
this.setState({ sending: false });
543+
this.sending = false;
473544
} else {
474545
try {
475546
this.props.sendMessage({
@@ -479,17 +550,19 @@ class MessageInput extends PureComponent {
479550
attachments,
480551
});
481552

482-
this.setState({
553+
this.sending = false;
554+
this.setState((prevState) => ({
483555
text: '',
484556
imageUploads: Immutable({}),
485557
imageOrder: Immutable([]),
486558
fileUploads: Immutable({}),
487559
fileOrder: Immutable([]),
488560
mentioned_users: [],
489-
sending: false,
490-
});
561+
numberOfUploads: prevState.numberOfUploads - attachments.length,
562+
}));
491563
} catch (err) {
492-
this.setState({ sending: false, text });
564+
this.sending = false;
565+
this.setState({ text });
493566
console.log('Failed');
494567
}
495568
}
@@ -669,21 +742,12 @@ class MessageInput extends PureComponent {
669742
};
670743

671744
_uploadImage = async (id) => {
672-
const img = this.state.imageUploads[id];
673-
if (!img) {
745+
const { file } = this.state.imageUploads[id];
746+
if (!file) {
674747
return;
675748
}
676-
const { file } = img;
677749

678-
await this.setState((prevState) => ({
679-
imageUploads: prevState.imageUploads.setIn(
680-
[id, 'state'],
681-
FileState.UPLOADING,
682-
),
683-
}));
684-
685-
let response = {};
686-
response = {};
750+
let response;
687751

688752
const filename = (file.name || file.uri).replace(
689753
/^(file:\/\/|content:\/\/)/,
@@ -697,13 +761,40 @@ class MessageInput extends PureComponent {
697761
file,
698762
this.props.channel,
699763
);
764+
} else if (this.props.sendImageAsync) {
765+
this.props.channel
766+
.sendImage(file.uri, null, contentType)
767+
.then((res) => {
768+
if (this.state.asyncIds.includes(id)) {
769+
// Evaluates to true if user hit send before image successfully uploaded
770+
this.setState((prevState) => ({
771+
asyncUploads: prevState.asyncUploads
772+
.setIn([id, 'state'], FileState.UPLOADED)
773+
.setIn([id, 'url'], res.file),
774+
}));
775+
} else {
776+
this.setState((prevState) => ({
777+
imageUploads: prevState.imageUploads
778+
.setIn([id, 'state'], FileState.UPLOADED)
779+
.setIn([id, 'url'], res.file),
780+
}));
781+
}
782+
});
700783
} else {
701784
response = await this.props.channel.sendImage(
702785
file.uri,
703786
null,
704787
contentType,
705788
);
706789
}
790+
791+
if (response) {
792+
this.setState((prevState) => ({
793+
imageUploads: prevState.imageUploads
794+
.setIn([id, 'state'], FileState.UPLOADED)
795+
.setIn([id, 'url'], response.file),
796+
}));
797+
}
707798
} catch (e) {
708799
console.warn(e);
709800
await this.setState((prevState) => {
@@ -725,15 +816,10 @@ class MessageInput extends PureComponent {
725816

726817
return;
727818
}
728-
this.setState((prevState) => ({
729-
imageUploads: prevState.imageUploads
730-
.setIn([id, 'state'], FileState.UPLOADED)
731-
.setIn([id, 'url'], response.file),
732-
}));
733819
};
734820

735821
onChangeText = (text) => {
736-
if (this.state.sending) return;
822+
if (this.sending) return;
737823
this.setState({ text });
738824

739825
if (text) {
@@ -913,9 +999,7 @@ class MessageInput extends PureComponent {
913999
title={t('Send message')}
9141000
sendMessage={this.sendMessage}
9151001
editing={this.props.editing}
916-
disabled={
917-
disabled || this.state.sending || !this.isValidMessage()
918-
}
1002+
disabled={disabled || this.sending || !this.isValidMessage()}
9191003
/>
9201004
</>
9211005
)}

0 commit comments

Comments
 (0)