From 29bd2bc03eca1b66d2756773627f7fb940198ba4 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 9 Feb 2021 23:12:52 -0800 Subject: [PATCH] Added image picker and cleaned up main and job list screens. --- App.js | 5 +- babel-translations.babel | 78 +++++++++ .../camera-select-job.component.jsx | 18 +- .../data-label/data-label.component.jsx | 2 +- .../job-documents/job-documents.component.jsx | 6 +- components/job-lines/job-lines.component.jsx | 2 +- .../job-list-item/job-list-item.component.jsx | 91 ++++++++-- .../job-notes-item.component.jsx | 10 +- components/job-notes/job-notes.component.jsx | 2 +- components/screen-camera/screen-camera.jsx | 73 ++++---- .../screen-main/screen-main.component.jsx | 10 ++ .../screen-media-browser.component.jsx | 162 ++++++++++++++++++ .../screen-media-cache.component.jsx | 52 ++++-- .../screen-splash/screen-splash.component.jsx | 8 +- .../sign-in-error-alert.component.jsx | 7 +- components/styles.js | 1 - .../upload-delete-switch.component.jsx | 48 ++++++ .../upload-progress.component.jsx | 46 +++++ graphql/jobs.queries.js | 6 +- package-lock.json | 125 ++++++++++++++ package.json | 5 +- redux/app/app.actions.js | 3 + redux/app/app.reducer.js | 6 + redux/app/app.selectors.js | 8 + redux/app/app.types.js | 1 + translations/en-US/common.json | 9 + translations/es-MX/common.json | 9 + translations/fr-CA/common.json | 9 + util/document-upload.utility.js | 15 +- 29 files changed, 703 insertions(+), 114 deletions(-) create mode 100644 components/screen-media-browser/screen-media-browser.component.jsx create mode 100644 components/upload-delete-switch/upload-delete-switch.component.jsx create mode 100644 components/upload-progress/upload-progress.component.jsx diff --git a/App.js b/App.js index bcfb4d9..f587127 100644 --- a/App.js +++ b/App.js @@ -4,6 +4,7 @@ import AppLoading from "expo-app-loading"; import * as FileSystem from "expo-file-system"; import * as Font from "expo-font"; import React from "react"; +import { SafeAreaView } from "react-native"; import { Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; import * as Sentry from "sentry-expo"; @@ -54,7 +55,9 @@ export default class App extends React.Component { - + + + diff --git a/babel-translations.babel b/babel-translations.babel index 86eecca..9a19633 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -1015,6 +1015,84 @@ + + mediabrowser + + + actions + + + upload + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + labels + + + deleteafterupload + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + selectjob + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + mediacache diff --git a/components/camera-select-job/camera-select-job.component.jsx b/components/camera-select-job/camera-select-job.component.jsx index 3f2e5f1..b1e8a47 100644 --- a/components/camera-select-job/camera-select-job.component.jsx +++ b/components/camera-select-job/camera-select-job.component.jsx @@ -1,7 +1,7 @@ import { useQuery } from "@apollo/client"; import { Picker } from "native-base"; import React from "react"; -import { View } from "react-native"; +import { Text, View } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; @@ -10,6 +10,7 @@ import { selectCurrentCameraJobId } from "../../redux/app/app.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import ErrorDisplay from "../error-display/error-display.component"; import LoadingDisplay from "../loading-display/loading-display.component"; +import { useTranslation } from "react-i18next"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -27,17 +28,13 @@ export function CameraSelectJob({ setCameraJobId, setCameraJob, }) { - const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, { variables: { statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], }, skip: !bodyshop, }); - - const onRefresh = async () => { - return refetch(); - }; - + const { t } = useTranslation(); if (loading) return ; if (error) return ; @@ -45,19 +42,14 @@ export function CameraSelectJob({ return ( + {!cameraJobId && {t("mediabrowser.labels.selectjob")}} { - console.log(value, idx); setCameraJobId(value); setCameraJob(data.jobs[idx]); }} diff --git a/components/data-label/data-label.component.jsx b/components/data-label/data-label.component.jsx index 37eb637..9abd8fd 100644 --- a/components/data-label/data-label.component.jsx +++ b/components/data-label/data-label.component.jsx @@ -1,9 +1,9 @@ +import { DateTime } from "luxon"; import { Input, Item, Label } from "native-base"; import React from "react"; import { StyleSheet } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { DateTime } from "luxon"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser }); diff --git a/components/job-documents/job-documents.component.jsx b/components/job-documents/job-documents.component.jsx index 5c38045..34f7c82 100644 --- a/components/job-documents/job-documents.component.jsx +++ b/components/job-documents/job-documents.component.jsx @@ -1,14 +1,11 @@ import { Container, Content, Thumbnail } from "native-base"; import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; import { FlatList, - SafeAreaView, + RefreshControl, StyleSheet, Text, TouchableOpacity, - View, - RefreshControl, } from "react-native"; import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component"; @@ -17,7 +14,6 @@ const REACT_APP_CLOUDINARY_IMAGE_ENDPOINT = const REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS = "c_fill,f_auto,h_250,w_250"; export default function JobDocumentsComponent({ job, loading, refetch }) { - const { t } = useTranslation(); const [previewVisible, setPreviewVisible] = useState(false); const [imgIndex, setImgIndex] = useState(0); const onRefresh = async () => { diff --git a/components/job-lines/job-lines.component.jsx b/components/job-lines/job-lines.component.jsx index 1729957..c16f7ee 100644 --- a/components/job-lines/job-lines.component.jsx +++ b/components/job-lines/job-lines.component.jsx @@ -6,7 +6,7 @@ import Dinero from "dinero.js"; export default function JobLines({ job, loading, refetch }) { const { t } = useTranslation(); - if (!!!job) { + if (!job) { Job is not defined. ; diff --git a/components/job-list-item/job-list-item.component.jsx b/components/job-list-item/job-list-item.component.jsx index 2335c16..d162240 100644 --- a/components/job-list-item/job-list-item.component.jsx +++ b/components/job-list-item/job-list-item.component.jsx @@ -1,9 +1,10 @@ import { Ionicons } from "@expo/vector-icons"; import { useNavigation } from "@react-navigation/native"; +import * as ImagePicker from "expo-image-picker"; import { Body, H3, Icon, ListItem, Right } from "native-base"; -import React from "react"; +import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { Text } from "react-native"; +import { Animated, Platform, Text } from "react-native"; import { TouchableOpacity } from "react-native-gesture-handler"; import Swipeable from "react-native-gesture-handler/Swipeable"; import { connect } from "react-redux"; @@ -23,9 +24,40 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) { const { t } = useTranslation(); const navigation = useNavigation(); - const RenderRightAction = () => { - const navigation = useNavigation(); - const { t } = useTranslation(); + useEffect(() => { + (async () => { + if (Platform.OS !== "web") { + const { + status, + } = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (status !== "granted") { + alert("Sorry, we need camera roll permissions to make this work!"); + } + } + })(); + }, []); + + const pickImage = async () => { + let result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.All, + allowsEditing: true, + aspect: [4, 3], + quality: 1, + }); + + console.log(result); + + if (!result.cancelled) { + // setImage(result.uri); + } + }; + + const RenderRightAction = (progress, dragX) => { + const scale = dragX.interpolate({ + inputRange: [-100, 0], + outputRange: [0.7, 0], + }); + return ( - - {/* - {t("joblist.actions.swipecamera")} - */} + + + + + ); + }; + const RenderLeftAction = (progress, dragX) => { + const scale = dragX.interpolate({ + inputRange: [0, 100], + outputRange: [0, 1], + extrapolate: "clamp", + }); + + return ( + { + setCameraJobId(item.id); + setCameraJob(item); + navigation.push("MediaBrowser"); + //pickImage(item.id); + }} + > + + Add + ); }; @@ -51,7 +119,10 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) { }; return ( - }> +

{item.ro_number || t("general.labels.na")}

diff --git a/components/job-notes-item/job-notes-item.component.jsx b/components/job-notes-item/job-notes-item.component.jsx index 1832820..3b7913a 100644 --- a/components/job-notes-item/job-notes-item.component.jsx +++ b/components/job-notes-item/job-notes-item.component.jsx @@ -1,16 +1,14 @@ -import { Ionicons } from "@expo/vector-icons"; +import { AntDesign, Ionicons } from "@expo/vector-icons"; import { useNavigation } from "@react-navigation/native"; -import { Card, CardItem, Text, View, Row } from "native-base"; +import { DateTime } from "luxon"; +import { Card, CardItem, Text, View } from "native-base"; import React from "react"; import { useTranslation } from "react-i18next"; import { TouchableOpacity } from "react-native-gesture-handler"; import Swipeable from "react-native-gesture-handler/Swipeable"; import styles from "../styles"; -import { AntDesign } from "@expo/vector-icons"; -import { DateTime } from "luxon"; -export default function NoteListItem({ item }) { - const { t } = useTranslation(); +export default function NoteListItem({ item }) { return ( }> diff --git a/components/job-notes/job-notes.component.jsx b/components/job-notes/job-notes.component.jsx index ebc6689..8723bde 100644 --- a/components/job-notes/job-notes.component.jsx +++ b/components/job-notes/job-notes.component.jsx @@ -6,7 +6,7 @@ import JobNotesItem from "../job-notes-item/job-notes-item.component"; export default function JobNotes({ job, loading, refetch }) { const { t } = useTranslation(); - if (!!!job) { + if (!job) { Job is not defined. ; diff --git a/components/screen-camera/screen-camera.jsx b/components/screen-camera/screen-camera.jsx index f94edda..03e0f99 100644 --- a/components/screen-camera/screen-camera.jsx +++ b/components/screen-camera/screen-camera.jsx @@ -2,7 +2,7 @@ import { useFocusEffect } from "@react-navigation/native"; import { Camera } from "expo-camera"; import * as FileSystem from "expo-file-system"; import React, { useEffect, useRef, useState } from "react"; -import { SafeAreaView, Text, View } from "react-native"; +import { Text, View } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { @@ -45,10 +45,8 @@ export function ScreenCamera({ cameraJobId, addPhoto }) { useEffect(() => { (async () => { - const { status: cameraStatus } = await Camera.requestPermissionsAsync(); - setHasPermission(cameraStatus === "granted"); - - console.log("Camera Perms:", await Camera.getPermissionsAsync()); + const { status } = await Camera.requestPermissionsAsync(); + setHasPermission(status === "granted"); })(); }, []); @@ -77,13 +75,6 @@ export function ScreenCamera({ cameraJobId, addPhoto }) { to: newUri, }); setState({ ...state, capturing: false }); - console.log("Add Photo Object", { - ...photo, - id: filename, - uri: newUri, - jobId: cameraJobId, - video: false, - }); addPhoto({ ...photo, id: filename, @@ -107,7 +98,13 @@ export function ScreenCamera({ cameraJobId, addPhoto }) { to: newUri, }); setState({ ...state, capturing: false }); - + console.log("Adding Photo", { + ...video, + id: filename, + uri: newUri, + jobId: cameraJobId, + video: true, + }); addPhoto({ ...video, id: filename, @@ -129,34 +126,32 @@ export function ScreenCamera({ cameraJobId, addPhoto }) { const { flashMode, cameraType, capturing } = state; return ( - - + - - + - - - - + +
+ ); } export default connect(mapStateToProps, mapDispatchToProps)(ScreenCamera); diff --git a/components/screen-main/screen-main.component.jsx b/components/screen-main/screen-main.component.jsx index 5a0db8e..863400d 100644 --- a/components/screen-main/screen-main.component.jsx +++ b/components/screen-main/screen-main.component.jsx @@ -25,6 +25,7 @@ import ScreenMessagingList from "../screen-messaging-list/screen-messaging-list. import ScreenSettingsComponent from "../screen-settings/screen-settings.component"; import ScreenSignIn from "../screen-sign-in/screen-sign-in.component"; import ScreenSplash from "../screen-splash/screen-splash.component"; +import ScreenMediaBrowser from "../screen-media-browser/screen-media-browser.component"; const JobStack = createStackNavigator(); const CameraStack = createStackNavigator(); @@ -63,6 +64,15 @@ const JobStackNavigator = () => ( i18n.t("joblist.labels.detail"), })} /> + ({ + // title: + // (route.params && route.params.title) || + // i18n.t("joblist.labels.detail"), + // })} + /> ); diff --git a/components/screen-media-browser/screen-media-browser.component.jsx b/components/screen-media-browser/screen-media-browser.component.jsx new file mode 100644 index 0000000..a451cec --- /dev/null +++ b/components/screen-media-browser/screen-media-browser.component.jsx @@ -0,0 +1,162 @@ +import { Ionicons } from "@expo/vector-icons"; +import { AssetsSelector } from "expo-images-picker"; +import _ from "lodash"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { StyleSheet, Text, View } from "react-native"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { + selectCurrentCameraJobId, + selectDeleteAfterUpload, +} from "../../redux/app/app.selectors"; +import { + selectBodyshop, + selectCurrentUser, +} from "../../redux/user/user.selectors"; +import { handleUpload } from "../../util/document-upload.utility"; +import CameraSelectJob from "../camera-select-job/camera-select-job.component"; +import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component"; +import UploadProgress from "../upload-progress/upload-progress.component"; +import * as FileSystem from "expo-file-system"; + +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + selectedCameraJobId: selectCurrentCameraJobId, + deleteAfterUpload: selectDeleteAfterUpload, +}); + +export default connect(mapStateToProps, null)(ImageBrowserScreen); +export function ImageBrowserScreen({ + currentUser, + bodyshop, + selectedCameraJobId, + deleteAfterUpload, +}) { + const { t } = useTranslation(); + const [uploads, setUploads] = useState({}); + + function handleOnProgress(uri, percent) { + setUploads((prevUploads) => ({ ...prevUploads, [uri]: { percent } })); + } + function handleOnSuccess(uri) { + console.log("onSucces!", uri); + setTimeout(async function () { + setUploads((prevUploads) => _.omit(prevUploads, uri)); + if (deleteAfterUpload) { + await FileSystem.deleteAsync(uri); + } + }, 1000); + } + + const onDone = async (data) => { + const actions = []; + console.log("Assets :>> ", data); + data.forEach(function (p) { + const uri = p.uri.split("/").pop(); + actions.push( + handleUpload( + { + uri: p.uri, + onError: handleOnError, + onProgress: ({ percent }) => handleOnProgress(uri, percent), + onSuccess: () => handleOnSuccess(uri), + }, + { + bodyshop: bodyshop, + jobId: selectedCameraJobId, + uploaded_by: currentUser.email, + photo: p, + } + ) + ); + }); + Promise.all(actions); + }; + + return ( + + + + {selectedCameraJobId && ( + { + console.log("Back Function", props); + }, + doneFunction: onDone, + }, + noAssets: { + Component: function NoAsset() { + return ( + + Looks like nothing here + + ); + }, + }, + }} + /> + )} + + + + ); +} + +const styles = StyleSheet.create({ + flex: { + flex: 1, + }, + container: { + display: "flex", + // position: "relative", + }, + buttonStyle: { + //backgroundColor: "tomato", + }, + // eslint-disable-next-line react-native/no-color-literals + textStyle: { + color: "dodgerblue", + }, +}); + +function handleOnError(...props) { + console.log("HandleOnError", props); +} diff --git a/components/screen-media-cache/screen-media-cache.component.jsx b/components/screen-media-cache/screen-media-cache.component.jsx index 00018ac..0ee1283 100644 --- a/components/screen-media-cache/screen-media-cache.component.jsx +++ b/components/screen-media-cache/screen-media-cache.component.jsx @@ -1,9 +1,11 @@ -import { Button, Container, Spinner, Text as NBText, View } from "native-base"; +import _ from "lodash"; +import { Button, Spinner, Text as NBText, View } from "native-base"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { FlatList, Image, + SafeAreaView, StyleSheet, Text, TouchableOpacity, @@ -38,24 +40,14 @@ export function ScreenMediaCache({ const { t } = useTranslation(); const [previewVisible, setPreviewVisible] = useState(false); const [imgIndex, setImgIndex] = useState(0); - const [imagesInDir, setImagesInDir] = useState([]); - console.log("Photos", photos); - - return ( - - - - - + const groupedPhotos = _.groupBy(photos, "jobId"); + const RenderJobPictures = ({ jobId, jobPhotos }) => ( + + {jobId} item.id} @@ -83,6 +75,32 @@ export function ScreenMediaCache({ ) } /> + + ); + + return ( + + + + + + item} + renderItem={(object) => ( + + )} + /> {`${photos.length} Photos`} - + ); } diff --git a/components/screen-splash/screen-splash.component.jsx b/components/screen-splash/screen-splash.component.jsx index 507a952..56e9eb6 100644 --- a/components/screen-splash/screen-splash.component.jsx +++ b/components/screen-splash/screen-splash.component.jsx @@ -1,10 +1,10 @@ +import { Container, Content, H1 } from "native-base"; import React from "react"; -import { StyleSheet, Image } from "react-native"; -import Logo from "../../assets/logo192.png"; import { useTranslation } from "react-i18next"; -import { H1, Container, Content } from "native-base"; -import styles from "../styles"; +import { Image, StyleSheet } from "react-native"; import { BarIndicator } from "react-native-indicators"; +import Logo from "../../assets/logo192.png"; +import styles from "../styles"; export default function ScreenSplash() { const { t } = useTranslation(); diff --git a/components/sign-in-error-alert/sign-in-error-alert.component.jsx b/components/sign-in-error-alert/sign-in-error-alert.component.jsx index 51c047f..462f839 100644 --- a/components/sign-in-error-alert/sign-in-error-alert.component.jsx +++ b/components/sign-in-error-alert/sign-in-error-alert.component.jsx @@ -1,10 +1,11 @@ -import React, { useState, useEffect } from "react"; +import { Text } from "native-base"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Container, Content, Text } from "native-base"; -import { View, StyleSheet } from "react-native"; +import { StyleSheet, View } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectSignInError } from "../../redux/user/user.selectors"; + const mapStateToProps = createStructuredSelector({ signInError: selectSignInError, }); diff --git a/components/styles.js b/components/styles.js index 750a02c..1e4e8ad 100644 --- a/components/styles.js +++ b/components/styles.js @@ -1,5 +1,4 @@ import { StyleSheet } from "react-native"; -import { Row } from "native-base"; export default StyleSheet.create({ contentContainer__centered: { diff --git a/components/upload-delete-switch/upload-delete-switch.component.jsx b/components/upload-delete-switch/upload-delete-switch.component.jsx new file mode 100644 index 0000000..d1607d6 --- /dev/null +++ b/components/upload-delete-switch/upload-delete-switch.component.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { StyleSheet, Switch, Text, View } from "react-native"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleDeleteAfterUpload } from "../../redux/app/app.actions"; +import { selectDeleteAfterUpload } from "../../redux/app/app.selectors"; + +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser + deleteAfterUpload: selectDeleteAfterUpload, +}); +const mapDispatchToProps = (dispatch) => ({ + toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()), +}); +export default connect(mapStateToProps, mapDispatchToProps)(UploadDeleteSwitch); + +export function UploadDeleteSwitch({ + deleteAfterUpload, + toggleDeleteAfterUpload, +}) { + const { t } = useTranslation(); + return ( + + + {t("mediabrowser.labels.deleteafterupload")} + + toggleDeleteAfterUpload()} + value={deleteAfterUpload} + /> + + ); +} +const styles = StyleSheet.create({ + container: { + display: "flex", + flexDirection: "row", + alignItems: "center", + }, + + text: { + flex: 1, + }, +}); diff --git a/components/upload-progress/upload-progress.component.jsx b/components/upload-progress/upload-progress.component.jsx new file mode 100644 index 0000000..46be88a --- /dev/null +++ b/components/upload-progress/upload-progress.component.jsx @@ -0,0 +1,46 @@ +import React from "react"; +import { ScrollView, StyleSheet, Text, View } from "react-native"; +import * as Progress from "react-native-progress"; + +export default function UploadProgress({ uploads }) { + return ( + + + {Object.keys(uploads).map((key) => ( + + {key} + + + + + ))} + + + ); +} +const styles = StyleSheet.create({ + container: { + display: "flex", + //flex: 1, + }, + progressItem: { + display: "flex", + flexDirection: "row", + alignItems: "center", + marginBottom: 12, + }, + progressText: { + flex: 1, + }, + progressBarContainer: { + flex: 1, + marginLeft: 12, + marginRight: 12, + }, +}); diff --git a/graphql/jobs.queries.js b/graphql/jobs.queries.js index 584f2fa..8c60a9d 100644 --- a/graphql/jobs.queries.js +++ b/graphql/jobs.queries.js @@ -269,7 +269,7 @@ export const GET_JOB_BY_PK = gql` date_open date_scheduled date_invoiced - date_closed + date_exported status owner_owing @@ -370,7 +370,7 @@ export const QUERY_JOB_CARD_DETAILS = gql` date_invoiced date_open date_exported - date_closed + date_scheduled date_estimated @@ -426,7 +426,7 @@ export const QUERY_TECH_JOB_DETAILS = gql` date_invoiced date_open date_exported - date_closed + date_scheduled date_estimated employee_body diff --git a/package-lock.json b/package-lock.json index 280ed31..1c9753d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1108,6 +1108,29 @@ "@types/hammerjs": "^2.0.36" } }, + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", @@ -2529,6 +2552,16 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "@react-native-community/art": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/art/-/art-1.2.0.tgz", + "integrity": "sha512-a+ZcRGl/BzLa89yi33Mbn5SHavsEXqKUMdbfLf3U8MDLElndPqUetoJyGkv63+BcPO49UMWiQRP1YUz6/zfJ+A==", + "requires": { + "art": "^0.10.3", + "invariant": "^2.2.4", + "prop-types": "^15.7.2" + } + }, "@react-native-community/async-storage": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.12.1.tgz", @@ -3905,6 +3938,11 @@ "function-bind": "^1.1.1" } }, + "art": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/art/-/art-0.10.3.tgz", + "integrity": "sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ==" + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -4007,6 +4045,22 @@ "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.13.18.tgz", "integrity": "sha512-f8pAxyKqXBNRIh8l4Sqju055BNec+DQlItdtutByYxULU0iJ1F7evIYE3skPKAkTB/xJH17l+n3Z8dVabGIIGg==" }, + "babel-plugin-styled-components": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz", + "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "babel-plugin-syntax-trailing-function-commas": { "version": "7.0.0-beta.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", @@ -4338,6 +4392,11 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caniuse-lite": { "version": "1.0.30001185", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz", @@ -4692,6 +4751,11 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-in-js-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", @@ -4701,6 +4765,16 @@ "isobject": "^3.0.1" } }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "dayjs": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", @@ -5608,6 +5682,11 @@ "fontfaceobserver": "^2.1.0" } }, + "expo-image-manipulator": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-9.0.0.tgz", + "integrity": "sha512-hziIcUONz7JtsonEehuYvXk8xCSGDHR6A4ynZcUTbZQCzxWYu7Xq2wahREz/DBdu0vz0LBw38wFLuSpfgaFojQ==" + }, "expo-image-picker": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-9.2.1.tgz", @@ -5624,6 +5703,16 @@ } } }, + "expo-images-picker": { + "version": "git+https://github.com/snaptsoft/expo-images-picker.git#9a423d67e4986742bb68b6d3e3118df262b68599", + "from": "git+https://github.com/snaptsoft/expo-images-picker.git", + "requires": { + "expo-image-manipulator": "^9.0.0", + "expo-media-library": "~10.0.0", + "expo-permissions": "~10.0.0", + "styled-components": "^5.1.1" + } + }, "expo-keep-awake": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.4.0.tgz", @@ -9424,6 +9513,11 @@ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, "pouchdb-collections": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz", @@ -10066,6 +10160,15 @@ "resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz", "integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==" }, + "react-native-progress": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-4.1.2.tgz", + "integrity": "sha512-sFHs6k6npWDOyvQoL2NeyOyHb+q1s8iOAOeyzoObN77zkxOAsgJt9FcSJLRq70Mw7qSGNJMFDqCgvYR1huYRwQ==", + "requires": { + "@react-native-community/art": "^1.1.2", + "prop-types": "^15.7.2" + } + }, "react-native-reanimated": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.13.2.tgz", @@ -10596,6 +10699,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -11000,6 +11108,23 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "styled-components": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.2.1.tgz", + "integrity": "sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.8", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "subscriptions-transport-ws": { "version": "0.9.18", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz", diff --git a/package.json b/package.json index 5fb3bbb..b12d7b7 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "formik": "^2.2.5", "graphql": "^15.4.0", "i18next": "^19.8.4", + "lodash": "^4.17.20", "luxon": "^1.25.0", "native-base": "^2.13.15", "react": "16.13.1", @@ -45,6 +46,7 @@ "react-native-gesture-handler": "~1.8.0", "react-native-image-zoom-viewer": "^3.0.1", "react-native-indicators": "^0.17.0", + "react-native-progress": "^4.1.2", "react-native-reanimated": "~1.13.0", "react-native-screens": "~2.15.0", "react-native-web": "~0.13.12", @@ -55,7 +57,8 @@ "redux-saga": "^1.1.3", "reselect": "^4.0.0", "sentry-expo": "^3.0.4", - "subscriptions-transport-ws": "^0.9.18" + "subscriptions-transport-ws": "^0.9.18", + "expo-images-picker": "https://github.com/snaptsoft/expo-images-picker/" }, "devDependencies": { "@babel/core": "^7.12.13", diff --git a/redux/app/app.actions.js b/redux/app/app.actions.js index b9af5db..1aeb536 100644 --- a/redux/app/app.actions.js +++ b/redux/app/app.actions.js @@ -24,3 +24,6 @@ export const documentUploadFailure = (error) => ({ type: AppActionTypes.DOCUMENT_UPLOAD_FAILURE, payload: error, }); +export const toggleDeleteAfterUpload = () => ({ + type: AppActionTypes.TOGGLE_DLETE_AFTER_UPLOAD, +}); diff --git a/redux/app/app.reducer.js b/redux/app/app.reducer.js index c22f6e9..c267510 100644 --- a/redux/app/app.reducer.js +++ b/redux/app/app.reducer.js @@ -5,6 +5,7 @@ const INITIAL_STATE = { cameraJob: null, documentUploadInProgress: null, documentUploadError: null, + deleteAfterUpload: true, }; const appReducer = (state = INITIAL_STATE, action) => { @@ -37,6 +38,11 @@ const appReducer = (state = INITIAL_STATE, action) => { documentUploadError: action.payload, documentUploadInProgress: null, }; + case AppActionTypes.TOGGLE_DLETE_AFTER_UPLOAD: + return { + ...state, + deleteAfterUpload: !state.deleteAfterUpload, + }; default: return state; } diff --git a/redux/app/app.selectors.js b/redux/app/app.selectors.js index 1ea895a..4626679 100644 --- a/redux/app/app.selectors.js +++ b/redux/app/app.selectors.js @@ -6,15 +6,23 @@ export const selectCurrentCameraJobId = createSelector( [selectApp], (app) => app.cameraJobId ); + export const selectCurrentCameraJob = createSelector( [selectApp], (app) => app.cameraJob ); + export const selectDocumentUploadInProgress = createSelector( [selectApp], (app) => app.documentUploadInProgress ); + export const selectDocumentUploadError = createSelector( [selectApp], (app) => app.documentUploadError ); + +export const selectDeleteAfterUpload = createSelector( + [selectApp], + (app) => app.deleteAfterUpload +); diff --git a/redux/app/app.types.js b/redux/app/app.types.js index d706bb7..029d8e0 100644 --- a/redux/app/app.types.js +++ b/redux/app/app.types.js @@ -4,5 +4,6 @@ const AppActionTypes = { DOCUMENT_UPLOAD_START: "DOCUMENT_UPLOAD_START", DOCUMENT_UPLOAD_SUCCESS: "DOCUMENT_UPLOAD_SUCCESS", DOCUMENT_UPLOAD_FAILURE: "DOCUMENT_UPLOAD_FAILURE", + TOGGLE_DLETE_AFTER_UPLOAD: "TOGGLE_DLETE_AFTER_UPLOAD", }; export default AppActionTypes; diff --git a/translations/en-US/common.json b/translations/en-US/common.json index c284e86..1b95f6c 100644 --- a/translations/en-US/common.json +++ b/translations/en-US/common.json @@ -72,6 +72,15 @@ "jobtab": "Jobs" } }, + "mediabrowser": { + "actions": { + "upload": "Upload" + }, + "labels": { + "deleteafterupload": "Delete After Upload", + "selectjob": "Please select a job." + } + }, "mediacache": { "actions": { "deleteall": "Delete All", diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json index eac291d..459616c 100644 --- a/translations/es-MX/common.json +++ b/translations/es-MX/common.json @@ -72,6 +72,15 @@ "jobtab": "" } }, + "mediabrowser": { + "actions": { + "upload": "" + }, + "labels": { + "deleteafterupload": "", + "selectjob": "" + } + }, "mediacache": { "actions": { "deleteall": "", diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json index 4519d5a..e67a01e 100644 --- a/translations/fr-CA/common.json +++ b/translations/fr-CA/common.json @@ -72,6 +72,15 @@ "jobtab": "" } }, + "mediabrowser": { + "actions": { + "upload": "" + }, + "labels": { + "deleteafterupload": "", + "selectjob": "" + } + }, "mediacache": { "actions": { "deleteall": "", diff --git a/util/document-upload.utility.js b/util/document-upload.utility.js index f5d27f2..d0bb9fa 100644 --- a/util/document-upload.utility.js +++ b/util/document-upload.utility.js @@ -10,7 +10,6 @@ var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); export const handleUpload = async (ev, context) => { - console.log("ev,context", ev, context); const { onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; @@ -74,7 +73,7 @@ export const uploadToCloudinary = async ( if (signedURLResponse.status !== 200) { console.log("Error Getting Signed URL", signedURLResponse.statusText); - if (!!onError) onError(signedURLResponse.statusText); + if (onError) onError(signedURLResponse.statusText); return { success: false, error: signedURLResponse.statusText }; } @@ -84,7 +83,7 @@ export const uploadToCloudinary = async ( var options = { headers: { "X-Requested-With": "XMLHttpRequest" }, onUploadProgress: (e) => { - if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); + if (onProgress) onProgress({ percent: e.loaded / e.total }); }, }; const formData = new FormData(); @@ -94,7 +93,7 @@ export const uploadToCloudinary = async ( type: fileType, name: file.data.name, }); - console.log("Applying lower quality transforms."); + formData.append("upload_preset", "incoming_upload"); formData.append("api_key", env.REACT_APP_CLOUDINARY_API_KEY); @@ -113,7 +112,7 @@ export const uploadToCloudinary = async ( ...options, } ); - console.log("Cloudinary Upload Response", cloudinaryUploadResponse.data); + // console.log("Cloudinary Upload Response", cloudinaryUploadResponse.data); } catch (error) { console.log("CLOUDINARY error", error, cloudinaryUploadResponse); return { success: false, error: error }; @@ -125,7 +124,7 @@ export const uploadToCloudinary = async ( cloudinaryUploadResponse.statusText, cloudinaryUploadResponse ); - if (!!onError) onError(cloudinaryUploadResponse.statusText); + if (onError) onError(cloudinaryUploadResponse.statusText); return { success: false, error: cloudinaryUploadResponse.statusText }; } @@ -145,7 +144,7 @@ export const uploadToCloudinary = async ( }, }); if (!documentInsert.errors) { - if (!!onSuccess) + if (onSuccess) onSuccess({ uid: documentInsert.data.insert_documents.returning[0].id, name: documentInsert.data.insert_documents.returning[0].name, @@ -159,7 +158,7 @@ export const uploadToCloudinary = async ( callback(); } } else { - if (!!onError) onError(JSON.stringify(documentInsert.errors)); + if (onError) onError(JSON.stringify(documentInsert.errors)); // notification["error"]({ // message: i18n.t("documents.errors.insert", { // message: JSON.stringify(JSON.stringify(documentInsert.errors)),