Skip to content
Open
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
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"path": "0.12.7",
"react-native-markdown-package": "1.8.2",
"react-native-url-polyfill": "^2.0.0",
"stream-chat": "^9.35.1",
"stream-chat": "^9.36.0",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
Expand Down
56 changes: 52 additions & 4 deletions package/src/components/Attachment/FileAttachment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React, { useMemo } from 'react';
import { Pressable, StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-native';
import React, { useCallback, useMemo } from 'react';
import {
ActivityIndicator,
Pressable,
StyleProp,
StyleSheet,
TextStyle,
ViewStyle,
} from 'react-native';

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

Expand All @@ -16,6 +23,8 @@ import {
useMessagesContext,
} from '../../contexts/messagesContext/MessagesContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useStateStore } from '../../hooks/useStateStore';
import type { PendingAttachmentsUploadingState } from '../../state-store/pending-attachments-uploading-state';

export type FileAttachmentPropsWithContext = Pick<
MessageContextValue,
Expand All @@ -32,10 +41,18 @@ export type FileAttachmentPropsWithContext = Pick<
size: StyleProp<TextStyle>;
title: StyleProp<TextStyle>;
}>;
/**
* Whether the attachment is currently being uploaded.
* This is used to show a loading indicator in the file attachment.
*/
isPendingAttachmentUploading: boolean;
};

const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
const styles = useStyles();
const {
theme: { semantics },
} = useTheme();

const {
additionalPressableProps,
Expand All @@ -46,10 +63,18 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
onPressIn,
preventPress,
styles: stylesProp = styles,
isPendingAttachmentUploading,
} = props;

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

const renderIndicator = useMemo(() => {
if (isPendingAttachmentUploading) {
return <ActivityIndicator color={semantics.accentPrimary} style={styles.activityIndicator} />;
}
return null;
}, [isPendingAttachmentUploading, semantics.accentPrimary, styles.activityIndicator]);

return (
<Pressable
disabled={preventPress}
Expand Down Expand Up @@ -88,6 +113,7 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
<FilePreview
attachment={attachment}
attachmentIconSize={attachmentIconSize}
indicator={renderIndicator}
styles={stylesProp}
/>
</Pressable>
Expand All @@ -98,8 +124,25 @@ export type FileAttachmentProps = Partial<Omit<FileAttachmentPropsWithContext, '
Pick<FileAttachmentPropsWithContext, 'attachment'>;

export const FileAttachment = (props: FileAttachmentProps) => {
const { onLongPress, onPress, onPressIn, preventPress } = useMessageContext();
const { additionalPressableProps, FileAttachmentIcon = FileIconDefault } = useMessagesContext();
const { attachment } = props;
const { onLongPress, onPress, onPressIn, preventPress, message } = useMessageContext();
const {
additionalPressableProps,
FileAttachmentIcon = FileIconDefault,
pendingAttachmentsUploadingStore,
} = useMessagesContext();

const attachmentId = `${message.id}-${attachment.originalFile?.uri}`;
const selector = useCallback(
(state: PendingAttachmentsUploadingState) => ({
isPendingAttachmentUploading: state.pendingAttachmentsUploading[attachmentId] ?? false,
}),
[attachmentId],
);
const { isPendingAttachmentUploading } = useStateStore(
pendingAttachmentsUploadingStore.store,
selector,
);

return (
<FileAttachmentWithContext
Expand All @@ -110,6 +153,7 @@ export const FileAttachment = (props: FileAttachmentProps) => {
onPress,
onPressIn,
preventPress,
isPendingAttachmentUploading,
}}
{...props}
/>
Expand All @@ -134,6 +178,10 @@ const useStyles = () => {
? semantics.chatBgAttachmentOutgoing
: semantics.chatBgAttachmentIncoming,
},
activityIndicator: {
alignItems: 'flex-start',
justifyContent: 'flex-start',
},
});
}, [showBackgroundTransparent, isMyMessage, semantics]);
};
54 changes: 52 additions & 2 deletions package/src/components/Attachment/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';

import type { Attachment, LocalMessage } from 'stream-chat';
Expand Down Expand Up @@ -34,8 +34,10 @@ import {
} from '../../contexts/overlayContext/OverlayContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';

