diff --git a/src/components/MessageInput/FileUploadPreview.js b/src/components/MessageInput/FileUploadPreview.js index 1707923e3..1c3a52120 100644 --- a/src/components/MessageInput/FileUploadPreview.js +++ b/src/components/MessageInput/FileUploadPreview.js @@ -7,6 +7,7 @@ import UploadProgressIndicator from './UploadProgressIndicator'; import FileIcon from '../Attachment/FileIcon'; +import closeRound from '../../images/icons/close-round.png'; import { themed } from '../../styles/theme'; import { FileState, ProgressIndicatorTypes } from '../../utils/utils'; @@ -39,8 +40,23 @@ const Container = styled.View` ${({ theme }) => theme.messageInput.fileUploadPreview.container.css}; `; -const DismissText = styled.Text` - ${({ theme }) => theme.messageInput.fileUploadPreview.dismissText.css}; +const Dismiss = styled.TouchableOpacity` + align-items: center; + background-color: #fff; + border-radius: 20px; + height: 20px; + justify-content: center; + position: absolute; + right: 5; + top: 5; + width: 20px; + ${({ theme }) => theme.messageInput.fileUploadPreview.dismiss.css}; +`; + +const DismissImage = styled.Image` + height: 10px; + width: 10px; + ${({ theme }) => theme.messageInput.fileUploadPreview.dismissImage.css}; `; const FilenameText = styled.Text` @@ -72,28 +88,38 @@ const FileUploadPreview = ({ } return ( - (retryUpload ? retryUpload(item.id) : null)} - active={item.state !== FileState.UPLOADED} - type={type} - > - - - - - {item.file.name.length > 35 - ? item.file.name.substring(0, 35).concat('...') - : item.file.name} - - - removeFile(item.id)} - testID='remove-file-upload-preview' - > - X - - - + <> + { + if (retryUpload) { + retryUpload(item.id); + } + }} + active={item.state !== FileState.UPLOADED} + type={type} + > + + + + + {item.file.name.length > 35 + ? item.file.name.substring(0, 35).concat('...') + : item.file.name} + + + + + { + if (removeFile) { + removeFile(item.id); + } + }} + testID='remove-file-upload-preview' + > + + + ); }; diff --git a/src/components/MessageInput/ImageUploadPreview.js b/src/components/MessageInput/ImageUploadPreview.js index 9e0e8b429..72ae22316 100644 --- a/src/components/MessageInput/ImageUploadPreview.js +++ b/src/components/MessageInput/ImageUploadPreview.js @@ -69,7 +69,11 @@ const ImageUploadPreview = ({ imageUploads, removeImage, retryUpload }) => { return ( (retryUpload ? retryUpload(item.id) : null)} + action={() => { + if (retryUpload) { + retryUpload(item.id); + } + }} active={item.state !== FileState.UPLOADED} type={type} > @@ -79,7 +83,11 @@ const ImageUploadPreview = ({ imageUploads, removeImage, retryUpload }) => { /> removeImage(item.id)} + onPress={() => { + if (removeImage) { + removeImage(item.id); + } + }} testID='remove-image-upload-preview' > diff --git a/src/components/MessageInput/MessageInput.js b/src/components/MessageInput/MessageInput.js index f903151ae..f709d4e20 100644 --- a/src/components/MessageInput/MessageInput.js +++ b/src/components/MessageInput/MessageInput.js @@ -8,8 +8,8 @@ import { logChatPromiseExecution } from 'stream-chat'; import ActionSheetAttachmentDefault from './ActionSheetAttachment'; import AttachButtonDefault from './AttachButton'; -import FileUploadPreview from './FileUploadPreview'; -import ImageUploadPreview from './ImageUploadPreview'; +import FileUploadPreviewDefault from './FileUploadPreview'; +import ImageUploadPreviewDefault from './ImageUploadPreview'; import SendButtonDefault from './SendButton'; import { useMessageDetailsForState } from './hooks/useMessageDetailsForState'; @@ -123,8 +123,10 @@ const MessageInput = (props) => { compressImageQuality, doDocUploadRequest, doImageUploadRequest, + FileUploadPreview = FileUploadPreviewDefault, hasFilePicker = true, hasImagePicker = true, + ImageUploadPreview = ImageUploadPreviewDefault, initialValue, Input, maxNumberOfFiles, @@ -870,10 +872,26 @@ MessageInput.propTypes = { * @param channel Current channel object * */ doImageUploadRequest: PropTypes.func, + /** + * Custom UI component for FileUploadPreview. + * Defaults to and accepts same props as: https://github.com/GetStream/stream-chat-react-native/blob/master/src/components/MessageInput/FileUploadPreview.js + */ + FileUploadPreview: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.elementType, + ]), /** If component should have file picker functionality */ hasFilePicker: PropTypes.bool, /** If component should have image picker functionality */ hasImagePicker: PropTypes.bool, + /** + * Custom UI component for ImageUploadPreview. + * Defaults to and accepts same props as: https://github.com/GetStream/stream-chat-react-native/blob/master/src/components/MessageInput/ImageUploadPreview.js + */ + ImageUploadPreview: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.elementType, + ]), /** Initial value to set on input */ initialValue: PropTypes.string, /** Limit on allowed number of files to attach at a time. */ diff --git a/src/components/MessageInput/UploadProgressIndicator.js b/src/components/MessageInput/UploadProgressIndicator.js index 0891a4a0f..1998911e2 100644 --- a/src/components/MessageInput/UploadProgressIndicator.js +++ b/src/components/MessageInput/UploadProgressIndicator.js @@ -1,5 +1,5 @@ import React from 'react'; -import { ActivityIndicator, Image, TouchableOpacity, View } from 'react-native'; +import { ActivityIndicator, Image, View } from 'react-native'; import styled from '@stream-io/styled-components'; import PropTypes from 'prop-types'; @@ -28,44 +28,53 @@ const Overlay = styled.View` ${({ theme }) => theme.messageInput.uploadProgressIndicator.overlay.css}; `; +const ActivityIndicatorContainer = styled.View` + align-items: center; + bottom: 0; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; +`; + +const RetryButtonContainer = styled.TouchableOpacity` + align-items: center; + bottom: 0; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; +`; + const UploadProgressIndicator = ({ action, active, children, type }) => !active ? ( {children} ) : ( - + {children} {type === ProgressIndicatorTypes.IN_PROGRESS && ( - + - + )} {type === ProgressIndicatorTypes.RETRY && ( - + + + )} - + ); UploadProgressIndicator.propTypes = { diff --git a/src/components/MessageInput/__tests__/FileUploadPreview.test.js b/src/components/MessageInput/__tests__/FileUploadPreview.test.js index 1ea8d37ac..0449d007e 100644 --- a/src/components/MessageInput/__tests__/FileUploadPreview.test.js +++ b/src/components/MessageInput/__tests__/FileUploadPreview.test.js @@ -49,13 +49,6 @@ describe('FileUploadPreview', () => { expect(retryUpload).toHaveBeenCalledTimes(0); }); - fireEvent.press(getAllByTestId('active-upload-progress-indicator')[0]); - - await waitFor(() => { - expect(removeFile).toHaveBeenCalledTimes(1); - expect(retryUpload).toHaveBeenCalledTimes(1); - }); - rerender( ({ @@ -170,7 +163,7 @@ describe('FileUploadPreview', () => { expect(retryUpload).toHaveBeenCalledTimes(0); }); - fireEvent.press(getAllByTestId('active-upload-progress-indicator')[0]); + fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); await waitFor(() => { expect(removeFile).toHaveBeenCalledTimes(1); diff --git a/src/components/MessageInput/__tests__/ImageUploadPreview.test.js b/src/components/MessageInput/__tests__/ImageUploadPreview.test.js index 4b9a4df9e..024b68089 100644 --- a/src/components/MessageInput/__tests__/ImageUploadPreview.test.js +++ b/src/components/MessageInput/__tests__/ImageUploadPreview.test.js @@ -49,13 +49,6 @@ describe('ImageUploadPreview', () => { expect(retryUpload).toHaveBeenCalledTimes(0); }); - fireEvent.press(getAllByTestId('active-upload-progress-indicator')[0]); - - await waitFor(() => { - expect(removeImage).toHaveBeenCalledTimes(1); - expect(retryUpload).toHaveBeenCalledTimes(1); - }); - rerender( ({ @@ -170,7 +163,7 @@ describe('ImageUploadPreview', () => { expect(retryUpload).toHaveBeenCalledTimes(0); }); - fireEvent.press(getAllByTestId('active-upload-progress-indicator')[0]); + fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); await waitFor(() => { expect(removeImage).toHaveBeenCalledTimes(1); diff --git a/src/components/MessageInput/__tests__/UploadProgressIndicator.test.js b/src/components/MessageInput/__tests__/UploadProgressIndicator.test.js index f2e9249ca..7dbb7c558 100644 --- a/src/components/MessageInput/__tests__/UploadProgressIndicator.test.js +++ b/src/components/MessageInput/__tests__/UploadProgressIndicator.test.js @@ -55,7 +55,7 @@ describe('UploadProgressIndicator', () => { it('should render an active IN_PROGRESS UploadProgressIndicator', async () => { const action = jest.fn(); - const { getByTestId, queryByTestId, toJSON } = render( + const { queryByTestId, toJSON } = render( { await waitFor(() => { expect(queryByTestId('active-upload-progress-indicator')).toBeTruthy(); expect(queryByTestId('inactive-upload-progress-indicator')).toBeFalsy(); + expect(queryByTestId('upload-progress-indicator')).toBeTruthy(); + expect(queryByTestId('retry-upload-progress-indicator')).toBeFalsy(); expect(action).toHaveBeenCalledTimes(0); }); - fireEvent.press(getByTestId('active-upload-progress-indicator')); - - await waitFor(() => expect(action).toHaveBeenCalledTimes(1)); - const snapshot = toJSON(); await waitFor(() => { @@ -94,10 +92,12 @@ describe('UploadProgressIndicator', () => { await waitFor(() => { expect(queryByTestId('active-upload-progress-indicator')).toBeTruthy(); expect(queryByTestId('inactive-upload-progress-indicator')).toBeFalsy(); + expect(queryByTestId('upload-progress-indicator')).toBeFalsy(); + expect(queryByTestId('retry-upload-progress-indicator')).toBeTruthy(); expect(action).toHaveBeenCalledTimes(0); }); - fireEvent.press(getByTestId('active-upload-progress-indicator')); + fireEvent.press(getByTestId('retry-upload-progress-indicator')); await waitFor(() => expect(action).toHaveBeenCalledTimes(1)); diff --git a/src/components/MessageInput/__tests__/__snapshots__/FileUploadPreview.test.js.snap b/src/components/MessageInput/__tests__/__snapshots__/FileUploadPreview.test.js.snap index b75ce12bd..31106d1fc 100644 --- a/src/components/MessageInput/__tests__/__snapshots__/FileUploadPreview.test.js.snap +++ b/src/components/MessageInput/__tests__/__snapshots__/FileUploadPreview.test.js.snap @@ -78,21 +78,6 @@ exports[`FileUploadPreview should render FileUploadPreview with 1 uploading, 1 u style={null} > - - X - + + + - - X - - - + + + + + - - X - - + > + + + + + @@ -515,21 +608,6 @@ exports[`FileUploadPreview should render FileUploadPreview with all failed files style={null} > - - X - - + > + + - - + + + + + - - X - - + > + + - - + + + + + - - X - + + + + + + + - - + /> @@ -1063,19 +1282,51 @@ exports[`FileUploadPreview should render FileUploadPreview with all uploaded fil dummy.pdf - - X - + + + - - X - + + + - - X - + + + @@ -1316,21 +1631,6 @@ exports[`FileUploadPreview should render FileUploadPreview with all uploading fi style={null} > - - X - - - + + + + + - - X - - - + + + + + - - X - + + + diff --git a/src/components/MessageInput/__tests__/__snapshots__/ImageUploadPreview.test.js.snap b/src/components/MessageInput/__tests__/__snapshots__/ImageUploadPreview.test.js.snap index f535e7db8..f5570ed4b 100644 --- a/src/components/MessageInput/__tests__/__snapshots__/ImageUploadPreview.test.js.snap +++ b/src/components/MessageInput/__tests__/__snapshots__/ImageUploadPreview.test.js.snap @@ -93,21 +93,6 @@ exports[`ImageUploadPreview should render ImageUploadPreview with 1 uploading, 1 } > - + > + + - + > + + - + > + + - + > + + - + > + + `; diff --git a/src/images/reload1.png b/src/images/reload1.png index a42a28ec1..fad57208c 100644 Binary files a/src/images/reload1.png and b/src/images/reload1.png differ diff --git a/src/styles/theme.js b/src/styles/theme.js index 446e5b442..f1319f414 100644 --- a/src/styles/theme.js +++ b/src/styles/theme.js @@ -202,8 +202,10 @@ export const defaultTheme = { attachmentContainerView: {}, attachmentView: {}, container: {}, - dismissText: {}, + dismiss: {}, + dismissImage: {}, filenameText: {}, + itemContainer: {}, }, imageUploadPreview: { container: {}, diff --git a/types/index.d.ts b/types/index.d.ts index 84ca80d3b..af5b729f9 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -264,33 +264,32 @@ export interface MessageInputProps SuggestionsContextValue, TranslationContextValue, StyledComponentProps { - /** Callback that is called when the text input's text changes. Changed text is passed as a single string argument to the callback handler. */ - onChangeText?(newText: string): void; - /** Initial value to set on input */ + ActionSheetAttachment?: React.ElementType; + /** https://github.com/beefe/react-native-actionsheet/blob/master/lib/styles.js */ + actionSheetStyles?: object; + additionalTextInputProps?: object; + AttachButton?: React.ElementType; + AttachmentFileIcon?: React.ElementType; + compressImageQuality?: number; + /** Override file upload request */ + doFileUploadRequest?(file: File): Promise; + /** Override image upload request */ + doImageUploadRequest?(file: File): Promise; + FileUploadPreview?: React.ElementType; + hasFilePicker?: boolean; + hasImagePicker?: boolean; + ImageUploadPreview?: React.ElementType; initialValue?: string; /** The parent message object when replying on a thread */ - parent?: Client.Message | null; - /** The component handling how the input is rendered */ Input?: React.ElementType; - - /** Override image upload request */ - doImageUploadRequest?(file: File): Promise; - - /** Override file upload request */ - doFileUploadRequest?(file: File): Promise; maxNumberOfFiles?: number; - hasImagePicker?: boolean; - hasFilePicker?: boolean; - focus?: boolean; - sendImageAsync?: boolean; - /** https://github.com/beefe/react-native-actionsheet/blob/master/lib/styles.js */ - actionSheetStyles?: object; - AttachmentFileIcon?: React.ElementType; - AttachButton?: React.ElementType; - ActionSheetAttachment?: React.ElementType; + /** Callback that is called when the text input's text changes. Changed text is passed as a single string argument to the callback handler. */ + onChangeText?(newText: string): void; + /** Initial value to set on input */ + parent?: Client.Message | null; SendButton: React.ElementType; - additionalTextInputProps?: object; + sendImageAsync?: boolean; } export interface DocumentPickerFile {