Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions package/src/components/Attachment/FileAttachment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { Pressable, StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-native';
import { Pressable, StyleProp, StyleSheet, TextStyle, View, ViewStyle } from 'react-native';

import type { Attachment } from 'stream-chat';

Expand All @@ -16,12 +16,17 @@ import {
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useIsPendingAttachmentUploading } from '../../hooks/useIsPendingAttachmentUploading';
import type { DefaultAttachmentData } from '../../types/types';

export type FileAttachmentPropsWithContext = Pick<
MessageContextValue,
'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'
> &
Pick<MessagesContextValue, 'additionalPressableProps' | 'FilePreview'> & {
Pick<
MessagesContextValue,
'additionalPressableProps' | 'FilePreview' | 'ImageUploadingIndicator'
> & {
/** The attachment to render */
attachment: Attachment;
attachmentIconSize?: FileIconProps['size'];
Expand All @@ -42,13 +47,17 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
attachment,
attachmentIconSize,
FilePreview,
ImageUploadingIndicator,
onLongPress,
onPress,
onPressIn,
preventPress,
styles: stylesProp = styles,
} = props;

const localId = (attachment as DefaultAttachmentData).localId;
const isPendingAttachmentUploading = useIsPendingAttachmentUploading(localId);

const defaultOnPress = () => openUrlSafely(attachment.asset_url);

return (
Expand Down Expand Up @@ -86,11 +95,14 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
testID='file-attachment'
{...additionalPressableProps}
>
<FilePreview
attachment={attachment}
attachmentIconSize={attachmentIconSize}
styles={stylesProp}
/>
<View style={styles.previewWrap}>
<FilePreview
attachment={attachment}
attachmentIconSize={attachmentIconSize}
styles={stylesProp}
/>
{isPendingAttachmentUploading ? <ImageUploadingIndicator /> : null}
</View>
</Pressable>
);
};
Expand All @@ -101,15 +113,21 @@ export type FileAttachmentProps = Partial<Omit<FileAttachmentPropsWithContext, '
export const FileAttachment = (props: FileAttachmentProps) => {
const { FilePreview: PropFilePreview } = props;
const { onLongPress, onPress, onPressIn, preventPress } = useMessageContext();
const { additionalPressableProps, FilePreview: ContextFilePreview } = useMessagesContext();
const {
additionalPressableProps,
FilePreview: ContextFilePreview,
ImageUploadingIndicator: ContextImageUploadingIndicator,
} = useMessagesContext();

const FilePreview = PropFilePreview || ContextFilePreview;
const ImageUploadingIndicator = props.ImageUploadingIndicator || ContextImageUploadingIndicator;

return (
<FileAttachmentWithContext
{...{
additionalPressableProps,
FilePreview,
ImageUploadingIndicator,
onLongPress,
onPress,
onPressIn,
Expand Down Expand Up @@ -138,6 +156,9 @@ const useStyles = () => {
? semantics.chatBgAttachmentOutgoing
: semantics.chatBgAttachmentIncoming,
},
previewWrap: {
position: 'relative',
},
});
}, [showBackgroundTransparent, isMyMessage, semantics]);
};
27 changes: 25 additions & 2 deletions package/src/components/Attachment/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
} from '../../contexts/overlayContext/OverlayContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';

