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;
}