import { useStateStore } from '../../hooks';
import { useLoadingImage } from '../../hooks/useLoadingImage';
import { isVideoPlayerAvailable } from '../../native';
import { PendingAttachmentsUploadingState } from '../../state-store/pending-attachments-uploading-state';
import { primitives } from '../../theme';
import { FileTypes } from '../../types/types';
import { getUrlWithoutParams } from '../../utils/utils';
Expand All @@ -60,7 +62,9 @@ export type GalleryPropsWithContext = Pick<ImageGalleryContextValue, 'imageGalle
| 'ImageLoadingIndicator'
| 'ImageLoadingFailedIndicator'
| 'ImageReloadIndicator'
| 'ImageUploadingIndicator'
| 'myMessageTheme'
| 'pendingAttachmentsUploadingStore'
> &
Pick<OverlayContextValue, 'setOverlay'> & {
channelId: string | undefined;
Expand All @@ -75,6 +79,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageReloadIndicator,
ImageUploadingIndicator,
images,
message,
onLongPress,
Expand All @@ -85,6 +90,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
videos,
VideoThumbnail,
messageHasOnlyOneImage = false,
pendingAttachmentsUploadingStore,
} = props;

const { resizableCDNHosts } = useChatConfigContext();
Expand Down Expand Up @@ -195,6 +201,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
ImageLoadingIndicator={ImageLoadingIndicator}
ImageReloadIndicator={ImageReloadIndicator}
ImageUploadingIndicator={ImageUploadingIndicator}
imagesAndVideos={imagesAndVideos}
invertedDirections={invertedDirections || false}
key={rowIndex}
Expand All @@ -209,6 +216,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
setOverlay={setOverlay}
thumbnail={thumbnail}
VideoThumbnail={VideoThumbnail}
pendingAttachmentsUploadingStore={pendingAttachmentsUploadingStore}
/>
);
})}
Expand Down Expand Up @@ -236,6 +244,8 @@ type GalleryThumbnailProps = {
| 'ImageLoadingIndicator'
| 'ImageLoadingFailedIndicator'
| 'ImageReloadIndicator'
| 'ImageUploadingIndicator'
| 'pendingAttachmentsUploadingStore'
> &
Pick<ImageGalleryContextValue, 'imageGalleryStateStore'> &
Pick<MessageContextValue, 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'> &
Expand All @@ -249,6 +259,7 @@ const GalleryThumbnail = ({
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageReloadIndicator,
ImageUploadingIndicator,
imagesAndVideos,
invertedDirections,
message,
Expand All @@ -262,6 +273,7 @@ const GalleryThumbnail = ({
setOverlay,
thumbnail,
VideoThumbnail,
pendingAttachmentsUploadingStore,
}: GalleryThumbnailProps) => {
const {
theme: {
Expand All @@ -274,6 +286,18 @@ const GalleryThumbnail = ({
const { t } = useTranslationContext();
const styles = useStyles();

const attachmentId = `${message.id}-${thumbnail.url}`;
const selector = useCallback(
(state: PendingAttachmentsUploadingState) => ({
isPendingAttachmentUploading: state.pendingAttachmentsUploading[attachmentId] ?? false,
}),
[attachmentId],
);
const { isPendingAttachmentUploading } = useStateStore(
pendingAttachmentsUploadingStore.store,
selector,
);

const openImageViewer = () => {
if (!message) {
return;
Expand Down Expand Up @@ -346,14 +370,17 @@ const GalleryThumbnail = ({
<VideoThumbnail
style={[styles.image, imageBorderRadius ?? borderRadius, image]}
thumb_url={thumbnail.thumb_url}
isPendingAttachmentUploading={isPendingAttachmentUploading}
/>
) : (
<GalleryImageThumbnail
borderRadius={imageBorderRadius ?? borderRadius}
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
ImageLoadingIndicator={ImageLoadingIndicator}
ImageReloadIndicator={ImageReloadIndicator}
ImageUploadingIndicator={ImageUploadingIndicator}
thumbnail={thumbnail}
isPendingAttachmentUploading={isPendingAttachmentUploading}
/>
)}
{colIndex === numOfColumns - 1 && rowIndex === numOfRows - 1 && imagesAndVideos.length > 4 ? (
Expand Down Expand Up @@ -381,14 +408,23 @@ const GalleryImageThumbnail = ({
ImageLoadingIndicator,
ImageReloadIndicator,
thumbnail,
isPendingAttachmentUploading,
ImageUploadingIndicator,
}: Pick<
GalleryThumbnailProps,
| 'ImageLoadingFailedIndicator'
| 'ImageLoadingIndicator'
| 'ImageReloadIndicator'
| 'ImageUploadingIndicator'
| 'thumbnail'
| 'borderRadius'
>) => {
> & {
/**
* Whether the attachment is currently being uploaded.
* This is used to show a loading indicator in the thumbnail.
*/
isPendingAttachmentUploading: boolean;
}) => {
const {
isLoadingImage,
isLoadingImageError,
Expand Down Expand Up @@ -434,6 +470,11 @@ const GalleryImageThumbnail = ({
<ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
</View>
)}
{isPendingAttachmentUploading && (
<View style={styles.imageLoadingIndicatorContainer}>
<ImageUploadingIndicator style={styles.imageLoadingIndicatorStyle} />
</View>
)}
</>
)}
</View>
Expand Down Expand Up @@ -513,6 +554,7 @@ export const Gallery = (props: GalleryProps) => {
ImageLoadingFailedIndicator: PropImageLoadingFailedIndicator,
ImageLoadingIndicator: PropImageLoadingIndicator,
ImageReloadIndicator: PropImageReloadIndicator,
ImageUploadingIndicator: PropImageUploadingIndicator,
images: propImages,
message: propMessage,
myMessageTheme: propMyMessageTheme,
Expand All @@ -524,6 +566,7 @@ export const Gallery = (props: GalleryProps) => {
videos: propVideos,
VideoThumbnail: PropVideoThumbnail,
messageContentOrder: propMessageContentOrder,
pendingAttachmentsUploadingStore: propPendingAttachmentsLoadingStore,
} = props;

const { imageGalleryStateStore } = useImageGalleryContext();
Expand All @@ -543,8 +586,10 @@ export const Gallery = (props: GalleryProps) => {
ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator,
ImageLoadingIndicator: ContextImageLoadingIndicator,
ImageReloadIndicator: ContextImageReloadIndicator,
ImageUploadingIndicator: ContextImageUploadingIndicator,
myMessageTheme: contextMyMessageTheme,
VideoThumbnail: ContextVideoThumnbnail,
pendingAttachmentsUploadingStore: contextPendingAttachmentsLoadingStore,
} = useMessagesContext();
const { setOverlay: contextSetOverlay } = useOverlayContext();

Expand All @@ -568,8 +613,11 @@ export const Gallery = (props: GalleryProps) => {
PropImageLoadingFailedIndicator || ContextImageLoadingFailedIndicator;
const ImageLoadingIndicator = PropImageLoadingIndicator || ContextImageLoadingIndicator;
const ImageReloadIndicator = PropImageReloadIndicator || ContextImageReloadIndicator;
const ImageUploadingIndicator = PropImageUploadingIndicator || ContextImageUploadingIndicator;
const myMessageTheme = propMyMessageTheme || contextMyMessageTheme;
const messageContentOrder = propMessageContentOrder || contextMessageContentOrder;
const pendingAttachmentsUploadingStore =
propPendingAttachmentsLoadingStore || contextPendingAttachmentsLoadingStore;

const messageHasOnlyOneImage =
messageContentOrder?.length === 1 &&
Expand All @@ -586,6 +634,7 @@ export const Gallery = (props: GalleryProps) => {
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageReloadIndicator,
ImageUploadingIndicator,
images,
message,
myMessageTheme,
Expand All @@ -598,6 +647,7 @@ export const Gallery = (props: GalleryProps) => {
VideoThumbnail,
messageHasOnlyOneImage,
messageContentOrder,
pendingAttachmentsUploadingStore,
}}
/>
);
Expand Down
27 changes: 27 additions & 0 deletions package/src/components/Attachment/ImageUploadingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 = (props: ImageUploadingIndicatorProps) => {
const {
theme: {
messageSimple: {
loadingIndicator: { container },
},
semantics,
},
} = useTheme();
const { style, ...rest } = props;
return (
<View
{...rest}
accessibilityHint='image-uploading'
style={[StyleSheet.absoluteFillObject, container, style]}
>
<ActivityIndicator color={semantics.accentPrimary} />
</View>
);
};
Loading
Loading