import React, { useEffect, useRef, useState } from "react"; import { ImageBackground, View } from "react-native"; import { useStyles } from "./MediaView.style"; import type { FormattedFile } from "../../types"; import { StatusCode } from "../../types"; import { computeA11yLabel } from "../../utils"; import { ActivityIndicator } from "../../../ActivityIndicator"; import { Icon } from "../../../Icon"; import { ProgressBar } from "../ProgressBar"; import { ErrorIcon } from "../ErrorIcon"; import { useAtlantisFormatFileContext } from "../../context/FormatFileContext"; import { useAtlantisI18n } from "../../../hooks/useAtlantisI18n"; interface MediaViewProps { readonly accessibilityLabel?: string; readonly showOverlay: boolean; readonly showError: boolean; readonly file: FormattedFile; readonly styleInGrid: boolean; readonly onUploadComplete: () => void; readonly onLoadEnd?: () => void; readonly surfaceColor?: string; } // eslint-disable-next-line max-statements export function MediaView({ accessibilityLabel, showOverlay, showError, file, styleInGrid, onUploadComplete, onLoadEnd, surfaceColor, }: MediaViewProps) { const { t } = useAtlantisI18n(); const { useCreateThumbnail } = useAtlantisFormatFileContext(); const { thumbnail, error } = useCreateThumbnail(file); const [isLoading, setIsLoading] = useState(false); const [decodeFailed, setDecodeFailed] = useState(false); /** * Tracks whether onLoadEnd has fired to prevent race conditions. * ImageBackground can fire onLoadEnd before onLoadStart when loading cached images, * which would cause isLoading to get stuck at true, showing an infinite spinner. */ const hasLoadedRef = useRef(false); const hasNotifiedLoadEndRef = useRef(false); const a11yLabel = computeA11yLabel({ accessibilityLabel, showOverlay, showError, t, }); const hasError = showError || error, uri = thumbnail || file.thumbnailUrl || file.source, styles = useStyles(); const handleLoadStart = () => { if (!hasLoadedRef.current) { setIsLoading(true); } }; const handleLoadEnd = () => { hasLoadedRef.current = true; setIsLoading(false); }; const handleMediaLoadEnd = () => { handleLoadEnd(); if (!hasNotifiedLoadEndRef.current) { hasNotifiedLoadEndRef.current = true; onLoadEnd?.(); } }; useEffect(() => { hasLoadedRef.current = false; hasNotifiedLoadEndRef.current = false; setIsLoading(false); setDecodeFailed(false); }, [uri]); if (isVideo(file.type) && (!uri || decodeFailed)) { return ( ); } return ( { handleMediaLoadEnd(); setDecodeFailed(true); }} > ); } interface OverlayProps { readonly isLoading: boolean; readonly showOverlay: boolean; readonly hasError: boolean; readonly file: FormattedFile; readonly onUploadComplete: () => void; readonly styles: ReturnType; readonly styleInGrid: boolean; } function Overlay({ isLoading, showOverlay, hasError, file, onUploadComplete, styles, styleInGrid, }: OverlayProps) { if (isLoading) { return ; } if (hasError) return ; if (showOverlay) { return ( ); } if (isVideo(file.type) && file.showFileTypeIndicator) { return ; } return null; } interface ProgressOverlayProps { readonly status: StatusCode; readonly progress: number; readonly onUploadComplete: () => void; readonly styles: ReturnType; } function ProgressOverlay({ status, progress, onUploadComplete, styles, }: ProgressOverlayProps) { const freezeProgressBar = status !== StatusCode.Completed && progress >= 0.9; return ( ); } function ErrorOverlay({ styles, }: { readonly styles: ReturnType; }) { return ( ); } function isVideo(fileType = ""): boolean { return fileType.includes("video"); } interface VideoPlaceholderProps { readonly a11yLabel: string; readonly styleInGrid: boolean; readonly surfaceColor?: string; readonly styles: ReturnType; readonly isLoading: boolean; readonly showOverlay: boolean; readonly hasError: boolean; readonly file: FormattedFile; readonly onUploadComplete: () => void; } function VideoPlaceholder({ a11yLabel, styleInGrid, surfaceColor, styles, isLoading, showOverlay, hasError, file, onUploadComplete, }: VideoPlaceholderProps) { return ( {file.showFileTypeIndicator && } ); } function PlaceholderStatusOverlay({ isLoading, showOverlay, hasError, file, onUploadComplete, styles, styleInGrid, }: OverlayProps) { if (isLoading) { return ; } if (hasError) return ; if (showOverlay) { return ( ); } return null; }