diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 51ed4935cc..a2cf19c3fe 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -161,6 +161,7 @@ import { ReactionListBottom as ReactionListBottomDefault } from '../Message/Mess import { ReactionListTop as ReactionListTopDefault } from '../Message/MessageSimple/ReactionList/ReactionListTop'; import { StreamingMessageView as DefaultStreamingMessageView } from '../Message/MessageSimple/StreamingMessageView'; import { AttachButton as AttachButtonDefault } from '../MessageInput/AttachButton'; +import { AttachmentUploadPreviewList as AttachmentUploadPreviewDefault } from '../MessageInput/AttachmentUploadPreviewList'; import { CommandsButton as CommandsButtonDefault } from '../MessageInput/CommandsButton'; import { AttachmentUploadProgressIndicator as AttachmentUploadProgressIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; import { AudioAttachmentUploadPreview as AudioAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview'; @@ -176,8 +177,6 @@ import { CommandInput as CommandInputDefault } from '../MessageInput/components/ import { InputEditingStateHeader as InputEditingStateHeaderDefault } from '../MessageInput/components/InputEditingStateHeader'; import { InputReplyStateHeader as InputReplyStateHeaderDefault } from '../MessageInput/components/InputReplyStateHeader'; import { CooldownTimer as CooldownTimerDefault } from '../MessageInput/CooldownTimer'; -import { FileUploadPreview as FileUploadPreviewDefault } from '../MessageInput/FileUploadPreview'; -import { ImageUploadPreview as ImageUploadPreviewDefault } from '../MessageInput/ImageUploadPreview'; import { InputButtons as InputButtonsDefault } from '../MessageInput/InputButtons'; import { MoreOptionsButton as MoreOptionsButtonDefault } from '../MessageInput/MoreOptionsButton'; import { SendButton as SendButtonDefault } from '../MessageInput/SendButton'; @@ -526,6 +525,7 @@ const ChannelWithContext = (props: PropsWithChildren) = AttachmentPickerError = DefaultAttachmentPickerError, AttachmentPickerErrorImage = DefaultAttachmentPickerErrorImage, AttachmentPickerIOSSelectMorePhotos = DefaultAttachmentPickerIOSSelectMorePhotos, + AttachmentUploadPreviewList = AttachmentUploadPreviewDefault, ImageOverlaySelectedComponent = DefaultImageOverlaySelectedComponent, attachmentPickerErrorButtonText, attachmentPickerErrorText, @@ -567,7 +567,6 @@ const ChannelWithContext = (props: PropsWithChildren) = FileAttachmentUploadPreview = FileAttachmentUploadPreviewDefault, FileAttachmentGroup = FileAttachmentGroupDefault, FileAttachmentIcon = FileIconDefault, - FileUploadPreview = FileUploadPreviewDefault, FlatList = NativeHandlers.FlatList, forceAlignMessages, Gallery = GalleryDefault, @@ -599,7 +598,6 @@ const ChannelWithContext = (props: PropsWithChildren) = ImageLoadingFailedIndicator = ImageLoadingFailedIndicatorDefault, ImageLoadingIndicator = ImageLoadingIndicatorDefault, ImageReloadIndicator = ImageReloadIndicatorDefault, - ImageUploadPreview = ImageUploadPreviewDefault, initialScrollToFirstUnreadMessage = false, InlineDateSeparator = InlineDateSeparatorDefault, InlineUnreadIndicator = InlineUnreadIndicatorDefault, @@ -1741,6 +1739,7 @@ const ChannelWithContext = (props: PropsWithChildren) = attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AttachmentUploadProgressIndicator, AudioAttachmentUploadPreview, AudioRecorder, @@ -1764,7 +1763,6 @@ const ChannelWithContext = (props: PropsWithChildren) = editMessage, FileAttachmentUploadPreview, FileSelectorIcon, - FileUploadPreview, handleAttachButtonPress, hasCameraPicker, hasCommands: hasCommands ?? !!clientChannelConfig?.commands?.length, @@ -1772,7 +1770,6 @@ const ChannelWithContext = (props: PropsWithChildren) = hasImagePicker, ImageAttachmentUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, InputButtons, InputEditingStateHeader, diff --git a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts index 9f2c83e38b..247117f34a 100644 --- a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts +++ b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts @@ -14,6 +14,7 @@ export const useCreateInputMessageInputContext = ({ attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AttachmentUploadProgressIndicator, AudioAttachmentUploadPreview, AudioRecorder, @@ -37,7 +38,6 @@ export const useCreateInputMessageInputContext = ({ editMessage, FileAttachmentUploadPreview, FileSelectorIcon, - FileUploadPreview, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -45,7 +45,6 @@ export const useCreateInputMessageInputContext = ({ hasImagePicker, ImageAttachmentUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, InputButtons, InputEditingStateHeader, @@ -81,6 +80,7 @@ export const useCreateInputMessageInputContext = ({ attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AttachmentUploadProgressIndicator, AudioAttachmentUploadPreview, AudioRecorder, @@ -103,7 +103,6 @@ export const useCreateInputMessageInputContext = ({ editMessage, FileAttachmentUploadPreview, FileSelectorIcon, - FileUploadPreview, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -111,7 +110,6 @@ export const useCreateInputMessageInputContext = ({ hasImagePicker, ImageAttachmentUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, InputButtons, InputEditingStateHeader, diff --git a/package/src/components/MessageInput/AttachmentUploadPreviewList.tsx b/package/src/components/MessageInput/AttachmentUploadPreviewList.tsx new file mode 100644 index 0000000000..9fc1f2e6e3 --- /dev/null +++ b/package/src/components/MessageInput/AttachmentUploadPreviewList.tsx @@ -0,0 +1,274 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FlatList, LayoutChangeEvent, StyleSheet, View } from 'react-native'; + +import { + isLocalAudioAttachment, + isLocalFileAttachment, + isLocalImageAttachment, + isLocalVoiceRecordingAttachment, + isVideoAttachment, + LocalAttachment, + LocalImageAttachment, +} from 'stream-chat'; + +import { useAudioPreviewManager } from './hooks/useAudioPreviewManager'; + +import { useMessageComposer } from '../../contexts'; +import { useAttachmentManagerState } from '../../contexts/messageInputContext/hooks/useAttachmentManagerState'; +import { + MessageInputContextValue, + useMessageInputContext, +} from '../../contexts/messageInputContext/MessageInputContext'; +import { useTheme } from '../../contexts/themeContext/ThemeContext'; +import { isSoundPackageAvailable } from '../../native'; + +const IMAGE_PREVIEW_SIZE = 100; +const FILE_PREVIEW_HEIGHT = 60; + +export type AttachmentUploadPreviewListPropsWithContext = Pick< + MessageInputContextValue, + | 'AudioAttachmentUploadPreview' + | 'FileAttachmentUploadPreview' + | 'ImageAttachmentUploadPreview' + | 'VideoAttachmentUploadPreview' +>; + +/** + * AttachmentUploadPreviewList + * UI Component to preview the files set for upload + */ +const UnMemoizedAttachmentUploadListPreview = ( + props: AttachmentUploadPreviewListPropsWithContext, +) => { + const [flatListWidth, setFlatListWidth] = useState(0); + const flatListRef = useRef | null>(null); + const { + AudioAttachmentUploadPreview, + FileAttachmentUploadPreview, + ImageAttachmentUploadPreview, + VideoAttachmentUploadPreview, + } = props; + const { attachmentManager } = useMessageComposer(); + const { attachments } = useAttachmentManagerState(); + const { + theme: { + colors: { grey_whisper }, + messageInput: { + attachmentSeparator, + attachmentUploadPreviewList: { filesFlatList, imagesFlatList, wrapper }, + }, + }, + } = useTheme(); + + const imageUploads = attachments.filter((attachment) => isLocalImageAttachment(attachment)); + const fileUploads = useMemo(() => { + return attachments.filter((attachment) => !isLocalImageAttachment(attachment)); + }, [attachments]); + const audioUploads = useMemo(() => { + return fileUploads.filter( + (attachment) => + isLocalAudioAttachment(attachment) || isLocalVoiceRecordingAttachment(attachment), + ); + }, [fileUploads]); + + const { audioAttachmentsStateMap, onLoad, onProgress, onPlayPause } = + useAudioPreviewManager(audioUploads); + + const renderImageItem = useCallback( + ({ item }: { item: LocalImageAttachment }) => { + return ( + + ); + }, + [ + ImageAttachmentUploadPreview, + attachmentManager.removeAttachments, + attachmentManager.uploadAttachment, + ], + ); + + const renderFileItem = useCallback( + ({ item }: { item: LocalAttachment }) => { + if (isLocalImageAttachment(item)) { + // This is already handled in the `renderImageItem` above, so we return null here to avoid duplication. + return null; + } else if (isLocalVoiceRecordingAttachment(item)) { + return ( + + ); + } else if (isLocalAudioAttachment(item)) { + if (isSoundPackageAvailable()) { + return ( + + ); + } else { + return ( + + ); + } + } else if (isVideoAttachment(item)) { + return ( + + ); + } else if (isLocalFileAttachment(item)) { + return ( + + ); + } else return null; + }, + [ + AudioAttachmentUploadPreview, + FileAttachmentUploadPreview, + VideoAttachmentUploadPreview, + attachmentManager.removeAttachments, + attachmentManager.uploadAttachment, + audioAttachmentsStateMap, + flatListWidth, + onLoad, + onPlayPause, + onProgress, + ], + ); + + useEffect(() => { + if (fileUploads.length && flatListRef.current) { + setTimeout(() => flatListRef.current?.scrollToEnd(), 1); + } + }, [fileUploads.length]); + + const onLayout = useCallback( + (event: LayoutChangeEvent) => { + if (flatListRef.current) { + setFlatListWidth(event.nativeEvent.layout.width); + } + }, + [flatListRef], + ); + + if (!attachments.length) { + return null; + } + + return ( + + {imageUploads.length ? ( + ({ + index, + length: IMAGE_PREVIEW_SIZE + 8, + offset: (IMAGE_PREVIEW_SIZE + 8) * index, + })} + horizontal + keyExtractor={(item) => item.localMetadata.id} + renderItem={renderImageItem} + style={[styles.imagesFlatList, imagesFlatList]} + /> + ) : null} + {imageUploads.length && fileUploads.length ? ( + + ) : null} + {fileUploads.length ? ( + ({ + index, + length: FILE_PREVIEW_HEIGHT + 8, + offset: (FILE_PREVIEW_HEIGHT + 8) * index, + })} + keyExtractor={(item) => item.localMetadata.id} + onLayout={onLayout} + ref={flatListRef} + renderItem={renderFileItem} + style={[styles.filesFlatList, filesFlatList]} + testID={'file-upload-preview'} + /> + ) : null} + + ); +}; + +export type AttachmentUploadPreviewListProps = Partial; + +const MemoizedAttachmentUploadPreviewListWithContext = React.memo( + UnMemoizedAttachmentUploadListPreview, +); + +/** + * AttachmentUploadPreviewList + * UI Component to preview the files set for upload + */ +export const AttachmentUploadPreviewList = (props: AttachmentUploadPreviewListProps) => { + const { + AudioAttachmentUploadPreview, + FileAttachmentUploadPreview, + ImageAttachmentUploadPreview, + VideoAttachmentUploadPreview, + } = useMessageInputContext(); + return ( + + ); +}; + +const styles = StyleSheet.create({ + attachmentSeparator: { + borderBottomWidth: 1, + marginBottom: 10, + }, + filesFlatList: { marginBottom: 12, maxHeight: FILE_PREVIEW_HEIGHT * 2.5 + 16 }, + imagesFlatList: { paddingBottom: 12 }, +}); + +AttachmentUploadPreviewList.displayName = + 'AttachmentUploadPreviewList{messageInput{attachmentUploadPreviewList}}'; diff --git a/package/src/components/MessageInput/FileUploadPreview.tsx b/package/src/components/MessageInput/FileUploadPreview.tsx deleted file mode 100644 index 0b9737bdac..0000000000 --- a/package/src/components/MessageInput/FileUploadPreview.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { FlatList, LayoutChangeEvent, StyleSheet } from 'react-native'; - -import { - isAudioAttachment, - isLocalAudioAttachment, - isLocalFileAttachment, - isLocalImageAttachment, - isLocalVideoAttachment, - isLocalVoiceRecordingAttachment, - isVideoAttachment, - isVoiceRecordingAttachment, - LocalAudioAttachment, - LocalFileAttachment, - LocalVideoAttachment, - LocalVoiceRecordingAttachment, -} from 'stream-chat'; - -import { useMessageComposer } from '../../contexts'; -import { useAttachmentManagerState } from '../../contexts/messageInputContext/hooks/useAttachmentManagerState'; -import { - MessageInputContextValue, - useMessageInputContext, -} from '../../contexts/messageInputContext/MessageInputContext'; -import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { isSoundPackageAvailable } from '../../native'; -import { AudioConfig } from '../../types/types'; - -const FILE_PREVIEW_HEIGHT = 60; - -export type FileUploadPreviewPropsWithContext = Pick< - MessageInputContextValue, - 'AudioAttachmentUploadPreview' | 'FileAttachmentUploadPreview' | 'VideoAttachmentUploadPreview' ->; - -type FileAttachmentType> = - | LocalFileAttachment - | LocalAudioAttachment - | LocalVoiceRecordingAttachment - | LocalVideoAttachment; - -/** - * FileUploadPreview - * UI Component to preview the files set for upload - */ -const UnMemoizedFileUploadPreview = (props: FileUploadPreviewPropsWithContext) => { - const { - AudioAttachmentUploadPreview, - FileAttachmentUploadPreview, - VideoAttachmentUploadPreview, - } = props; - const { attachmentManager } = useMessageComposer(); - const { attachments } = useAttachmentManagerState(); - const [audioAttachmentsStateMap, setAudioAttachmentsStateMap] = useState< - Record - >({}); - const flatListRef = useRef | null>(null); - const [flatListWidth, setFlatListWidth] = useState(0); - - const fileUploads = useMemo(() => { - return attachments.filter( - (attachment) => - isLocalFileAttachment(attachment) || - isLocalAudioAttachment(attachment) || - isLocalVoiceRecordingAttachment(attachment) || - isLocalVideoAttachment(attachment), - ); - }, [attachments]); - - useEffect(() => { - const newAudioAttachmentsStateMap = fileUploads.reduce( - (acc, attachment) => { - if (isAudioAttachment(attachment) || isVoiceRecordingAttachment(attachment)) { - acc[attachment.localMetadata.id] = { - duration: - attachment.duration || - audioAttachmentsStateMap[attachment.localMetadata.id]?.duration || - 0, - paused: true, - progress: 0, - }; - } - return acc; - }, - {} as Record, - ); - - setAudioAttachmentsStateMap(newAudioAttachmentsStateMap); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fileUploads]); - - // Handler triggered when an audio is loaded in the message input. The initial state is defined for the audio here - // and the duration is set. - const onLoad = useCallback((index: string, duration: number) => { - setAudioAttachmentsStateMap((prevState) => ({ - ...prevState, - [index]: { - ...prevState[index], - duration, - }, - })); - }, []); - - // The handler which is triggered when the audio progresses/ the thumb is dragged in the progress control. The - // progressed duration is set here. - const onProgress = useCallback((index: string, progress: number) => { - setAudioAttachmentsStateMap((prevState) => ({ - ...prevState, - [index]: { - ...prevState[index], - progress, - }, - })); - }, []); - - // The handler which controls or sets the paused/played state of the audio. - const onPlayPause = useCallback((index: string, pausedStatus?: boolean) => { - if (pausedStatus === false) { - // In this case, all others except the index are set to paused. - setAudioAttachmentsStateMap((prevState) => { - const newState = { ...prevState }; - Object.keys(newState).forEach((key) => { - if (key !== index) { - newState[key].paused = true; - } - }); - return { - ...newState, - [index]: { - ...newState[index], - paused: false, - }, - }; - }); - } else { - setAudioAttachmentsStateMap((prevState) => ({ - ...prevState, - [index]: { - ...prevState[index], - paused: true, - }, - })); - } - }, []); - - const { - theme: { - messageInput: { - fileUploadPreview: { flatList }, - }, - }, - } = useTheme(); - - const renderItem = useCallback( - ({ item }: { item: FileAttachmentType }) => { - if (isLocalImageAttachment(item)) { - // This is already handled in the `ImageUploadPreview` component - return null; - } else if (isLocalVoiceRecordingAttachment(item)) { - return ( - - ); - } else if (isLocalAudioAttachment(item)) { - if (isSoundPackageAvailable()) { - return ( - - ); - } else { - return ( - - ); - } - } else if (isVideoAttachment(item)) { - return ( - - ); - } else if (isLocalFileAttachment(item)) { - return ( - - ); - } else return null; - }, - [ - AudioAttachmentUploadPreview, - FileAttachmentUploadPreview, - VideoAttachmentUploadPreview, - attachmentManager.removeAttachments, - attachmentManager.uploadAttachment, - audioAttachmentsStateMap, - flatListWidth, - onLoad, - onPlayPause, - onProgress, - ], - ); - - useEffect(() => { - if (fileUploads.length && flatListRef.current) { - setTimeout(() => flatListRef.current?.scrollToEnd(), 1); - } - }, [fileUploads.length]); - - const onLayout = useCallback( - (event: LayoutChangeEvent) => { - if (flatListRef.current) { - setFlatListWidth(event.nativeEvent.layout.width); - } - }, - [flatListRef], - ); - - if (fileUploads.length === 0) { - return null; - } - - return ( - ({ - index, - length: FILE_PREVIEW_HEIGHT + 8, - offset: (FILE_PREVIEW_HEIGHT + 8) * index, - })} - keyExtractor={(item) => item.localMetadata.id} - onLayout={onLayout} - ref={flatListRef} - renderItem={renderItem} - style={[styles.flatList, flatList]} - testID={'file-upload-preview'} - /> - ); -}; - -export type FileUploadPreviewProps = Partial; - -const MemoizedFileUploadPreviewWithContext = React.memo(UnMemoizedFileUploadPreview); - -/** - * FileUploadPreview - * UI Component to preview the files set for upload - */ -export const FileUploadPreview = (props: FileUploadPreviewProps) => { - const { - AudioAttachmentUploadPreview, - FileAttachmentUploadPreview, - VideoAttachmentUploadPreview, - } = useMessageInputContext(); - return ( - - ); -}; - -const styles = StyleSheet.create({ - flatList: { marginBottom: 12, maxHeight: FILE_PREVIEW_HEIGHT * 2.5 + 16 }, -}); - -FileUploadPreview.displayName = 'FileUploadPreview{messageInput{fileUploadPreview}}'; diff --git a/package/src/components/MessageInput/ImageUploadPreview.tsx b/package/src/components/MessageInput/ImageUploadPreview.tsx deleted file mode 100644 index 607013d73e..0000000000 --- a/package/src/components/MessageInput/ImageUploadPreview.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useCallback } from 'react'; -import { FlatList, StyleSheet } from 'react-native'; - -import { isLocalImageAttachment, LocalImageAttachment } from 'stream-chat'; - -import { - MessageInputContextValue, - useMessageComposer, - useMessageInputContext, -} from '../../contexts'; -import { useAttachmentManagerState } from '../../contexts/messageInputContext/hooks/useAttachmentManagerState'; -import { useTheme } from '../../contexts/themeContext/ThemeContext'; - -const IMAGE_PREVIEW_SIZE = 100; - -export type ImageUploadPreviewPropsWithContext = Pick< - MessageInputContextValue, - 'ImageAttachmentUploadPreview' ->; - -export type ImageAttachmentPreview> = - LocalImageAttachment; - -type ImageUploadPreviewItem = { index: number; item: ImageAttachmentPreview }; - -/** - * UI Component to preview the images set for upload - */ -const UnmemoizedImageUploadPreview = (props: ImageUploadPreviewPropsWithContext) => { - const { ImageAttachmentUploadPreview } = props; - const { attachmentManager } = useMessageComposer(); - const { attachments } = useAttachmentManagerState(); - - const imageUploads = attachments.filter((attachment) => isLocalImageAttachment(attachment)); - - const { - theme: { - messageInput: { - imageUploadPreview: { flatList }, - }, - }, - } = useTheme(); - - const renderItem = useCallback( - ({ item }: ImageUploadPreviewItem) => { - return ( - - ); - }, - [ - ImageAttachmentUploadPreview, - attachmentManager.removeAttachments, - attachmentManager.uploadAttachment, - ], - ); - - if (!imageUploads.length) { - return null; - } - - return ( - ({ - index, - length: IMAGE_PREVIEW_SIZE + 8, - offset: (IMAGE_PREVIEW_SIZE + 8) * index, - })} - horizontal - keyExtractor={(item) => item.localMetadata.id} - renderItem={renderItem} - style={[styles.flatList, flatList]} - /> - ); -}; - -const MemoizedImageUploadPreviewWithContext = React.memo(UnmemoizedImageUploadPreview); - -export type ImageUploadPreviewProps = Partial; - -/** - * UI Component to preview the images set for upload - */ -export const ImageUploadPreview = (props: ImageUploadPreviewProps) => { - const { ImageAttachmentUploadPreview } = useMessageInputContext(); - return ; -}; - -const styles = StyleSheet.create({ - flatList: { paddingBottom: 12 }, -}); - -ImageUploadPreview.displayName = 'ImageUploadPreview{messageInput{imageUploadPreview}}'; diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index c39c9457f3..75969e081f 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -16,12 +16,7 @@ import Animated, { withSpring, } from 'react-native-reanimated'; -import { - isLocalImageAttachment, - type MessageComposerState, - type TextComposerState, - type UserResponse, -} from 'stream-chat'; +import { type MessageComposerState, type TextComposerState, type UserResponse } from 'stream-chat'; import { useAudioController } from './hooks/useAudioController'; import { useCountdown } from './hooks/useCountdown'; @@ -122,6 +117,7 @@ type MessageInputPropsWithContext = Pick< | 'attachmentPickerBottomSheetHeight' | 'AttachmentPickerSelectionBar' | 'attachmentSelectionBarHeight' + | 'AttachmentUploadPreviewList' | 'AudioRecorder' | 'AudioRecordingInProgress' | 'AudioRecordingLockIndicator' @@ -131,8 +127,6 @@ type MessageInputPropsWithContext = Pick< | 'CooldownTimer' | 'closeAttachmentPicker' | 'compressImageQuality' - | 'FileUploadPreview' - | 'ImageUploadPreview' | 'Input' | 'inputBoxRef' | 'InputButtons' @@ -183,6 +177,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { asyncMessagesMinimumPressDuration, asyncMessagesMultiSendEnabled, asyncMessagesSlideToCancelDistance, + AttachmentUploadPreviewList, AudioRecorder, audioRecordingEnabled, AudioRecordingInProgress, @@ -196,8 +191,6 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { CooldownTimer, CreatePollContent, editing, - FileUploadPreview, - ImageUploadPreview, Input, inputBoxRef, InputButtons, @@ -224,9 +217,6 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { const { attachments } = useAttachmentManagerState(); const hasSendableData = useMessageComposerHasSendableData(); - const imageUploads = attachments.filter((attachment) => isLocalImageAttachment(attachment)); - const fileUploads = attachments.filter((attachment) => !isLocalImageAttachment(attachment)); - const [height, setHeight] = useState(0); const { @@ -234,7 +224,6 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { colors: { border, grey_whisper, white, white_smoke }, messageInput: { attachmentSelectionBar, - attachmentSeparator, autoCompleteInputContainer, composerContainer, container, @@ -508,20 +497,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { )} - - {imageUploads.length && fileUploads.length ? ( - - ) : null} - + {command ? ( ) : ( @@ -754,6 +730,7 @@ export const MessageInput = (props: MessageInputProps) => { attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AudioRecorder, audioRecordingEnabled, AudioRecordingInProgress, @@ -770,9 +747,7 @@ export const MessageInput = (props: MessageInputProps) => { CreatePollContent, CreatePollIcon, FileSelectorIcon, - FileUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, inputBoxRef, InputButtons, @@ -820,6 +795,7 @@ export const MessageInput = (props: MessageInputProps) => { attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AudioRecorder, audioRecordingEnabled, AudioRecordingInProgress, @@ -842,9 +818,7 @@ export const MessageInput = (props: MessageInputProps) => { CreatePollIcon, editing, FileSelectorIcon, - FileUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, inputBoxRef, InputButtons, diff --git a/package/src/components/MessageInput/__tests__/FileUploadPreview.test.js b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.js similarity index 53% rename from package/src/components/MessageInput/__tests__/FileUploadPreview.test.js rename to package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.js index 161bf73178..5532989480 100644 --- a/package/src/components/MessageInput/__tests__/FileUploadPreview.test.js +++ b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.js @@ -14,7 +14,7 @@ import { import { FileState } from '../../../utils/utils'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; -import { FileUploadPreview } from '../FileUploadPreview'; +import { AttachmentUploadPreviewList } from '../AttachmentUploadPreviewList'; jest.mock('../../../native.ts', () => { const { View } = require('react-native'); @@ -38,14 +38,14 @@ const renderComponent = ({ client, channel, props }) => { - + , ); }; -describe("FileUploadPreview's render", () => { +describe('AttachmentUploadPreviewList', () => { let client; let channel; @@ -280,4 +280,213 @@ describe("FileUploadPreview's render", () => { }); }); }); + + describe('ImageAttachmentUploadPreview', () => { + it('should render ImageAttachmentUploadPreview with all uploading images', async () => { + const attachments = [ + generateImageAttachment({ + localMetadata: { + id: 'image-attachment', + uploadState: FileState.UPLOADING, + }, + }), + ]; + const props = {}; + + await act(() => { + channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); + }); + + renderComponent({ channel, client, props }); + + const { getAllByTestId, queryAllByTestId } = screen; + + await waitFor(() => { + expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); + expect(queryAllByTestId('active-upload-progress-indicator')).toHaveLength(1); + expect(queryAllByTestId('upload-progress-indicator')).toHaveLength(1); + }); + + await act(() => { + fireEvent.press(getAllByTestId('remove-upload-preview')[0]); + }); + + await waitFor(() => { + expect(channel.messageComposer.attachmentManager.attachments).toHaveLength(0); + }); + }); + + it('should return null when no images are uploaded', async () => { + const props = {}; + + renderComponent({ channel, client, props }); + + const { queryAllByTestId } = screen; + + await waitFor(() => { + expect(queryAllByTestId('file-upload-preview')).toHaveLength(0); + }); + }); + + it('should render ImageAttachmentUploadPreview with all uploaded images', async () => { + const attachments = [ + generateImageAttachment({ + localMetadata: { + id: 'image-attachment', + uploadState: FileState.FINISHED, + }, + }), + ]; + const props = {}; + + await act(() => { + channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); + }); + + renderComponent({ channel, client, props }); + + const { queryAllByTestId } = screen; + + await waitFor(() => { + const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); + for (const image of imageAttachments) { + fireEvent(image, 'loadEnd'); + } + }); + + await waitFor(() => { + expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); + expect(queryAllByTestId('inactive-upload-progress-indicator')).toHaveLength(1); + }); + }); + + it('should render ImageAttachmentUploadPreview with all failed images', async () => { + const uploadAttachmentSpy = jest.fn(); + channel.messageComposer.attachmentManager.uploadAttachment = uploadAttachmentSpy; + const attachments = [ + generateImageAttachment({ + localMetadata: { + id: 'image-attachment', + uploadState: FileState.FAILED, + }, + }), + ]; + const props = {}; + + await act(() => { + channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); + }); + + renderComponent({ channel, client, props }); + + const { getAllByTestId, queryAllByTestId } = screen; + + await waitFor(() => { + const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); + for (const image of imageAttachments) { + fireEvent(image, 'loadEnd'); + } + }); + + await waitFor(() => { + expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); + expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(1); + }); + + await act(() => { + fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); + }); + + await waitFor(() => { + expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); + expect(channel.messageComposer.attachmentManager.attachments).toHaveLength(1); + expect(uploadAttachmentSpy).toHaveBeenCalled(); + }); + }); + + it('should render ImageAttachmentUploadPreview with all unsupported', async () => { + const attachments = [ + generateImageAttachment({ + localMetadata: { + id: 'image-attachment', + uploadState: FileState.BLOCKED, + }, + }), + ]; + const props = {}; + + await act(() => { + channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); + }); + + renderComponent({ channel, client, props }); + + const { queryAllByText, queryAllByTestId } = screen; + + await waitFor(() => { + const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); + for (const image of imageAttachments) { + fireEvent(image, 'loadEnd'); + } + }); + + await waitFor(() => { + expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); + expect(queryAllByText('Not supported')).toHaveLength(1); + }); + }); + + it('should render ImageAttachmentUploadPreview with 1 uploading, 1 uploaded, and 1 failed image, and 1 unsupported', async () => { + const attachments = [ + generateImageAttachment({ + localMetadata: { + id: 'image-attachment-1', + uploadState: FileState.UPLOADING, + }, + }), + generateImageAttachment({ + localMetadata: { + id: 'image-attachment-2', + uploadState: FileState.FINISHED, + }, + }), + generateImageAttachment({ + localMetadata: { + id: 'image-attachment-3', + uploadState: FileState.FAILED, + }, + }), + generateImageAttachment({ + localMetadata: { + id: 'image-attachment-4', + uploadState: FileState.BLOCKED, + }, + }), + ]; + + const props = {}; + await act(() => { + channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); + }); + + renderComponent({ channel, client, props }); + + const { queryAllByTestId, queryAllByText } = screen; + + await waitFor(() => { + const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); + for (const image of imageAttachments) { + fireEvent(image, 'loadEnd'); + } + }); + + await waitFor(() => { + expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(4); + expect(queryAllByTestId('upload-progress-indicator')).toHaveLength(1); + expect(queryAllByTestId('inactive-upload-progress-indicator')).toHaveLength(1); + expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(1); + expect(queryAllByText('Not supported')).toHaveLength(1); + }); + }); + }); }); diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js index d60d90b1b0..00a93050cb 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js @@ -9,7 +9,7 @@ import { generateAudioAttachment } from '../../../mock-builders/attachments'; import { FileState } from '../../../utils/utils'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; -import { FileUploadPreview } from '../FileUploadPreview'; +import { AttachmentUploadPreviewList } from '../AttachmentUploadPreviewList'; jest.mock('../../../native.ts', () => { const View = require('react-native').View; @@ -33,7 +33,7 @@ const renderComponent = ({ client, channel, props }) => { - + , diff --git a/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js b/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js deleted file mode 100644 index 7b4b1475f4..0000000000 --- a/package/src/components/MessageInput/__tests__/ImageUploadPreview.test.js +++ /dev/null @@ -1,250 +0,0 @@ -import React from 'react'; - -import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; - -import { OverlayProvider } from '../../../contexts'; -import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; -import { generateImageAttachment } from '../../../mock-builders/attachments'; -import { FileState } from '../../../utils/utils'; - -import { Channel } from '../../Channel/Channel'; -import { Chat } from '../../Chat/Chat'; -import { ImageUploadPreview } from '../ImageUploadPreview'; - -const renderComponent = ({ client, channel, props }) => { - return render( - - - - - - - , - ); -}; - -describe('ImageUploadPreview', () => { - let client; - let channel; - - beforeEach(async () => { - const { client: chatClient, channels } = await initiateClientWithChannels(); - client = chatClient; - channel = channels[0]; - }); - - afterEach(() => { - jest.clearAllMocks(); - cleanup(); - act(() => { - channel.messageComposer.clear(); - }); - }); - - it('should render ImageUploadPreview with all uploading images', async () => { - const attachments = [ - generateImageAttachment({ - localMetadata: { - id: 'image-attachment', - uploadState: FileState.UPLOADING, - }, - }), - ]; - const props = {}; - - act(() => { - channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); - }); - - renderComponent({ channel, client, props }); - - const { getAllByTestId, queryAllByTestId } = screen; - - await waitFor(() => { - expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); - expect(queryAllByTestId('active-upload-progress-indicator')).toHaveLength(1); - expect(queryAllByTestId('upload-progress-indicator')).toHaveLength(1); - }); - - await act(() => { - fireEvent.press(getAllByTestId('remove-upload-preview')[0]); - }); - - await waitFor(() => { - expect(channel.messageComposer.attachmentManager.attachments).toHaveLength(0); - }); - }); - - it('should return null when no images are uploaded', async () => { - const props = {}; - - renderComponent({ channel, client, props }); - - const { queryAllByTestId } = screen; - - await waitFor(() => { - expect(queryAllByTestId('file-upload-preview')).toHaveLength(0); - }); - }); - - it('should render ImageUploadPreview with all uploaded images', async () => { - const attachments = [ - generateImageAttachment({ - localMetadata: { - id: 'image-attachment', - uploadState: FileState.FINISHED, - }, - }), - ]; - const props = {}; - - act(() => { - channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); - }); - - renderComponent({ channel, client, props }); - - const { queryAllByTestId } = screen; - - await waitFor(() => { - const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); - for (const image of imageAttachments) { - fireEvent(image, 'loadEnd'); - } - }); - - await waitFor(() => { - expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); - expect(queryAllByTestId('inactive-upload-progress-indicator')).toHaveLength(1); - }); - }); - - it('should render ImageUploadPreview with all failed images', async () => { - const uploadAttachmentSpy = jest.fn(); - channel.messageComposer.attachmentManager.uploadAttachment = uploadAttachmentSpy; - const attachments = [ - generateImageAttachment({ - localMetadata: { - id: 'image-attachment', - uploadState: FileState.FAILED, - }, - }), - ]; - const props = {}; - - act(() => { - channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); - }); - - renderComponent({ channel, client, props }); - - const { getAllByTestId, queryAllByTestId } = screen; - - await waitFor(() => { - const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); - for (const image of imageAttachments) { - fireEvent(image, 'loadEnd'); - } - }); - - await waitFor(() => { - expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); - expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(1); - }); - - act(() => { - fireEvent.press(getAllByTestId('retry-upload-progress-indicator')[0]); - }); - - await waitFor(() => { - expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); - expect(channel.messageComposer.attachmentManager.attachments).toHaveLength(1); - expect(uploadAttachmentSpy).toHaveBeenCalled(); - }); - }); - - it('should render ImageUploadPreview with all unsupported', async () => { - const attachments = [ - generateImageAttachment({ - localMetadata: { - id: 'image-attachment', - uploadState: FileState.BLOCKED, - }, - }), - ]; - const props = {}; - - act(() => { - channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); - }); - - renderComponent({ channel, client, props }); - - const { queryAllByText, queryAllByTestId } = screen; - - await waitFor(() => { - const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); - for (const image of imageAttachments) { - fireEvent(image, 'loadEnd'); - } - }); - - await waitFor(() => { - expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); - expect(queryAllByText('Not supported')).toHaveLength(1); - }); - }); - - it('should render ImageUploadPreview with 1 uploading, 1 uploaded, and 1 failed image, and 1 unsupported', async () => { - const attachments = [ - generateImageAttachment({ - localMetadata: { - id: 'image-attachment-1', - uploadState: FileState.UPLOADING, - }, - }), - generateImageAttachment({ - localMetadata: { - id: 'image-attachment-2', - uploadState: FileState.FINISHED, - }, - }), - generateImageAttachment({ - localMetadata: { - id: 'image-attachment-3', - uploadState: FileState.FAILED, - }, - }), - generateImageAttachment({ - localMetadata: { - id: 'image-attachment-4', - uploadState: FileState.BLOCKED, - }, - }), - ]; - - const props = {}; - act(() => { - channel.messageComposer.attachmentManager.upsertAttachments(attachments ?? []); - }); - - renderComponent({ channel, client, props }); - - const { queryAllByTestId, queryAllByText } = screen; - - await waitFor(() => { - const imageAttachments = queryAllByTestId('image-attachment-upload-preview-image'); - for (const image of imageAttachments) { - fireEvent(image, 'loadEnd'); - } - }); - - await waitFor(() => { - expect(queryAllByTestId('image-attachment-upload-preview')).toHaveLength(4); - expect(queryAllByTestId('upload-progress-indicator')).toHaveLength(1); - expect(queryAllByTestId('inactive-upload-progress-indicator')).toHaveLength(1); - expect(queryAllByTestId('retry-upload-progress-indicator')).toHaveLength(1); - expect(queryAllByText('Not supported')).toHaveLength(1); - }); - }); -}); diff --git a/package/src/components/MessageInput/hooks/useAudioPreviewManager.tsx b/package/src/components/MessageInput/hooks/useAudioPreviewManager.tsx new file mode 100644 index 0000000000..b0c6936bdb --- /dev/null +++ b/package/src/components/MessageInput/hooks/useAudioPreviewManager.tsx @@ -0,0 +1,98 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { LocalAttachment } from 'stream-chat'; + +import { AudioConfig } from '../../../types/types'; + +/** + * Manages the state of audio attachments for preview and playback. + * @param files The audio files to manage. + * @returns An object containing the state and handlers for audio attachments. + */ +export const useAudioPreviewManager = (files: LocalAttachment[]) => { + const [audioAttachmentsStateMap, setAudioAttachmentsStateMap] = useState< + Record + >({}); + + useEffect(() => { + setAudioAttachmentsStateMap((prevState) => { + const updatedStateMap = Object.fromEntries( + files.map((attachment) => { + const id = attachment.localMetadata.id; + + const config: AudioConfig = { + duration: attachment.duration ?? prevState[id]?.duration ?? 0, + paused: prevState[id]?.paused ?? true, + progress: prevState[id]?.progress ?? 0, + }; + + return [id, config]; + }), + ); + + return updatedStateMap; + }); + }, [files]); + + // Handler triggered when an audio is loaded in the message input. The initial state is defined for the audio here + // and the duration is set. + const onLoad = useCallback((index: string, duration: number) => { + setAudioAttachmentsStateMap((prevState) => ({ + ...prevState, + [index]: { + ...prevState[index], + duration, + }, + })); + }, []); + + // The handler which is triggered when the audio progresses/ the thumb is dragged in the progress control. The + // progressed duration is set here. + const onProgress = useCallback((index: string, progress: number) => { + setAudioAttachmentsStateMap((prevState) => ({ + ...prevState, + [index]: { + ...prevState[index], + progress, + }, + })); + }, []); + + // The handler which controls or sets the paused/played state of the audio. + const onPlayPause = useCallback((index: string, pausedStatus?: boolean) => { + if (pausedStatus === false) { + // In this case, all others except the index are set to paused. + setAudioAttachmentsStateMap((prevState) => { + const newState = { ...prevState }; + Object.keys(newState).forEach((key) => { + if (key !== index) { + newState[key].paused = true; + } + }); + return { + ...newState, + [index]: { + ...newState[index], + paused: false, + }, + }; + }); + } else { + setAudioAttachmentsStateMap((prevState) => ({ + ...prevState, + [index]: { + ...prevState[index], + paused: true, + }, + })); + } + }, []); + + return { + audioAttachmentsStateMap, + onLoad, + onPlayPause, + onProgress, + setAudioAttachmentsStateMap, + }; +}; diff --git a/package/src/components/index.ts b/package/src/components/index.ts index efab84fd58..30cc31bbdd 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -124,8 +124,7 @@ export * from './MessageInput/components/AudioRecorder/AudioRecordingPreview'; export * from './MessageInput/components/AudioRecorder/AudioRecordingWaveform'; export * from './MessageInput/components/CommandInput'; export * from './MessageInput/CooldownTimer'; -export * from './MessageInput/FileUploadPreview'; -export * from './MessageInput/ImageUploadPreview'; +export * from './MessageInput/AttachmentUploadPreviewList'; export * from './MessageInput/InputButtons'; export * from './MessageInput/MessageInput'; export * from './MessageInput/MoreOptionsButton'; diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index dd5f2092d9..17b2cfb425 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -34,6 +34,7 @@ import { } from '../../components'; import { parseLinksFromText } from '../../components/Message/MessageSimple/utils/parseLinks'; import type { AttachButtonProps } from '../../components/MessageInput/AttachButton'; +import { AttachmentUploadPreviewListProps } from '../../components/MessageInput/AttachmentUploadPreviewList'; import type { CommandsButtonProps } from '../../components/MessageInput/CommandsButton'; import type { AttachmentUploadProgressIndicatorProps } from '../../components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; import { AudioAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview'; @@ -48,9 +49,7 @@ import type { AudioRecordingWaveformProps } from '../../components/MessageInput/ import type { CommandInputProps } from '../../components/MessageInput/components/CommandInput'; import type { InputEditingStateHeaderProps } from '../../components/MessageInput/components/InputEditingStateHeader'; import type { CooldownTimerProps } from '../../components/MessageInput/CooldownTimer'; -import type { FileUploadPreviewProps } from '../../components/MessageInput/FileUploadPreview'; import { useCooldown } from '../../components/MessageInput/hooks/useCooldown'; -import type { ImageUploadPreviewProps } from '../../components/MessageInput/ImageUploadPreview'; import type { InputButtonsProps } from '../../components/MessageInput/InputButtons'; import type { MessageInputProps } from '../../components/MessageInput/MessageInput'; import type { MoreOptionsButtonProps } from '../../components/MessageInput/MoreOptionsButton'; @@ -209,6 +208,7 @@ export type InputMessageInputContextValue = { */ attachmentSelectionBarHeight: number; + AttachmentUploadPreviewList: React.ComponentType; /** * Custom UI component for [camera selector icon](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) * @@ -268,12 +268,6 @@ export type InputMessageInputContextValue = { localMessage: LocalMessage; options?: UpdateMessageOptions; }) => ReturnType; - /** - * Custom UI component for FileUploadPreview. - * Defaults to and accepts same props as: - * https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/FileUploadPreview.tsx - */ - FileUploadPreview: React.ComponentType; /** When false, CameraSelectorIcon will be hidden */ hasCameraPicker: boolean; @@ -284,12 +278,7 @@ export type InputMessageInputContextValue = { hasFilePicker: boolean; /** When false, ImageSelectorIcon will be hidden */ hasImagePicker: boolean; - /** - * Custom UI component for ImageUploadPreview. - * Defaults to and accepts same props as: - * https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/ImageUploadPreview.tsx - */ - ImageUploadPreview: React.ComponentType; + InputEditingStateHeader: React.ComponentType; CommandInput: React.ComponentType; InputReplyStateHeader: React.ComponentType; diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index 6ddc823a20..7ad8d8f712 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -15,6 +15,7 @@ export const useCreateMessageInputContext = ({ attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AttachmentUploadProgressIndicator, AudioAttachmentUploadPreview, AudioRecorder, @@ -39,7 +40,6 @@ export const useCreateMessageInputContext = ({ editMessage, FileAttachmentUploadPreview, FileSelectorIcon, - FileUploadPreview, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -47,7 +47,6 @@ export const useCreateMessageInputContext = ({ hasImagePicker, ImageAttachmentUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, inputBoxRef, InputButtons, @@ -90,6 +89,7 @@ export const useCreateMessageInputContext = ({ attachmentPickerBottomSheetHeight, AttachmentPickerSelectionBar, attachmentSelectionBarHeight, + AttachmentUploadPreviewList, AttachmentUploadProgressIndicator, AudioAttachmentUploadPreview, AudioRecorder, @@ -114,7 +114,6 @@ export const useCreateMessageInputContext = ({ editMessage, FileAttachmentUploadPreview, FileSelectorIcon, - FileUploadPreview, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -122,7 +121,6 @@ export const useCreateMessageInputContext = ({ hasImagePicker, ImageAttachmentUploadPreview, ImageSelectorIcon, - ImageUploadPreview, Input, inputBoxRef, InputButtons, diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 5b528452d7..9df8cf26b3 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -274,6 +274,11 @@ export type Theme = { warningIcon: IconProps; text: TextStyle; }; + attachmentUploadPreviewList: { + filesFlatList: ViewStyle; + imagesFlatList: ViewStyle; + wrapper: ViewStyle; + }; audioRecorder: { arrowLeftIcon: IconProps; checkContainer: ViewStyle; @@ -1081,6 +1086,11 @@ export const defaultTheme: Theme = { text: {}, warningIcon: {}, }, + attachmentUploadPreviewList: { + filesFlatList: {}, + imagesFlatList: {}, + wrapper: {}, + }, audioRecorder: { arrowLeftIcon: {}, checkContainer: {},