diff --git a/App.js b/App.js index edce35a..4d54d85 100644 --- a/App.js +++ b/App.js @@ -1,15 +1,17 @@ +import { ApolloProvider } from "@apollo/client"; import { Ionicons } from "@expo/vector-icons"; import { AppLoading } from "expo"; +import * as FileSystem from "expo-file-system"; import * as Font from "expo-font"; import React from "react"; import { StatusBar as rnStatusBar, StyleSheet } from "react-native"; import { Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; import ScreenMainComponent from "./components/screen-main/screen-main.component"; +import { client } from "./graphql/client"; import { persistor, store } from "./redux/store"; import "./translations/i18n"; -import { client } from "./graphql/client"; -import { ApolloProvider } from "@apollo/client"; + export default class App extends React.Component { constructor(props) { super(props); @@ -25,6 +27,11 @@ export default class App extends React.Component { ...Ionicons.font, }); this.setState({ isReady: true }); + await FileSystem.makeDirectoryAsync( + FileSystem.documentDirectory + "photos" + ).catch((e) => { + console.log(e, "Directory already exists"); + }); } render() { diff --git a/components/screen-camera/screen-camera.component.jsx b/components/screen-camera/screen-camera.component.jsx index c28078f..7ae1a04 100644 --- a/components/screen-camera/screen-camera.component.jsx +++ b/components/screen-camera/screen-camera.component.jsx @@ -1,30 +1,31 @@ -import React, { useState, useEffect, useRef } from "react"; -import { Text, View, TouchableOpacity, SafeAreaView } from "react-native"; -import { Camera } from "expo-camera"; import { - Ionicons, FontAwesome, + Ionicons, MaterialCommunityIcons, } from "@expo/vector-icons"; import { useNavigation } from "@react-navigation/native"; +import { Camera } from "expo-camera"; import * as FileSystem from "expo-file-system"; import * as Permissions from "expo-permissions"; -import * as MediaLibrary from "expo-media-library"; - +import React, { useEffect, useRef, useState } from "react"; +import { SafeAreaView, Text, TouchableOpacity, View } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { - selectCurrentCameraJobId, selectCurrentCameraJob, + selectCurrentCameraJobId, } from "../../redux/app/app.selectors"; +import { addPhoto } from "../../redux/photos/photos.actions"; const mapStateToProps = createStructuredSelector({ cameraJobId: selectCurrentCameraJobId, cameraJob: selectCurrentCameraJob, }); -const mapDispatchToProps = (dispatch) => ({}); +const mapDispatchToProps = (dispatch) => ({ + addPhoto: (photo) => dispatch(addPhoto(photo)), +}); -export function ScreenCamera({ cameraJobId, cameraJob }) { +export function ScreenCamera({ cameraJobId, cameraJob, addPhoto }) { const navigation = useNavigation(); const [hasPermission, setHasPermission] = useState(null); const [rollPermision, setRollPermission] = useState(null); @@ -33,18 +34,6 @@ export function ScreenCamera({ cameraJobId, cameraJob }) { useEffect(() => { (async () => { - //Ensure local photo direcotry exists. - console.log( - "ScreenCamera -> FileSystem.documentDirectory ", - FileSystem.documentDirectory - ); - - await FileSystem.makeDirectoryAsync( - FileSystem.documentDirectory + "photos" - ).catch((e) => { - console.log(e, "Directoryc already exists"); - }); - const { status } = await Camera.requestPermissionsAsync(); setHasPermission(status === "granted"); // camera roll @@ -72,21 +61,16 @@ export function ScreenCamera({ cameraJobId, cameraJob }) { let photo = await cameraRef.current.takePictureAsync(options); console.log("ScreenCamera -> photo", photo); - const filename = new Date().getTime() + ".jpg"; + const filename = photo.uri.substring(photo.uri.lastIndexOf("/") + 1); + + const newUri = FileSystem.documentDirectory + "photos/" + filename; await FileSystem.copyAsync({ from: photo.uri, - to: FileSystem.documentDirectory + "photos/" + filename, + to: newUri, }); - console.log( - "List of Files", - await FileSystem.readDirectoryAsync(FileSystem.documentDirectory), - await FileSystem.readDirectoryAsync( - FileSystem.documentDirectory + "photos" - ) - ); - //const asset = await MediaLibrary.createAssetAsync(photo.uri); + addPhoto({ ...photo, id: filename, uri: newUri, jobId: cameraJobId }); } }; diff --git a/components/screen-media-cache/screen-media-cache.component.jsx b/components/screen-media-cache/screen-media-cache.component.jsx index 866b208..5d0056e 100644 --- a/components/screen-media-cache/screen-media-cache.component.jsx +++ b/components/screen-media-cache/screen-media-cache.component.jsx @@ -1,47 +1,44 @@ -import * as FileSystem from "expo-file-system"; -import React, { useEffect, useState } from "react"; +import { Button, Text as NBText, Thumbnail, View } from "native-base"; +import React from "react"; import { SafeAreaView, Text } from "react-native"; -import { Button, Text as NBText } from "native-base"; -export default function ScreenMediaCache() { - const [images, setImages] = useState([]); - useEffect(() => { - (async () => { - setImages( - ( - await FileSystem.readDirectoryAsync( - FileSystem.documentDirectory + "photos" - ) - ).map((f) => { - return { - uri: FileSystem.documentDirectory + "photos/" + f, - dimensions: { width: 2142, height: 4224 }, - }; - }) - ); - })(); - }, []); +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { removeAllPhotos } from "../../redux/photos/photos.actions"; +import { selectPhotos } from "../../redux/photos/photos.selectors"; +const mapStateToProps = createStructuredSelector({ + photos: selectPhotos, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) + removeAllPhotos: () => dispatch(removeAllPhotos()), +}); + +export function ScreenMediaCache({ photos, removeAllPhotos }) { return ( This is the media cache screen. - + {photos.length} + + The View + {photos.map((i, idx) => ( + + {i.uri} + + + ))} + ); } +export default connect(mapStateToProps, mapDispatchToProps)(ScreenMediaCache); diff --git a/package.json b/package.json index f438872..87d56eb 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "expo-localization": "~9.0.0", "expo-media-library": "~9.2.1", "expo-permissions": "~9.3.0", + "expo-sqlite": "~8.4.0", "expo-status-bar": "~1.0.2", "firebase": "7.9.0", "formik": "^2.2.3", diff --git a/redux/photos/photos.actions.js b/redux/photos/photos.actions.js new file mode 100644 index 0000000..fdcfecb --- /dev/null +++ b/redux/photos/photos.actions.js @@ -0,0 +1,24 @@ +import PhotosActionTypes from "./photos.types"; + +export const addPhoto = (photo) => ({ + type: PhotosActionTypes.ADD_PHOTO, + payload: photo, +}); + +export const removePhotos = (photoIds) => ({ + type: PhotosActionTypes.REMOVE_PHOTOS, + payload: photoIds, +}); + +export const removeAllPhotos = () => ({ + type: PhotosActionTypes.REMOVE_ALL_PHOTOS, +}); + +export const uploadAllPhotos = () => ({ + type: PhotosActionTypes.UPLOAD_ALL_PHOTOS_START, +}); + +export const uploadSelectedPhotos = (photoIds) => ({ + type: PhotosActionTypes.UPLOAD_SELECTED_PHOTOS_START, + payload: photoIds, +}); diff --git a/redux/photos/photos.reducer.js b/redux/photos/photos.reducer.js new file mode 100644 index 0000000..cfb52f2 --- /dev/null +++ b/redux/photos/photos.reducer.js @@ -0,0 +1,44 @@ +import PhotosActionTypes from "./photos.types"; + +const INITIAL_STATE = { + photos: [], + uploadInProgress: false, + uploadError: null, +}; + +const photosReducer = (state = INITIAL_STATE, action) => { + switch (action.type) { + case PhotosActionTypes.ADD_PHOTO: + return { + ...state, + photos: [...state.photos, action.payload], + }; + case PhotosActionTypes.REMOVE_ALL_PHOTOS: + return { + ...state, + photos: [], + }; + case PhotosActionTypes.REMOVE_PHOTOS: + return { + ...state, + photos: state.photos.filter((p) => !action.payload.includes(p.id)), + }; + case PhotosActionTypes.UPLOAD_PHOTO_FAILURE: + return { + ...state, + uploadInProgress: false, + uploadError: action.payload, + }; + case PhotosActionTypes.UPLOAD_ALL_PHOTOS_START: + case PhotosActionTypes.UPLOAD_SELECTED_PHOTOS_START: + return { + ...state, + uploadInProgress: true, + uploadError: null, + }; + default: + return state; + } +}; + +export default photosReducer; diff --git a/redux/photos/photos.sagas.js b/redux/photos/photos.sagas.js new file mode 100644 index 0000000..eb17913 --- /dev/null +++ b/redux/photos/photos.sagas.js @@ -0,0 +1,29 @@ +import { all, call, takeLatest } from "redux-saga/effects"; +import PhotosActionTypes from "./photos.types"; + +export function* onRemoveAllPhotos() { + yield takeLatest(PhotosActionTypes.REMOVE_ALL_PHOTOS, removeAllPhotosAction); +} +export function* removeAllPhotosAction() { + try { + //Physically delete all photosSagas. + const fps = (yield FileSystem.readDirectoryAsync( + FileSystem.documentDirectory + "photos" + )).map((f) => { + return FileSystem.documentDirectory + "photos/" + f; + }); + const all = []; + fps.forEach((f) => all.push(FileSystem.deleteAsync(f))); + yield Promise.all(all); + + console.log("All photos deleted."); + } catch (error) { + console.log("Saga Error: onRemoveAllPhotos", error); + //yield put(signInFailure(error)); + //logImEXEvent("redux_sign_in_failure", { user: email, error }); + } +} + +export function* photosSagas() { + yield all([call(onRemoveAllPhotos)]); +} diff --git a/redux/photos/photos.selectors.js b/redux/photos/photos.selectors.js new file mode 100644 index 0000000..f581003 --- /dev/null +++ b/redux/photos/photos.selectors.js @@ -0,0 +1,16 @@ +import { createSelector } from "reselect"; + +const selectPhotosState = (state) => state.photos; + +export const selectPhotos = createSelector( + [selectPhotosState], + (photos) => photos.photos +); +export const selectUploadInProgress = createSelector( + [selectPhotosState], + (photos) => photos.uploadInProgress +); +export const selectUploadError = createSelector( + [selectPhotosState], + (photos) => photos.uploadError +); diff --git a/redux/photos/photos.types.js b/redux/photos/photos.types.js new file mode 100644 index 0000000..6b32087 --- /dev/null +++ b/redux/photos/photos.types.js @@ -0,0 +1,10 @@ +const PhotosActionTypes = { + ADD_PHOTO: "ADD_PHOTO", + REMOVE_PHOTOS: "REMOVE_PHOTOS", + REMOVE_ALL_PHOTOS: "REMOVE_ALL_PHOTOS", + UPLOAD_SELECTED_PHOTOS_START: "UPLOAD_SELECTED_PHOTOS_START", + UPLOAD_ALL_PHOTOS_START: "UPLOAD_ALL_PHOTOS_START", + UPLOAD_PHOTO_SUCCESS: "UPLOAD_PHOTO_SUCCESS", + UPLOAD_PHOTO_FAILURE: "UPLOAD_PHOTO_FAILURE", +}; +export default PhotosActionTypes; diff --git a/redux/root.reducer.js b/redux/root.reducer.js index 043e82e..0ba49e1 100644 --- a/redux/root.reducer.js +++ b/redux/root.reducer.js @@ -2,12 +2,13 @@ import AsyncStorage from "@react-native-community/async-storage"; import { combineReducers } from "redux"; import { persistReducer } from "redux-persist"; import appReducer from "./app/app.reducer"; +import photosReducer from "./photos/photos.reducer"; import userReducer from "./user/user.reducer"; const persistConfig = { key: "root", storage: AsyncStorage, - whitelist: [], + whitelist: ["photos"], blacklist: ["user", "app"], // whitelist: ["messaging", "tech", "application"], // blacklist: ["user", "email", "modals"], @@ -16,6 +17,7 @@ const persistConfig = { const rootReducer = combineReducers({ user: userReducer, app: appReducer, + photos: photosReducer, }); export default persistReducer(persistConfig, rootReducer); diff --git a/redux/root.saga.js b/redux/root.saga.js index 921340c..b2e2da1 100644 --- a/redux/root.saga.js +++ b/redux/root.saga.js @@ -1,7 +1,8 @@ import { all, call } from "redux-saga/effects"; import { appSagas } from "./app/app.sagas"; +import { photosSagas } from "./photos/photos.sagas"; import { userSagas } from "./user/user.sagas"; export default function* rootSaga() { - yield all([call(userSagas), call(appSagas)]); + yield all([call(userSagas), call(appSagas), call(photosSagas)]); } diff --git a/redux/store.js b/redux/store.js index a01700f..13a1b02 100644 --- a/redux/store.js +++ b/redux/store.js @@ -10,12 +10,11 @@ const sagaMiddleWare = createSagaMiddleware(); const middlewares = [sagaMiddleWare]; if (process.env.NODE_ENV === "development") { - middlewares - .push - // createLogger({ - // collapsed: true, - // }) - (); + middlewares.push( + createLogger({ + collapsed: true, + }) + ); } //Add in for React Native Debugger.