import React, { useCallback, useMemo, useState } from 'react'; import { StyleSheet, View, Image, ActivityIndicator, PermissionsAndroid, Platform } from 'react-native'; import Video, { LoadError, OnLoadData } from 'react-native-video'; import { SAFE_AREA_PADDING } from './Constants'; import { useIsForeground } from './hooks/useIsForeground'; import { PressableOpacity } from 'react-native-pressable-opacity'; import IonIcon from 'react-native-vector-icons/Ionicons'; import { Alert } from 'react-native'; import CameraRoll from '@react-native-community/cameraroll'; import { StatusBarBlurBackground } from './views/StatusBarBlurBackground'; import type { NativeSyntheticEvent } from 'react-native'; import type { ImageLoadEventData } from 'react-native'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { Routes } from './Routes'; import { useIsFocused } from '@react-navigation/core'; const requestSavePermission = async (): Promise => { if (Platform.OS !== 'android') return true; const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE; if (permission == null) return false; let hasPermission = await PermissionsAndroid.check(permission); if (!hasPermission) { const permissionRequestResult = await PermissionsAndroid.request(permission); hasPermission = permissionRequestResult === 'granted'; } return hasPermission; }; const isVideoOnLoadEvent = (event: OnLoadData | NativeSyntheticEvent): event is OnLoadData => 'duration' in event && 'naturalSize' in event; type Props = NativeStackScreenProps; export function MediaPage({ navigation, route }: Props): React.ReactElement { const { path, type } = route.params; const [hasMediaLoaded, setHasMediaLoaded] = useState(false); const isForeground = useIsForeground(); const isScreenFocused = useIsFocused(); const isVideoPaused = !isForeground || !isScreenFocused; const [savingState, setSavingState] = useState<'none' | 'saving' | 'saved'>('none'); const onClosePressed = useCallback(() => { navigation.goBack(); }, [navigation]); const onMediaLoad = useCallback((event: OnLoadData | NativeSyntheticEvent) => { if (isVideoOnLoadEvent(event)) { console.log( `Video loaded. Size: ${event.naturalSize.width}x${event.naturalSize.height} (${event.naturalSize.orientation}, ${event.duration} seconds)`, ); } else { console.log(`Image loaded. Size: ${event.nativeEvent.source.width}x${event.nativeEvent.source.height}`); } }, []); const onMediaLoadEnd = useCallback(() => { console.log('media has loaded.'); setHasMediaLoaded(true); }, []); const onMediaLoadError = useCallback((error: LoadError) => { console.log(`failed to load media: ${JSON.stringify(error)}`); }, []); const onSavePressed = useCallback(async () => { try { setSavingState('saving'); const hasPermission = await requestSavePermission(); if (!hasPermission) { Alert.alert('Permission denied!', 'Vision Camera does not have permission to save the media to your camera roll.'); return; } await CameraRoll.save(`file://${path}`, { type: type, }); setSavingState('saved'); } catch (e) { const message = e instanceof Error ? e.message : JSON.stringify(e); setSavingState('none'); Alert.alert('Failed to save!', `An unexpected error occured while trying to save your ${type}. ${message}`); } }, [path, type]); const source = useMemo(() => ({ uri: `file://${path}` }), [path]); const screenStyle = useMemo(() => ({ opacity: hasMediaLoaded ? 1 : 0 }), [hasMediaLoaded]); return ( {type === 'photo' && ( )} {type === 'video' && ( ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'white', }, closeButton: { position: 'absolute', top: SAFE_AREA_PADDING.paddingTop, left: SAFE_AREA_PADDING.paddingLeft, width: 40, height: 40, }, saveButton: { position: 'absolute', bottom: SAFE_AREA_PADDING.paddingBottom, left: SAFE_AREA_PADDING.paddingLeft, width: 40, height: 40, }, icon: { textShadowColor: 'black', textShadowOffset: { height: 0, width: 0, }, textShadowRadius: 1, }, });