import { Portal } from "@gorhom/portal"; import { Asset } from "expo-asset"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { Pressable, Text, View } from "react-native"; const CLOSE_THRESHOLD_SECONDS = 0.35; const CLOSE_POLL_INTERVAL_MS = 500; const overlayStyle = { position: "fixed", top: 0, right: 0, bottom: 0, left: 0, backgroundColor: "black", justifyContent: "center", alignItems: "center", zIndex: 9999, }; const videoStyle = { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", objectFit: "cover", }; const closeButtonStyle = { position: "absolute", top: 50, right: 20, backgroundColor: "#00000080", paddingVertical: 10, paddingHorizontal: 14, borderRadius: 20, borderWidth: 1, borderColor: "#FFFFFF55", cursor: "pointer", }; const closeTextStyle = { color: "#FFF", fontSize: 14, }; const resolveModuleUri = async (module) => { const asset = Asset.fromModule(module); if (!asset.localUri && !asset.uri) { await asset.downloadAsync(); } return asset.localUri ?? asset.uri ?? null; }; const FullscreenIntroVideo = ({ url, visible = true, onClose }) => { const videoRef = useRef(null); const [uri, setUri] = useState(null); const [muted, setMuted] = useState(false); const hasClosedRef = useRef(false); const handleClose = useCallback(() => { if (hasClosedRef.current) return; hasClosedRef.current = true; onClose?.(); }, [onClose]); useEffect(() => { let isMounted = true; const assignUri = (nextUri) => { if (isMounted) { setMuted(false); setUri(nextUri); } }; if (typeof url === "string") { assignUri(url); return () => { isMounted = false; }; } const load = async () => { try { const nextUri = await resolveModuleUri(url); assignUri(nextUri); } catch { if (isMounted) { setUri(null); } } }; load(); return () => { isMounted = false; }; }, [url]); useEffect(() => { if (!visible) { return undefined; } hasClosedRef.current = false; const video = videoRef.current; if (!video || !uri) { return undefined; } const shouldClose = () => { if (hasClosedRef.current) { return false; } if (video.ended) { return true; } const remaining = video.duration - video.currentTime; return Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS; }; const tryHandleClose = () => { if (shouldClose()) { handleClose(); } }; const handleEnded = tryHandleClose; const handleTimeUpdate = tryHandleClose; const handlePause = tryHandleClose; video.addEventListener("ended", handleEnded); video.addEventListener("timeupdate", handleTimeUpdate); video.addEventListener("pause", handlePause); const pollId = setInterval(tryHandleClose, CLOSE_POLL_INTERVAL_MS); video.currentTime = 0; const attemptPlay = () => { const result = video.play(); if (result?.catch) { result.catch((error) => { if (error?.name === "NotAllowedError" && !muted) { setMuted(true); } }); } }; attemptPlay(); return () => { video.pause(); video.removeEventListener("ended", handleEnded); video.removeEventListener("timeupdate", handleTimeUpdate); video.removeEventListener("pause", handlePause); clearInterval(pollId); }; }, [handleClose, muted, uri, visible]); useEffect(() => { const video = videoRef.current; if (!video || !muted) { return; } const result = video.play(); if (result?.catch) { result.catch(() => {}); } }, [muted]); if (!visible) { return null; } return ( {uri ? ( ); }; export default FullscreenIntroVideo;