// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {type ImageLoadEventData} from 'expo-image'; import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; import {Pressable, View} from 'react-native'; import Animated from 'react-native-reanimated'; import {SvgUri} from 'react-native-svg'; import ExpoImage from '@components/expo_image'; import ExternalImage, {isSVGImage} from '@components/external_image'; import FileIcon from '@components/files/file_icon'; import {GalleryInit} from '@context/gallery'; import {useIsTablet} from '@hooks/device'; import {useGalleryItem} from '@hooks/gallery'; import {lookupMimeType} from '@utils/file'; import {openGalleryAtIndex} from '@utils/gallery'; import {getViewPortWidth, isGifTooLarge} from '@utils/images'; import {urlSafeBase64Encode} from '@utils/security'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {extractFilenameFromUrl, isValidUrl} from '@utils/url'; import {MmBlocksLayoutWidthContext, MmBlocksRenderContext} from './context'; import { computeMmBlocksImageLayout, refineMmBlocksImageLayoutFromIntrinsic, type MmBlocksImageLayout, } from './utils/image'; import type {GalleryItemType} from '@typings/screens/gallery'; export type MmBlocksImageProps = { altText?: string; caps: {maxWidth?: number; maxHeight?: number}; imageMetadata?: PostImage; imageStyle?: 'default' | 'person'; imageUrl: string; theme: Theme; }; type MmBlocksImageContentProps = MmBlocksImageProps & { displaySrc: string; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ errorFrame: { alignItems: 'center', borderColor: changeOpacity(theme.centerChannelColor, 0.2), borderRadius: 4, borderWidth: 1, justifyContent: 'center', }, })); const MmBlocksImageContent = ({ altText, caps, displaySrc, imageMetadata, imageStyle = 'default', imageUrl, theme, }: MmBlocksImageContentProps) => { const layoutWidth = useContext(MmBlocksLayoutWidthContext); const {location, postId} = useContext(MmBlocksRenderContext)!; const galleryIdentifier = `${postId}-MmBlocksImage-${location}`; const isTablet = useIsTablet(); const viewPortWidth = getViewPortWidth(false, isTablet); const style = getStyleSheet(theme); const [loadError, setLoadError] = useState(false); const [intrinsicSize, setIntrinsicSize] = useState<{width: number; height: number} | undefined>(undefined); const isSvg = isSVGImage(imageMetadata, imageUrl); const layout = useMemo((): MmBlocksImageLayout => { if (intrinsicSize && !imageMetadata?.width && !imageMetadata?.height) { return refineMmBlocksImageLayoutFromIntrinsic( intrinsicSize.width, intrinsicSize.height, caps, layoutWidth, viewPortWidth, ); } return computeMmBlocksImageLayout(caps, layoutWidth, imageMetadata, viewPortWidth); }, [caps, imageMetadata, intrinsicSize, layoutWidth, viewPortWidth]); const fileId = useRef(`uid-mm-blocks-image-${urlSafeBase64Encode(imageUrl)}`); const dimensionsStyle = useMemo(() => ({ width: layout.width, height: layout.height, }), [layout.height, layout.width]); const imageBorderRadius = imageStyle === 'person' ? layout.width / 2 : 3; const onPress = useCallback(() => { const item: GalleryItemType = { id: fileId.current, postId, uri: displaySrc, width: layout.galleryWidth, height: layout.galleryHeight, name: altText || extractFilenameFromUrl(imageUrl) || 'image.png', mime_type: lookupMimeType(imageUrl) || 'image/png', type: 'image', lastPictureUpdate: 0, cacheKey: fileId.current, }; openGalleryAtIndex(galleryIdentifier, 0, [item]); }, [altText, displaySrc, galleryIdentifier, imageUrl, layout.galleryHeight, layout.galleryWidth, postId]); const {ref, onGestureEvent, styles: galleryStyles} = useGalleryItem( galleryIdentifier, 0, onPress, ); const onError = useCallback(() => { setLoadError(true); }, []); const onLoad = useCallback((event: ImageLoadEventData) => { const intrinsicWidth = event.source.width; const intrinsicHeight = event.source.height; if (!intrinsicWidth || !intrinsicHeight) { return; } if (imageMetadata?.width && imageMetadata?.height) { return; } setIntrinsicSize({width: intrinsicWidth, height: intrinsicHeight}); }, [imageMetadata?.height, imageMetadata?.width]); if (!isValidUrl(imageUrl) || !displaySrc || loadError || (imageMetadata && isGifTooLarge(imageMetadata))) { return ( ); } const renderImage = () => { if (isSvg) { return ( ); } return ( ); }; const imageContent = ( pressed && {opacity: 0.72}} accessibilityRole='imagebutton' accessibilityLabel={altText} > {renderImage()} ); return ( {imageContent} ); }; const MmBlocksImage = ({imageUrl, imageMetadata, ...contentProps}: MmBlocksImageProps) => { return ( {(displaySrc: string) => ( )} ); }; export default MmBlocksImage;