mattermost-mobile/app/components/files/files.tsx
Elias Nahum 2f3dfbbbfa
Update dependencies and upgrade to RN 0.76.5 (#8421)
* update dev deps

* partial update dependencies

* update watermelondb

* update rn to 0.76.5, expo to 52.0.18 and others

* upgrade android firebase

* upgrade detox deps

* fix package-lock.json

* update emm and paste-input

* update turbo-log

* update network library

* fix tests

* review feedback

* fix Keyboard blocking signIn button

* Fall back to iphone 14 iOS 17.2 simulator as app crashes on iOS 17.4

* changes in deleteCredentialsForServer is causing a crash

* withOptions x2 clearly is wrong

* re-add cli-platform-apple fix

* fix: RN 0.76.5 issue with bottom sheet disappearing (#8478)

* experiment, using view vs BottomSheetView

* revert previous & try disabling enableDynamicSizing

* revert an unintended removal

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
2025-01-16 07:11:32 -07:00

155 lines
5.3 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useMemo, useState} from 'react';
import {DeviceEventEmitter, type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native';
import Animated from 'react-native-reanimated';
import {Events} from '@constants';
import {GalleryInit} from '@context/gallery';
import {useIsTablet} from '@hooks/device';
import {useImageAttachments} from '@hooks/files';
import {isImage, isVideo} from '@utils/file';
import {fileToGalleryItem, openGalleryAtIndex} from '@utils/gallery';
import {getViewPortWidth} from '@utils/images';
import {preventDoubleTap} from '@utils/tap';
import File from './file';
type FilesProps = {
canDownloadFiles: boolean;
failed?: boolean;
filesInfo: FileInfo[];
layoutWidth?: number;
location: string;
isReplyPost: boolean;
postId?: string;
postProps?: Record<string, unknown>;
publicLinkEnabled: boolean;
}
const MAX_VISIBLE_ROW_IMAGES = 4;
const styles = StyleSheet.create({
row: {
flex: 1,
flexDirection: 'row',
marginTop: 5,
},
container: {
flex: 1,
},
gutter: {
marginLeft: 8,
},
failed: {
opacity: 0.5,
},
marginTop: {
marginTop: 10,
},
});
const Files = ({canDownloadFiles, failed, filesInfo, isReplyPost, layoutWidth, location, postId, postProps, publicLinkEnabled}: FilesProps) => {
const galleryIdentifier = `${postId}-fileAttachments-${location}`;
const [inViewPort, setInViewPort] = useState(false);
const isTablet = useIsTablet();
const {images: imageAttachments, nonImages: nonImageAttachments} = useImageAttachments(filesInfo, publicLinkEnabled);
const filesForGallery = useMemo(() => imageAttachments.concat(nonImageAttachments),
[imageAttachments, nonImageAttachments]);
const attachmentIndex = (fileId: string) => {
return filesForGallery.findIndex((file) => file.id === fileId) || 0;
};
const handlePreviewPress = preventDoubleTap((idx: number) => {
const items = filesForGallery.map((f) => fileToGalleryItem(f, f.user_id, postProps));
openGalleryAtIndex(galleryIdentifier, idx, items);
});
const updateFileForGallery = (idx: number, file: FileInfo) => {
'worklet';
filesForGallery[idx] = file;
};
const isSingleImage = useMemo(() => filesInfo.filter((f) => isImage(f) || isVideo(f)).length === 1, [filesInfo]);
const renderItems = (items: FileInfo[], moreImagesCount = 0, includeGutter = false) => {
let nonVisibleImagesCount: number;
let container: StyleProp<ViewStyle> = items.length > 1 ? styles.container : undefined;
const containerWithGutter = [container, styles.gutter];
return items.map((file, idx) => {
if (moreImagesCount && idx === MAX_VISIBLE_ROW_IMAGES - 1) {
nonVisibleImagesCount = moreImagesCount;
}
if (idx !== 0 && includeGutter) {
container = containerWithGutter;
}
return (
<View
style={[container, styles.marginTop]}
key={file.id}
>
<File
galleryIdentifier={galleryIdentifier}
key={file.id}
canDownloadFiles={canDownloadFiles}
file={file}
index={attachmentIndex(file.id!)}
onPress={handlePreviewPress}
isSingleImage={isSingleImage}
nonVisibleImagesCount={nonVisibleImagesCount}
publicLinkEnabled={publicLinkEnabled}
updateFileForGallery={updateFileForGallery}
wrapperWidth={layoutWidth || (getViewPortWidth(isReplyPost, isTablet) - 6)}
inViewPort={inViewPort}
/>
</View>
);
});
};
const renderImageRow = () => {
if (imageAttachments.length === 0) {
return null;
}
const visibleImages = imageAttachments.slice(0, MAX_VISIBLE_ROW_IMAGES);
const portraitPostWidth = layoutWidth || (getViewPortWidth(isReplyPost, isTablet) - 6);
let nonVisibleImagesCount;
if (imageAttachments.length > MAX_VISIBLE_ROW_IMAGES) {
nonVisibleImagesCount = imageAttachments.length - MAX_VISIBLE_ROW_IMAGES;
}
return (
<View style={[styles.row, {width: portraitPostWidth}]}>
{ renderItems(visibleImages, nonVisibleImagesCount, true) }
</View>
);
};
useEffect(() => {
const onScrollEnd = DeviceEventEmitter.addListener(Events.ITEM_IN_VIEWPORT, (viewableItems) => {
if (`${location}-${postId}` in viewableItems) {
setInViewPort(true);
}
});
return () => onScrollEnd.remove();
}, []);
return (
<GalleryInit galleryIdentifier={galleryIdentifier}>
<Animated.View style={[failed && styles.failed]}>
{renderImageRow()}
{renderItems(nonImageAttachments)}
</Animated.View>
</GalleryInit>
);
};
export default React.memo(Files);