import React, { useEffect, useRef, useState } from "react"; import { ActivityIndicator, Image, View, StyleSheet, Platform, ImageSourcePropType, ImageStyle, ViewStyle, } from "react-native"; import { ImageViewerModal } from "../CometChatImageViewerModal"; import { CommonUtils } from "../../utils/CommonUtils"; /** * Props for the CometChatImageLoader component. */ export interface CometChatImageLoaderPropType { /** * Image URL passed as an object with a `uri` property. * Example: `{ uri: "dummyUrl" }` */ imageUrl: ImageSourcePropType; /** * Thumbnail image URL. * * @type {ImageSourcePropType} * @description Thumbnail image to display while the full image loads. */ thumbnailUrl?: ImageSourcePropType; /** * Placeholder image to display while loading. */ placeHolderImage?: ImageSourcePropType; /** * Custom callback function to execute when the image is pressed. */ onPress?: Function; /** * Custom style for the image. */ style?: ImageStyle; /** * Resize mode for the image. * * @default "cover" */ imageResizeMode?: "cover" | "contain" | "stretch" | "repeat" | "center"; /** * Size of the activity indicator. */ activityIndicatorSize: number; /** * Custom style for the view containing the activity indicator. */ activityIndicatorViewStyle: ViewStyle; } /** * CometChatImageLoader is a component that displays an image with an activity indicator * until the image is loaded. It supports thumbnail prefetching and opens an image viewer * modal on a quick tap. * * Props for the component. * The rendered image loader component. */ export const CometChatImageLoader = (props: CometChatImageLoaderPropType) => { const { thumbnailUrl, imageUrl, style, imageResizeMode, activityIndicatorSize, activityIndicatorViewStyle, } = props; const [isLoaded, setIsLoaded] = useState(false); const [isVisible, setIsVisible] = useState(false); const [imageSource, setImageSource] = useState(); // Ref to record touch press time for detecting quick taps const pressTime = useRef(0); /** * Handles the touch start event. */ const handleTouchStart = () => { pressTime.current = Date.now(); }; /** * Handles the touch end event and opens the image viewer if tap duration is short. */ const handleTouchEnd = () => { if (pressTime.current === null && Platform.OS === "ios") return; const endTime = Date.now(); const pressDuration = endTime - pressTime.current!; if (pressDuration < 500) { setIsVisible(true); } }; /** * Handles the touch move event. On iOS, cancels the tap detection. */ const onTouchMove = () => { if (Platform.OS === "ios") { pressTime.current = null; } }; useEffect(() => { // Prefetch the thumbnail if available, else fallback to the full image if (thumbnailUrl && typeof thumbnailUrl === "object" && "uri" in thumbnailUrl) { CommonUtils.prefetchThumbnail(thumbnailUrl.uri!).then((success: any) => { if (success) { setImageSource(thumbnailUrl); } else { setImageSource(imageUrl); // Fallback to original imageUrl if prefetch fails } }); } else { setImageSource(imageUrl); // No thumbnail available, fallback to imageUrl } }, [thumbnailUrl, imageUrl]); return ( <> {isVisible && ( { setIsVisible(false); }} /> )} {/* Render the image. It is initially hidden until loaded. */} setIsLoaded(true)} /> {/* Render the activity indicator until the image is loaded */} {!isLoaded && ( )} ); }; // Default styles for the component const styles = StyleSheet.create({ container: { position: "relative", // Use relative positioning for the container justifyContent: "center", alignItems: "center", }, loader: { position: "absolute", // Keep the loader centered over the image zIndex: 1, // Ensure it is rendered above the image until the image loads }, image: { position: "absolute", // The image remains fixed in place while loading }, });