import { useIsPendingAttachmentUploading } from '../../hooks/useIsPendingAttachmentUploading';
import { useLoadingImage } from '../../hooks/useLoadingImage';
import { useStableCallback } from '../../hooks/useStableCallback';
import { isVideoPlayerAvailable } from '../../native';
Expand All @@ -60,6 +61,7 @@ export type GalleryPropsWithContext = Pick<ImageGalleryContextValue, 'imageGalle
| 'VideoThumbnail'
| 'ImageLoadingIndicator'
| 'ImageLoadingFailedIndicator'
| 'ImageUploadingIndicator'
| 'myMessageTheme'
> &
Pick<OverlayContextValue, 'setOverlay'> & {
Expand All @@ -74,6 +76,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
imageGalleryStateStore,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageUploadingIndicator,
images,
message,
onLongPress,
Expand Down Expand Up @@ -193,6 +196,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
imageGalleryStateStore={imageGalleryStateStore}
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
ImageLoadingIndicator={ImageLoadingIndicator}
ImageUploadingIndicator={ImageUploadingIndicator}
imagesAndVideos={imagesAndVideos}
invertedDirections={invertedDirections || false}
key={rowIndex}
Expand Down Expand Up @@ -233,6 +237,7 @@ type GalleryThumbnailProps = {
| 'VideoThumbnail'
| 'ImageLoadingIndicator'
| 'ImageLoadingFailedIndicator'
| 'ImageUploadingIndicator'
> &
Pick<ImageGalleryContextValue, 'imageGalleryStateStore'> &
Pick<MessageContextValue, 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'> &
Expand All @@ -245,6 +250,7 @@ const GalleryThumbnail = ({
imageGalleryStateStore,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageUploadingIndicator,
imagesAndVideos,
invertedDirections,
message,
Expand All @@ -269,6 +275,7 @@ const GalleryThumbnail = ({
} = useTheme();
const { t } = useTranslationContext();
const styles = useStyles();
const isPendingAttachmentUploading = useIsPendingAttachmentUploading(thumbnail.localId);

const openImageViewer = () => {
if (!message) {
Expand Down Expand Up @@ -333,6 +340,7 @@ const GalleryThumbnail = ({
>
{thumbnail.type === FileTypes.Video ? (
<VideoThumbnail
isPendingAttachmentUploading={isPendingAttachmentUploading}
style={[styles.image, imageBorderRadius ?? borderRadius, image]}
thumb_url={thumbnail.thumb_url}
/>
Expand All @@ -341,6 +349,8 @@ const GalleryThumbnail = ({
borderRadius={imageBorderRadius ?? borderRadius}
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
ImageLoadingIndicator={ImageLoadingIndicator}
ImageUploadingIndicator={ImageUploadingIndicator}
isPendingAttachmentUploading={isPendingAttachmentUploading}
thumbnail={thumbnail}
/>
)}
Expand All @@ -367,11 +377,19 @@ const GalleryImageThumbnail = ({
borderRadius,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageUploadingIndicator,
isPendingAttachmentUploading,
thumbnail,
}: Pick<
GalleryThumbnailProps,
'ImageLoadingFailedIndicator' | 'ImageLoadingIndicator' | 'thumbnail' | 'borderRadius'
>) => {
| 'ImageLoadingFailedIndicator'
| 'ImageLoadingIndicator'
| 'ImageUploadingIndicator'
| 'thumbnail'
| 'borderRadius'
> & {
isPendingAttachmentUploading: boolean;
}) => {
const {
isLoadingImage,
isLoadingImageError,
Expand Down Expand Up @@ -421,6 +439,7 @@ const GalleryImageThumbnail = ({
uri={thumbnail.url}
/>
{isLoadingImage ? <ImageLoadingIndicator /> : null}
{isPendingAttachmentUploading ? <ImageUploadingIndicator /> : null}
</>
)}
</View>
Expand Down Expand Up @@ -499,6 +518,7 @@ export const Gallery = (props: GalleryProps) => {
additionalPressableProps: propAdditionalPressableProps,
ImageLoadingFailedIndicator: PropImageLoadingFailedIndicator,
ImageLoadingIndicator: PropImageLoadingIndicator,
ImageUploadingIndicator: PropImageUploadingIndicator,
images: propImages,
message: propMessage,
myMessageTheme: propMyMessageTheme,
Expand Down Expand Up @@ -528,6 +548,7 @@ export const Gallery = (props: GalleryProps) => {
additionalPressableProps: contextAdditionalPressableProps,
ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator,
ImageLoadingIndicator: ContextImageLoadingIndicator,
ImageUploadingIndicator: ContextImageUploadingIndicator,
myMessageTheme: contextMyMessageTheme,
VideoThumbnail: ContextVideoThumnbnail,
} = useMessagesContext();
Expand All @@ -553,6 +574,7 @@ export const Gallery = (props: GalleryProps) => {
const ImageLoadingFailedIndicator =
PropImageLoadingFailedIndicator || ContextImageLoadingFailedIndicator;
const ImageLoadingIndicator = PropImageLoadingIndicator || ContextImageLoadingIndicator;
const ImageUploadingIndicator = PropImageUploadingIndicator || ContextImageUploadingIndicator;
const myMessageTheme = propMyMessageTheme || contextMyMessageTheme;
const messageContentOrder = propMessageContentOrder || contextMessageContentOrder;

Expand All @@ -570,6 +592,7 @@ export const Gallery = (props: GalleryProps) => {
imageGalleryStateStore,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageUploadingIndicator,
images,
message,
myMessageTheme,
Expand Down
28 changes: 28 additions & 0 deletions package/src/components/Attachment/ImageUploadingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { ActivityIndicator, StyleSheet, View, ViewProps } from 'react-native';

import { useTheme } from '../../contexts/themeContext/ThemeContext';

export type ImageUploadingIndicatorProps = ViewProps;

export const ImageUploadingIndicator = ({ style, ...rest }: ImageUploadingIndicatorProps) => {
const {
theme: { semantics },
} = useTheme();
return (
<View
pointerEvents='none'
style={[StyleSheet.absoluteFillObject, styles.centered, style]}
{...rest}
>
<ActivityIndicator color={semantics.accentPrimary} />
</View>
);
};

const styles = StyleSheet.create({
centered: {
alignItems: 'center',
justifyContent: 'center',
},
});
30 changes: 28 additions & 2 deletions package/src/components/Attachment/VideoThumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import React from 'react';
import { ImageBackground, ImageStyle, StyleProp, StyleSheet, ViewStyle } from 'react-native';
import {
ActivityIndicator,
ImageBackground,
ImageStyle,
StyleProp,
StyleSheet,
View,
ViewStyle,
} from 'react-native';

import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { primitives } from '../../theme';
import { VideoPlayIndicator } from '../ui/VideoPlayIndicator';

const styles = StyleSheet.create({
activityIndicator: {
alignItems: 'flex-start',
justifyContent: 'flex-start',
},
activityIndicatorContainer: {
bottom: primitives.spacingXs,
left: primitives.spacingXs,
position: 'absolute',
},
container: {
alignItems: 'center',
justifyContent: 'center',
Expand All @@ -15,6 +33,8 @@ const styles = StyleSheet.create({

export type VideoThumbnailProps = {
imageStyle?: StyleProp<ImageStyle>;
/** When true, shows upload progress over the thumbnail */
isPendingAttachmentUploading?: boolean;
style?: StyleProp<ViewStyle>;
thumb_url?: string;
};
Expand All @@ -25,9 +45,10 @@ export const VideoThumbnail = (props: VideoThumbnailProps) => {
messageItemView: {
videoThumbnail: { container },
},
semantics,
},
} = useTheme();
const { imageStyle, style, thumb_url } = props;
const { imageStyle, isPendingAttachmentUploading, style, thumb_url } = props;
return (
<ImageBackground
accessibilityLabel='Video Thumbnail'
Expand All @@ -36,6 +57,11 @@ export const VideoThumbnail = (props: VideoThumbnailProps) => {
style={[styles.container, container, style]}
>
<VideoPlayIndicator size='md' />
{isPendingAttachmentUploading ? (
<View style={[styles.activityIndicatorContainer, styles.activityIndicator]}>
<ActivityIndicator color={semantics.accentPrimary} />
</View>
) : null}
</ImageBackground>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { generateMessage } from '../../../mock-builders/generator/message';

import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
import { ImageUploadingIndicator } from '../../Attachment/ImageUploadingIndicator';
import { Attachment } from '../Attachment';
import { FilePreview as FilePreviewDefault } from '../FilePreview';

Expand All @@ -24,15 +25,20 @@ jest.mock('../../../native.ts', () => ({
isSoundPackageAvailable: jest.fn(() => false),
}));

jest.mock('../../../hooks/useIsPendingAttachmentUploading', () => ({
useIsPendingAttachmentUploading: jest.fn(() => false),
}));

const getAttachmentComponent = (props) => {
const message = generateMessage();
return (
<ThemeProvider>
<MessagesProvider
value={{
FilePreview: FilePreviewDefault,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
FilePreview: FilePreviewDefault,
ImageUploadingIndicator,
}}
>
<MessageProvider value={{ message }}>
Expand Down
9 changes: 8 additions & 1 deletion package/src/components/Attachment/__tests__/Giphy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { getTestClientWithUser } from '../../../mock-builders/mock';
import { Streami18n } from '../../../utils/i18n/Streami18n';
import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
import { ImageUploadingIndicator } from '../../Attachment/ImageUploadingIndicator';
import { Channel } from '../../Channel/Channel';
import { Chat } from '../../Chat/Chat';
import { MessageList } from '../../MessageList/MessageList';
Expand All @@ -40,7 +41,13 @@ describe('Giphy', () => {
const message = generateMessage();
return (
<ThemeProvider>
<MessagesProvider value={{ ImageLoadingFailedIndicator, ImageLoadingIndicator }}>
<MessagesProvider
value={{
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageUploadingIndicator,
}}
>
<MessageProvider value={{ message, ...messageContextValue }}>
<Giphy {...props} />
</MessageProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Attachment } from 'stream-chat';
import type { Thumbnail } from './types';

import { ChatConfigContextValue } from '../../../../contexts/chatConfigContext/ChatConfigContext';
import type { DefaultAttachmentData } from '../../../../types/types';

import { getResizedImageUrl } from '../../../../utils/getResizedImageUrl';
import { getUrlOfImageAttachment } from '../../../../utils/getUrlOfImageAttachment';
Expand Down Expand Up @@ -33,9 +34,11 @@ export function buildThumbnail({
? originalImageHeight + originalImageWidth > height + width
: true;
const imageUrl = getUrlOfImageAttachment(image) as string;
const localId = (image as Attachment & DefaultAttachmentData).localId;

return {
flex,
localId,
resizeMode: resizeMode
? resizeMode
: ((image.original_height && image.original_width ? 'contain' : 'cover') as ImageResizeMode),
Expand Down
Loading
Loading