diff --git a/components/media-cache-overlay/media-cache-overlay.component.jsx b/components/media-cache-overlay/media-cache-overlay.component.jsx new file mode 100644 index 0000000..65f6c2f --- /dev/null +++ b/components/media-cache-overlay/media-cache-overlay.component.jsx @@ -0,0 +1,85 @@ +import { Button, View } from "native-base"; +import React from "react"; +import { Alert, Modal, StyleSheet, Text } from "react-native"; +import ImageViewer from "react-native-image-zoom-viewer"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { removePhotos } from "../../redux/photos/photos.actions"; +import { selectPhotos } from "../../redux/photos/photos.selectors"; + +const mapStateToProps = createStructuredSelector({ + photos: selectPhotos, +}); +const mapDispatchToProps = (dispatch) => ({ + removePhotos: (ids) => dispatch(removePhotos(ids)), +}); + +export function MediaCacheOverlay({ + photos, + removePhotos, + previewVisible, + setPreviewVisible, + imgIndex, + setImgIndex, +}) { + return ( + { + Alert.alert("Modal has been closed."); + }} + visible={previewVisible} + transparent={true} + > + setPreviewVisible(false)} + index={imgIndex} + onChange={(index) => setImgIndex(index)} + style={{ display: "flex" }} + renderFooter={(index) => ( + + {index} This is the thing. + + + )} + enableSwipeDown + enablePreload + imageUrls={photos.map((p) => { + return { url: p.uri }; + })} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + actions: { + display: "flex", + flexDirection: "row", + justifyContent: "space-evenly", + }, + listContentContainer: { + flex: 1, + }, + thumbnail: { + width: 10, + height: 10, + backgroundColor: "tomato", + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(MediaCacheOverlay); diff --git a/components/screen-camera/screen-camera.component.jsx b/components/screen-camera/screen-camera.component.jsx index eacc253..f755412 100644 --- a/components/screen-camera/screen-camera.component.jsx +++ b/components/screen-camera/screen-camera.component.jsx @@ -1,5 +1,5 @@ import { Ionicons } from "@expo/vector-icons"; -import { useNavigation } from "@react-navigation/native"; +import { useFocusEffect, useNavigation } from "@react-navigation/native"; import { Camera } from "expo-camera"; import * as FileSystem from "expo-file-system"; import React, { useEffect, useRef, useState } from "react"; @@ -28,9 +28,22 @@ export function ScreenCamera({ cameraJobId, cameraJob, addPhoto }) { flashMode: Camera.Constants.FlashMode.off, capturing: null, cameraType: Camera.Constants.Type.back, + tabHasFocus: null, }); const cameraRef = useRef(null); + useFocusEffect( + React.useCallback(() => { + // Do something when the screen is focused + setState({ ...state, tabHasFocus: true }); + return () => { + // Do something when the screen is unfocused + // Useful for cleanup functions + setState({ ...state, tabHasFocus: false }); + }; + }, []) + ); + useEffect(() => { (async () => { const { status } = await Camera.requestPermissionsAsync(); @@ -101,7 +114,7 @@ export function ScreenCamera({ cameraJobId, cameraJob, addPhoto }) { } }; - if (hasPermission === null) { + if (hasPermission === null || !state.tabHasFocus) { return ; } diff --git a/components/screen-media-cache/screen-media-cache.component.jsx b/components/screen-media-cache/screen-media-cache.component.jsx index 54d058a..f114904 100644 --- a/components/screen-media-cache/screen-media-cache.component.jsx +++ b/components/screen-media-cache/screen-media-cache.component.jsx @@ -1,15 +1,13 @@ +import * as FileSystem from "expo-file-system"; import { Button, Text as NBText, Thumbnail, View } from "native-base"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { - Alert, FlatList, - Modal, SafeAreaView, StyleSheet, Text, TouchableOpacity, } from "react-native"; -import ImageViewer from "react-native-image-zoom-viewer"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { @@ -17,6 +15,7 @@ import { uploadAllPhotos, } from "../../redux/photos/photos.actions"; import { selectPhotos } from "../../redux/photos/photos.selectors"; +import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component"; const mapStateToProps = createStructuredSelector({ photos: selectPhotos, @@ -29,11 +28,25 @@ const mapDispatchToProps = (dispatch) => ({ export function ScreenMediaCache({ photos, removeAllPhotos, uploadAllphotos }) { const [previewVisible, setPreviewVisible] = useState(false); const [imgIndex, setImgIndex] = useState(0); + const [imagesInDir, setImagesInDir] = useState([]); + + useEffect(() => { + const check = async () => { + setImagesInDir( + await FileSystem.readDirectoryAsync( + FileSystem.documentDirectory + "photos" + ) + ); + }; + photos.length; + check(); + }, [photos]); + return ( item.id} numColumns={5} @@ -52,6 +65,12 @@ export function ScreenMediaCache({ photos, removeAllPhotos, uploadAllphotos }) { ) } /> + item.id} + renderItem={(object) => {object.item}} + /> {`${photos.length} Photos`} - { - Alert.alert("Modal has been closed."); - }} - visible={previewVisible} - transparent={true} - > - setPreviewVisible(false)} - index={imgIndex} - onChange={(index) => setImgIndex(index)} - style={{ display: "flex" }} - renderFooter={(index) => ( - - {index} This is the thing. - - )} - enableSwipeDown - enablePreload - imageUrls={photos.map((p) => { - return { url: p.uri }; - })} - /> - + ); } diff --git a/redux/photos/photos.sagas.js b/redux/photos/photos.sagas.js index 6a53ff3..bf3ad3d 100644 --- a/redux/photos/photos.sagas.js +++ b/redux/photos/photos.sagas.js @@ -3,6 +3,25 @@ import { all, call, select, takeLatest } from "redux-saga/effects"; import { handleUpload } from "../../util/document-upload.utility"; import PhotosActionTypes from "./photos.types"; +export function* onRemovePhotos() { + yield takeLatest(PhotosActionTypes.REMOVE_PHOTOS, removePhotosAction); +} +export function* removePhotosAction({ payload: photoIdsToRemove }) { + try { + const photos = yield select((state) => state.photos.photos); + const fps = photos + .filter((p) => !photoIdsToRemove.includes(p.id)) + .map((p) => p.uri); + + const all = []; + fps.forEach((f) => all.push(FileSystem.deleteAsync(f))); + + yield Promise.all(all); + } catch (error) { + console.log("Saga Error: removePhotos", error); + } +} + export function* onRemoveAllPhotos() { yield takeLatest(PhotosActionTypes.REMOVE_ALL_PHOTOS, removeAllPhotosAction); } @@ -30,6 +49,7 @@ export function* onUploadAllPhotos() { uploadAllPhotosAction ); } + export function* uploadAllPhotosAction() { try { const photos = yield select((state) => state.photos.photos); @@ -38,41 +58,39 @@ export function* uploadAllPhotosAction() { const actions = []; photos.forEach(async (p) => actions.push( - handleUpload( + await handleUpload( { file: await (await fetch(p.uri)).blob(), - - onError: (props) => { - console.log("Error Callback", props); - }, - onProgress: (props) => { - console.log("Progress Calback", props); - }, - onSuccess: (props) => { - console.log("Success Calback", props); - }, + onError: handleOnError, + onProgress: handleOnProgress, + onSuccess: handleOnSuccess, }, { bodyshop: bodyshop, jobId: p.jobId, uploaded_by: user.currentUser.email, - callback: (props) => { - console.log("Context Callback", props); - }, - photo: { - ...p, - name: p.uri.substring(p.uri.lastIndexOf("/") + 1), - }, + photo: p, } ) ) ); yield Promise.all(actions); + console.log("function*uploadAllPhotosAction -> actions", actions); } catch (error) { console.log("Saga Error: onRemoveAllPhotos", error); } } +function handleOnError(...props) { + console.log("HandleOnError", props); +} +function handleOnProgress(...props) { + console.log("HandleOnProgress", props); +} +function handleOnSuccess(...props) { + console.log("handleOnSuccess", props); +} + export function* photosSagas() { yield all([call(onRemoveAllPhotos), call(onUploadAllPhotos)]); } diff --git a/util/document-upload.utility.js b/util/document-upload.utility.js index 75bfaa0..a7f8da6 100644 --- a/util/document-upload.utility.js +++ b/util/document-upload.utility.js @@ -9,7 +9,7 @@ import { axiosAuthInterceptorId } from "./CleanAxios"; var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); -export const handleUpload = (ev, context) => { +export const handleUpload = async (ev, context) => { const { onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; @@ -18,7 +18,7 @@ export const handleUpload = (ev, context) => { "" )}`; - uploadToCloudinary( + return uploadToCloudinary( key, ev.file.type, ev.file, @@ -70,18 +70,14 @@ export const uploadToCloudinary = async ( ); } catch (error) { console.log("ERROR GETTING SIGNED URL", error); - return; + return { success: false, error: error }; } if (signedURLResponse.status !== 200) { console.log("Error Getting Signed URL", signedURLResponse.statusText); if (!!onError) onError(signedURLResponse.statusText); - // notification["error"]({ - // message: i18n.t("documents.errors.getpresignurl", { - // message: signedURLResponse.statusText, - // }), - // }); - return; + + return { success: false, error: signedURLResponse.statusText }; } //Build request to end to cloudinary. @@ -94,11 +90,6 @@ export const uploadToCloudinary = async ( }; const formData = new FormData(); - console.log("Sending!", { - uri: photo.uri, - type: fileType, - name: file.data.name, - }); formData.append("file", { uri: photo.uri, type: fileType, @@ -126,6 +117,7 @@ export const uploadToCloudinary = async ( console.log("Cloudinary Upload Response", cloudinaryUploadResponse.data); } catch (error) { console.log("CLOUDINARY error", error, cloudinaryUploadResponse); + return { success: false, error: error }; } if (cloudinaryUploadResponse.status !== 200) { @@ -135,12 +127,7 @@ export const uploadToCloudinary = async ( cloudinaryUploadResponse ); if (!!onError) onError(cloudinaryUploadResponse.statusText); - // notification["error"]({ - // message: i18n.t("documents.errors.insert", { - // message: cloudinaryUploadResponse.statusText, - // }), - // }); - return; + return { success: false, error: cloudinaryUploadResponse.statusText }; } //Insert the document with the matching key. @@ -179,6 +166,7 @@ export const uploadToCloudinary = async ( // message: JSON.stringify(JSON.stringify(documentInsert.errors)), // }), // }); - return; + return { success: false, error: JSON.stringify(documentInsert.errors) }; } + return { success: true }; };