diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 77de233..7fc75dc 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -3,12 +3,19 @@
+
+
+
+
+
+
NSMicrophoneUsageDescription
VisionCamera needs access to your Microphone to record videos with audio.
+ NSPhotoLibraryUsageDescription
+ VisionCamera needs access to your photo library to save captured videos and photos.
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
diff --git a/example/package-lock.json b/example/package-lock.json
index d73b5d2..2e1c8f2 100644
--- a/example/package-lock.json
+++ b/example/package-lock.json
@@ -1015,6 +1015,11 @@
"prop-types": "^15.5.10"
}
},
+ "@react-native-community/cameraroll": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/cameraroll/-/cameraroll-4.0.2.tgz",
+ "integrity": "sha512-GtSZO6pqUzyZvaYidB5zH90o6Yb9YatapgiMQ+JVdbK4bDD74GdrNGDwyinDTzE5LkAQ90HDoAhVgV/uWt5OrQ=="
+ },
"@react-native-community/cli-debugger-ui": {
"version": "4.13.1",
"resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-4.13.1.tgz",
diff --git a/example/package.json b/example/package.json
index d89b465..30b780c 100644
--- a/example/package.json
+++ b/example/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"@react-native-community/blur": "^3.6.0",
+ "@react-native-community/cameraroll": "^4.0.2",
"react": "16.13.1",
"react-native": "0.63.4",
"react-native-gesture-handler": "^1.10.1",
diff --git a/example/src/Media.tsx b/example/src/Media.tsx
index a24743b..468907b 100644
--- a/example/src/Media.tsx
+++ b/example/src/Media.tsx
@@ -1,34 +1,85 @@
-import React, { useCallback, useMemo } from 'react';
-import { StyleSheet, View, Text, Image, Pressable } from 'react-native';
-import { Navigation, NavigationFunctionComponent } from 'react-native-navigation';
+import React, { useCallback, useMemo, useState } from 'react';
+import { StyleSheet, View, Image, ActivityIndicator, PermissionsAndroid, Platform } from 'react-native';
+import { Navigation, NavigationFunctionComponent, OptionsModalPresentationStyle } from 'react-native-navigation';
import Video from 'react-native-video';
-import { CONTENT_SPACING } from './Constants';
+import { SAFE_AREA_PADDING } from './Constants';
import { useIsForeground } from './hooks/useIsForeground';
import { useIsScreenFocused } from './hooks/useIsScreenFocused';
+import { PressableOpacity } from './views/PressableOpacity';
+import IonIcon from 'react-native-vector-icons/Ionicons';
+import { Alert } from 'react-native';
+import CameraRoll from '@react-native-community/cameraroll';
interface MediaProps {
path: string,
type: 'video' | 'photo'
}
+const requestSavePermission = async (): Promise => {
+ if (Platform.OS !== "android") {
+ return true;
+ }
+ const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE;
+ let hasPermission = await PermissionsAndroid.check(permission);
+ if (!hasPermission) {
+ const permissionRequestResult = await PermissionsAndroid.request(permission);
+ hasPermission = permissionRequestResult === "granted";
+ }
+ return hasPermission;
+}
+
export const Media: NavigationFunctionComponent = ({ componentId, type, path }) => {
+ const [hasMediaLoaded, setHasMediaLoaded] = useState(false);
const isForeground = useIsForeground();
const isScreenFocused = useIsScreenFocused(componentId);
const isVideoPaused = !isForeground || !isScreenFocused;
+ const [savingState, setSavingState] = useState<"none" | "saving" | "saved">("none");
const onClosePressed = useCallback(() => {
Navigation.dismissModal(componentId);
}, [componentId]);
- const source = useMemo(() => ({ uri: `file://${path}` }), [path])
+ const onMediaLoadEnd = useCallback(() => {
+ console.log(`media has loaded.`);
+ setHasMediaLoaded(true);
+ }, []);
+
+ 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) {
+ setSavingState("none");
+ Alert.alert(
+ "Failed to save!",
+ `An unexpected error occured while trying to save your ${type}. ${e?.message ?? JSON.stringify(e)}`
+ );
+ }
+ }, [path, type]);
+
+ const source = useMemo(() => ({ uri: `file://${path}` }), [path]);
+
+ const screenStyle = useMemo(() => ({ opacity: hasMediaLoaded ? 1 : 0 }), [
+ hasMediaLoaded,
+ ]);
return (
-
+
{type === "photo" && (
+ resizeMode="cover"
+ onLoadEnd={onMediaLoadEnd} />
)}
{type === "video" && (
);
}
+Media.options = {
+ modal: {
+ swipeToDismiss: false,
+ },
+ modalPresentationStyle: OptionsModalPresentationStyle.overCurrentContext,
+ animations: {
+ showModal: {
+ waitForRender: true,
+ enabled: false,
+ },
+ dismissModal: {
+ enabled: false,
+ },
+ },
+ layout: {
+ backgroundColor: "transparent",
+ componentBackgroundColor: "transparent",
+ },
+};
+
const styles = StyleSheet.create({
container: {
flex: 1,
@@ -60,9 +165,24 @@ const styles = StyleSheet.create({
},
closeButton: {
position: 'absolute',
- top: CONTENT_SPACING,
- left: CONTENT_SPACING,
+ 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,
+ },
});