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
70 changes: 16 additions & 54 deletions examples/SampleApp/src/screens/ChannelImagesScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import Dayjs from 'dayjs';
import { SafeAreaView } from 'react-native-safe-area-context';
import {
DateHeader,
Photo,
useImageGalleryContext,
useOverlayContext,
useTheme,
ImageGalleryState,
useStateStore,
} from 'stream-chat-react-native';

import { ScreenHeader } from '../components/ScreenHeader';
import { usePaginatedAttachments } from '../hooks/usePaginatedAttachments';
import { Picture } from '../icons/Picture';

import type { RouteProp } from '@react-navigation/native';
import type { Attachment } from 'stream-chat';

import type { StackNavigatorParamList } from '../types';

Expand Down Expand Up @@ -61,16 +61,17 @@ export type ChannelImagesScreenProps = {
route: ChannelImagesScreenRouteProp;
};

const selector = (state: ImageGalleryState) => ({
assets: state.assets,
});

export const ChannelImagesScreen: React.FC<ChannelImagesScreenProps> = ({
route: {
params: { channel },
},
}) => {
const {
messages: images,
setMessages: setImages,
setSelectedMessage: setImage,
} = useImageGalleryContext();
const { imageGalleryStateStore } = useImageGalleryContext();
const { assets } = useStateStore(imageGalleryStateStore.state, selector);
const { setOverlay } = useOverlayContext();
const { loading, loadMore, messages } = usePaginatedAttachments(channel, 'image');
const {
Expand All @@ -79,8 +80,6 @@ export const ChannelImagesScreen: React.FC<ChannelImagesScreenProps> = ({
},
} = useTheme();

const channelImages = useRef(images);

const [stickyHeaderDate, setStickyHeaderDate] = useState(
Dayjs(messages?.[0]?.created_at).format('MMM YYYY'),
);
Expand All @@ -106,30 +105,6 @@ export const ChannelImagesScreen: React.FC<ChannelImagesScreenProps> = ({
}
});

/**
* Photos array created from all currently available
* photo attachments
*/
const photos = messages.reduce((acc: Photo[], cur) => {
const attachmentImages =
(cur.attachments as Attachment[])?.filter(
(attachment) =>
attachment.type === 'image' &&
!attachment.title_link &&
!attachment.og_scrape_url &&
(attachment.image_url || attachment.thumb_url),
) || [];

const attachmentPhotos = attachmentImages.map((attachmentImage) => ({
created_at: cur.created_at,
id: `photoId-${cur.id}-${attachmentImage.image_url || attachmentImage.thumb_url}`,
messageId: cur.id,
uri: attachmentImage.image_url || (attachmentImage.thumb_url as string),
}));

return [...acc, ...attachmentPhotos];
}, []);

const messagesWithImages = messages
.map((message) => ({ ...message, groupStyles: [], readBy: false }))
.filter((message) => {
Expand All @@ -145,32 +120,19 @@ export const ChannelImagesScreen: React.FC<ChannelImagesScreenProps> = ({
return false;
});

/**
* This is for the useEffect to run again in the case that a message
* gets edited with more or the same number of images
*/
const imageString = messagesWithImages
.map((message) =>
(message.attachments as Attachment[])
.map((attachment) => attachment.image_url || attachment.thumb_url || '')
.join(),
)
.join();

useEffect(() => {
setImages(messagesWithImages);
const channelImagesCurrent = channelImages.current;
return () => setImages(channelImagesCurrent);
imageGalleryStateStore.openImageGallery({ messages: messagesWithImages });
return () => imageGalleryStateStore.clear();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [imageString, setImages]);
}, [imageGalleryStateStore, messagesWithImages.length]);

return (
<SafeAreaView style={[styles.flex, { backgroundColor: white }]}>
<ScreenHeader inSafeArea titleText='Photos and Videos' />
<View style={styles.flex}>
<FlatList
contentContainerStyle={styles.contentContainer}
data={photos}
data={assets}
keyExtractor={(item, index) => `${item.id}-${index}`}
ListEmptyComponent={EmptyListComponent}
numColumns={3}
Expand All @@ -180,9 +142,9 @@ export const ChannelImagesScreen: React.FC<ChannelImagesScreenProps> = ({
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
setImage({
messageId: item.messageId,
url: item.uri,
imageGalleryStateStore.openImageGallery({
messages: messagesWithImages,
selectedAttachmentUrl: item.uri,
});
setOverlay('gallery');
}}
Expand All @@ -202,7 +164,7 @@ export const ChannelImagesScreen: React.FC<ChannelImagesScreenProps> = ({
viewAreaCoveragePercentThreshold: 50,
}}
/>
{photos && photos.length ? (
{assets.length > 0 ? (
<View style={styles.stickyHeader}>
<DateHeader dateString={stickyHeaderDate} />
</View>
Expand Down
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
]
},
"dependencies": {
"@gorhom/bottom-sheet": "^5.1.8",
"@gorhom/bottom-sheet": "^5.2.8",
"@ungap/structured-clone": "^1.3.0",
"dayjs": "1.11.13",
"emoji-regex": "^10.4.0",
Expand Down
61 changes: 15 additions & 46 deletions package/src/components/Attachment/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ import { isVideoPlayerAvailable } from '../../native';
import { FileTypes } from '../../types/types';
import { getUrlWithoutParams } from '../../utils/utils';

export type GalleryPropsWithContext = Pick<
ImageGalleryContextValue,
'setSelectedMessage' | 'setMessages'
> &
export type GalleryPropsWithContext = Pick<ImageGalleryContextValue, 'imageGalleryStateStore'> &
Pick<
MessageContextValue,
| 'alignment'
Expand All @@ -51,11 +48,11 @@ export type GalleryPropsWithContext = Pick<
| 'onPressIn'
| 'preventPress'
| 'threadList'
| 'message'
> &
Pick<
MessagesContextValue,
| 'additionalPressableProps'
| 'legacyImageViewerSwipeBehaviour'
| 'VideoThumbnail'
| 'ImageLoadingIndicator'
| 'ImageLoadingFailedIndicator'
Expand All @@ -65,19 +62,6 @@ export type GalleryPropsWithContext = Pick<
Pick<OverlayContextValue, 'setOverlay'> & {
channelId: string | undefined;
hasThreadReplies?: boolean;
/**
* `message` prop has been introduced here as part of `legacyImageViewerSwipeBehaviour` prop.
* https://github.com/GetStream/stream-chat-react-native/commit/d5eac6193047916f140efe8e396a671675c9a63f
* messageId and messageText may seem redundant now, but to avoid breaking change as part
* of minor release, we are keeping those props.
*
* Also `message` type should ideally be imported from MessageContextValue and not be explicitely mentioned
* here, but due to some circular dependencies within the SDK, it causes "excessive deep nesting" issue with
* typescript within Channel component. We should take it as a mini-project and resolve all these circular imports.
*
* TODO: Fix circular dependencies of imports
*/
message?: LocalMessage;
};

const GalleryWithContext = (props: GalleryPropsWithContext) => {
Expand All @@ -86,19 +70,17 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
alignment,
groupStyles,
hasThreadReplies,
imageGalleryStateStore,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageReloadIndicator,
images,
legacyImageViewerSwipeBehaviour,
message,
onLongPress,
onPress,
onPressIn,
preventPress,
setMessages,
setOverlay,
setSelectedMessage,
threadList,
videos,
VideoThumbnail,
Expand Down Expand Up @@ -204,13 +186,13 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
additionalPressableProps={additionalPressableProps}
borderRadius={borderRadius}
colIndex={colIndex}
imageGalleryStateStore={imageGalleryStateStore}
ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
ImageLoadingIndicator={ImageLoadingIndicator}
ImageReloadIndicator={ImageReloadIndicator}
imagesAndVideos={imagesAndVideos}
invertedDirections={invertedDirections || false}
key={rowIndex}
legacyImageViewerSwipeBehaviour={legacyImageViewerSwipeBehaviour}
message={message}
numOfColumns={numOfColumns}
numOfRows={numOfRows}
Expand All @@ -219,9 +201,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => {
onPressIn={onPressIn}
preventPress={preventPress}
rowIndex={rowIndex}
setMessages={setMessages}
setOverlay={setOverlay}
setSelectedMessage={setSelectedMessage}
thumbnail={thumbnail}
VideoThumbnail={VideoThumbnail}
/>
Expand Down Expand Up @@ -252,26 +232,25 @@ type GalleryThumbnailProps = {
} & Pick<
MessagesContextValue,
| 'additionalPressableProps'
| 'legacyImageViewerSwipeBehaviour'
| 'VideoThumbnail'
| 'ImageLoadingIndicator'
| 'ImageLoadingFailedIndicator'
| 'ImageReloadIndicator'
> &
Pick<ImageGalleryContextValue, 'setSelectedMessage' | 'setMessages'> &
Pick<ImageGalleryContextValue, 'imageGalleryStateStore'> &
Pick<MessageContextValue, 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'> &
Pick<OverlayContextValue, 'setOverlay'>;

const GalleryThumbnail = ({
additionalPressableProps,
borderRadius,
colIndex,
imageGalleryStateStore,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageReloadIndicator,
imagesAndVideos,
invertedDirections,
legacyImageViewerSwipeBehaviour,
message,
numOfColumns,
numOfRows,
Expand All @@ -280,9 +259,7 @@ const GalleryThumbnail = ({
onPressIn,
preventPress,
rowIndex,
setMessages,
setOverlay,
setSelectedMessage,
thumbnail,
VideoThumbnail,
}: GalleryThumbnailProps) => {
Expand All @@ -304,17 +281,14 @@ const GalleryThumbnail = ({
const { t } = useTranslationContext();

const openImageViewer = () => {
if (!legacyImageViewerSwipeBehaviour && message) {
// Added if-else to keep the logic readable, instead of DRY.
// if - legacyImageViewerSwipeBehaviour is disabled
// else - legacyImageViewerSwipeBehaviour is enabled
setMessages([message]);
setSelectedMessage({ messageId: message.id, url: thumbnail.url });
setOverlay('gallery');
} else if (legacyImageViewerSwipeBehaviour) {
setSelectedMessage({ messageId: message?.id, url: thumbnail.url });
setOverlay('gallery');
if (!message) {
return;
}
imageGalleryStateStore.openImageGallery({
messages: [message],
selectedAttachmentUrl: thumbnail.url,
});
setOverlay('gallery');
};

const defaultOnPress = () => {
Expand Down Expand Up @@ -585,13 +559,12 @@ export const Gallery = (props: GalleryProps) => {
onPressIn: propOnPressIn,
preventPress: propPreventPress,
setOverlay: propSetOverlay,
setSelectedMessage: propSetSelectedMessage,
threadList: propThreadList,
videos: propVideos,
VideoThumbnail: PropVideoThumbnail,
} = props;

const { setMessages, setSelectedMessage: contextSetSelectedMessage } = useImageGalleryContext();
const { imageGalleryStateStore } = useImageGalleryContext();
const {
alignment: contextAlignment,
groupStyles: contextGroupStyles,
Expand All @@ -609,7 +582,6 @@ export const Gallery = (props: GalleryProps) => {
ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator,
ImageLoadingIndicator: ContextImageLoadingIndicator,
ImageReloadIndicator: ContextImageReloadIndicator,
legacyImageViewerSwipeBehaviour,
myMessageTheme: contextMyMessageTheme,
VideoThumbnail: ContextVideoThumnbnail,
} = useMessagesContext();
Expand All @@ -631,7 +603,6 @@ export const Gallery = (props: GalleryProps) => {
const onPress = propOnPress || contextOnPress;
const preventPress =
typeof propPreventPress === 'boolean' ? propPreventPress : contextPreventPress;
const setSelectedMessage = propSetSelectedMessage || contextSetSelectedMessage;
const setOverlay = propSetOverlay || contextSetOverlay;
const threadList = propThreadList || contextThreadList;
const VideoThumbnail = PropVideoThumbnail || ContextVideoThumnbnail;
Expand All @@ -649,20 +620,18 @@ export const Gallery = (props: GalleryProps) => {
channelId: message?.cid,
groupStyles,
hasThreadReplies: hasThreadReplies || !!message?.reply_count,
imageGalleryStateStore,
ImageLoadingFailedIndicator,
ImageLoadingIndicator,
ImageReloadIndicator,
images,
legacyImageViewerSwipeBehaviour,
message,
myMessageTheme,
onLongPress,
onPress,
onPressIn,
preventPress,
setMessages,
setOverlay,
setSelectedMessage,
threadList,
videos,
VideoThumbnail,
Expand Down
Loading
Loading