diff --git a/android-keystore.md b/android-keystore.md new file mode 100644 index 0000000..80db6f0 --- /dev/null +++ b/android-keystore.md @@ -0,0 +1,7 @@ +Saving Keystore to /Users/pfic/Documents/Development/imexmobile/imexmobile.jks +Keystore credentials +Keystore password: a8350e9998e94a9e84e5dd8743deeb8a +Key alias: QHBmaWMvaW1leG1vYmlsZQ== +Key password: 2db55c4fb1964e2e996271a2082133ee + +Path to Keystore: /Users/pfic/Documents/Development/imexmobile/imexmobile.jks diff --git a/app.json b/app.json index f717945..4c48a38 100644 --- a/app.json +++ b/app.json @@ -1,19 +1,20 @@ { "expo": { - "name": "imexmobile", + "name": "ImEX Mobile", "slug": "imexmobile", - "version": "1.1.0.2", + "version": "1.2.3", + "extra": { "expover": "1" }, "orientation": "default", "icon": "./assets/logo192noa.png", "ios": { "supportsTablet": true, "bundleIdentifier": "com.imex.imexmobile", - "buildNumber": "1.1.0.2", + "buildNumber": "1.2.3", "googleServicesFile": "./GoogleService-Info.plist" }, "android": { "package": "com.imex.imexmobile", - "versionCode": 1010002, + "versionCode": 1020300, "googleServicesFile": "./google-services.json" }, "splash": { diff --git a/babel-translations.babel b/babel-translations.babel index 42554b6..6bdcb8f 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -1299,6 +1299,27 @@ labels + + converting + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + deleteafterupload false @@ -1425,6 +1446,27 @@ + + storageused + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + temporarystorage false @@ -1446,6 +1488,27 @@ + + uploading + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + diff --git a/components/job-documents/job-documents.component.jsx b/components/job-documents/job-documents.component.jsx index f4411a6..e5946c7 100644 --- a/components/job-documents/job-documents.component.jsx +++ b/components/job-documents/job-documents.component.jsx @@ -10,9 +10,9 @@ import { import env from "../../env"; import { DetermineFileType } from "../../util/document-upload.utility"; import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component"; - export default function JobDocumentsComponent({ job, loading, refetch }) { const [previewVisible, setPreviewVisible] = useState(false); + const [imgIndex, setImgIndex] = useState(0); const onRefresh = async () => { return refetch(); @@ -22,18 +22,17 @@ export default function JobDocumentsComponent({ job, loading, refetch }) { () => job.documents.map((doc) => { return { - source: { - uri: `${env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType( - doc.type - )}/upload/${doc.key}`, - }, + videoUrl: + DetermineFileType(doc.type) === "video" && GenerateSrcUrl(doc), + source: + DetermineFileType(doc.type) === "video" + ? { uri: GenerateThumbUrl(doc) } + : { uri: GenerateSrcUrl(doc) }, }; }), [job.documents] ); - console.log(job.documents); - return ( item.id} renderItem={(object) => ( - { + setImgIndex(object.index); + setPreviewVisible(true); }} > - { - setImgIndex(object.index); - setPreviewVisible(true); + - - - + /> + )} /> {job.documents.length} + ); } + +export const GenerateSrcUrl = (value) => { + let extension = value.extension; + if (extension && extension.toLowerCase().includes("heic")) extension = "jpg"; + + return `${env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType( + value.type + )}/upload/${value.key}${extension ? `.${extension}` : ""}`; +}; + +export const GenerateThumbUrl = (value) => { + let extension = value.extension; + if (extension && extension.includes("heic")) extension = "jpg"; + else if ( + DetermineFileType(value.type) !== "image" || + (value.type && value.type.includes("application")) + ) + extension = "jpg"; + + return `${env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType( + value.type + )}/upload/${env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}${ + extension ? `.${extension}` : "" + }`; +}; diff --git a/components/job-lines/job-lines.component.jsx b/components/job-lines/job-lines.component.jsx index 66b6655..05240b4 100644 --- a/components/job-lines/job-lines.component.jsx +++ b/components/job-lines/job-lines.component.jsx @@ -34,9 +34,6 @@ export default function JobLines({ job, loading, refetch }) { {t("jobdetail.labels.lines_qty")} - - {t("jobdetail.labels.lines_price")} - diff --git a/components/job-space-available/job-space-available.component.jsx b/components/job-space-available/job-space-available.component.jsx new file mode 100644 index 0000000..6b0971e --- /dev/null +++ b/components/job-space-available/job-space-available.component.jsx @@ -0,0 +1,44 @@ +import { useQuery } from "@apollo/client"; +import React from "react"; +import { ProgressBar } from "react-native-paper"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_DOC_SIZE_TOTALS } from "../../graphql/documents.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { View, Text } from "react-native"; +import { useTranslation } from "react-i18next"; +import { formatBytes } from "../../util/document-upload.utility"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect(mapStateToProps, mapDispatchToProps)(JobSpaceAvailable); + +export function JobSpaceAvailable({ bodyshop, style, jobid }) { + const { t } = useTranslation(); + const { data } = useQuery(GET_DOC_SIZE_TOTALS, { + variables: { jobId: jobid }, + skip: !jobid, + }); + + if (!jobid || !data) return <>; + + const progress = + data.documents_aggregate.aggregate.sum.size / + ((bodyshop && bodyshop.jobsizelimit) || 1); + + return ( + + + {t("mediabrowser.labels.storageused", { + used: formatBytes(data.documents_aggregate.aggregate.sum.size), + total: formatBytes((bodyshop && bodyshop.jobsizelimit) || 1), + percent: Math.round(progress * 100), + })} + + + + ); +} diff --git a/components/media-cache-overlay/media-cache-overlay.component.jsx b/components/media-cache-overlay/media-cache-overlay.component.jsx index f160a73..3d113fb 100644 --- a/components/media-cache-overlay/media-cache-overlay.component.jsx +++ b/components/media-cache-overlay/media-cache-overlay.component.jsx @@ -1,7 +1,14 @@ import { Ionicons } from "@expo/vector-icons"; -import React from "react"; -import { Modal, SafeAreaView, TouchableOpacity, View } from "react-native"; +import { Video } from "expo-av"; +import React, { useState } from "react"; +import { + Dimensions, + Modal, + SafeAreaView, + TouchableOpacity, +} from "react-native"; import Gallery from "react-native-image-gallery"; + export default function MediaCacheOverlay({ photos, previewVisible, @@ -9,6 +16,11 @@ export default function MediaCacheOverlay({ imgIndex, setImgIndex, }) { + const [currentIndex, setcurrentIndex] = useState(0); + const [dragging, setDragging] = useState(false); + + const videoRef = React.useRef(null); + return ( setPreviewVisible(false)} @@ -17,7 +29,14 @@ export default function MediaCacheOverlay({ transparent={false} > - + setcurrentIndex(position)} + onPageScrollStateChanged={(state) => + state === "idle" ? setDragging(false) : setDragging(true) + } + /> setPreviewVisible(false)} @@ -29,7 +48,29 @@ export default function MediaCacheOverlay({ style={{ margin: 20 }} /> + {!dragging && photos[currentIndex] && photos[currentIndex].videoUrl && ( + { + await videoRef.current.loadAsync( + { uri: photos[currentIndex].videoUrl }, + {}, + false + ); + videoRef.current.presentFullscreenPlayer(); + }} + > + + + )} + ); } diff --git a/components/screen-main/screen-main.component.jsx b/components/screen-main/screen-main.component.jsx index 179de25..4728390 100644 --- a/components/screen-main/screen-main.component.jsx +++ b/components/screen-main/screen-main.component.jsx @@ -115,7 +115,7 @@ const BottomTabsNavigator = () => ( } else if (route.name === "MoreTab") { iconName = "ios-settings"; } else if (route.name === "MediaBrowserTab") { - iconName = "ios-photos"; + iconName = "ios-camera"; } else { //iconName = "customerservice"; } diff --git a/components/screen-media-browser/screen-media-browser.component.jsx b/components/screen-media-browser/screen-media-browser.component.jsx index b794e90..fd12739 100644 --- a/components/screen-media-browser/screen-media-browser.component.jsx +++ b/components/screen-media-browser/screen-media-browser.component.jsx @@ -1,147 +1,38 @@ -import { useApolloClient } from "@apollo/client"; import { Ionicons } from "@expo/vector-icons"; -//const limit = plimit(2); -import * as FileSystem from "expo-file-system"; import { AssetsSelector } from "expo-images-picker"; -import * as MediaLibrary from "expo-media-library"; -import _ from "lodash"; import React, { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Alert, StyleSheet, Text, View } from "react-native"; +import { StyleSheet, Text, View } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.analytics"; -import { GET_DOC_SIZE_TOTALS } from "../../graphql/documents.queries"; -import { - selectCurrentCameraJobId, - selectDeleteAfterUpload, -} from "../../redux/app/app.selectors"; -import { - selectBodyshop, - selectCurrentUser, -} from "../../redux/user/user.selectors"; -import { handleUpload } from "../../util/document-upload.utility"; +import { selectCurrentCameraJobId } from "../../redux/app/app.selectors"; import CameraSelectJob from "../camera-select-job/camera-select-job.component"; +import JobSpaceAvailable from "../job-space-available/job-space-available.component"; import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component"; import UploadProgress from "../upload-progress/upload-progress.component"; const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser, - bodyshop: selectBodyshop, selectedCameraJobId: selectCurrentCameraJobId, - deleteAfterUpload: selectDeleteAfterUpload, }); -export function ImageBrowserScreen({ - currentUser, - bodyshop, - selectedCameraJobId, - deleteAfterUpload, -}) { +export function ImageBrowserScreen({ selectedCameraJobId }) { const { t } = useTranslation(); - const [uploads, setUploads] = useState({}); - - function handleOnProgress(uri, percent) { - setUploads((prevUploads) => ({ ...prevUploads, [uri]: { percent } })); - } - + const [uploads, setUploads] = useState(null); const [tick, setTick] = useState(0); const forceRerender = useCallback(() => { setTick((tick) => tick + 1); }, []); - const client = useApolloClient(); - async function handleOnSuccess(uri, id) { - logImEXEvent("imexmobile_successful_upload"); - setUploads((prevUploads) => _.omit(prevUploads, uri)); - } - - const onDone = async (data) => { + const onDone = (data) => { logImEXEvent("imexmobile_upload_documents", { count: data.length }); - - //Validate to make sure the totals for the file sizes do not exceed the total on the job. - - if (selectedCameraJobId !== "temp") { - const queryData = await client.query({ - query: GET_DOC_SIZE_TOTALS, - fetchPolicy: "network-only", - variables: { - jobId: selectedCameraJobId, - }, - }); - const totalOfUploads = await data.reduce(async (acc, val) => { - //Get the size of the file based on URI. - const info = await FileSystem.getInfoAsync(val.uri, { size: true }); - return (await acc) + info.size; - }, 0); - - // console.log( - // "Size of uploaded documents.", - // queryData.data.documents_aggregate.aggregate.sum.size, - // "Shop Limit", - // bodyshop.jobsizelimit, - // "Space remaining", - // bodyshop.jobsizelimit - - // queryData.data.documents_aggregate.aggregate.sum.size, - // "Total of uploaded files", - // totalOfUploads - // ); - - if ( - bodyshop.jobsizelimit - - queryData.data.documents_aggregate.aggregate.sum.size <= - totalOfUploads - ) { - //No more room... abandon ship. - Alert.alert( - t("mediabrowser.labels.storageexceeded_title"), - t("mediabrowser.labels.storageexceeded") - ); - return; - } - } - - const ret = await Promise.all( - data.map(async (p) => { - let filename; - //Appears to work for android. - //iOS provides the filename, android doe snot. - - filename = p.filename || p.uri.split("/").pop(); - const result = await handleUpload( - { - //iOS provides the file name. Android does not. - - filename, - mediaId: p.id, - onError: handleOnError, - onProgress: ({ percent }) => handleOnProgress(filename, percent), - onSuccess: () => handleOnSuccess(filename, p.id), - }, - { - bodyshop: bodyshop, - jobId: selectedCameraJobId !== "temp" ? selectedCameraJobId : null, - uploaded_by: currentUser.email, - photo: p, - } - ); - return result; - }) - ); - - if (deleteAfterUpload) { - try { - await MediaLibrary.deleteAssetsAsync(ret.map((r) => r.mediaId)); - } catch (error) { - console.log("Unable to delete picture.", error); - } - } - forceRerender(); + if (data.length !== 0) setUploads(data); }; return ( + {!selectedCameraJobId && ( - + {t("mediabrowser.labels.nomedia")} @@ -223,7 +107,7 @@ export function ImageBrowserScreen({ }} /> )} - + ); } @@ -245,9 +129,4 @@ const styles = StyleSheet.create({ }, }); -function handleOnError(...props) { - console.log("HandleOnError", props); - logImEXEvent("imexmobile_upload_documents_error", { props }); -} - export default connect(mapStateToProps, null)(ImageBrowserScreen); diff --git a/components/screen-settings/screen-settings.component.jsx b/components/screen-settings/screen-settings.component.jsx index 4281fd7..32ce074 100644 --- a/components/screen-settings/screen-settings.component.jsx +++ b/components/screen-settings/screen-settings.component.jsx @@ -9,8 +9,6 @@ import * as Updates from "expo-updates"; export default function ScreenSettingsComponent() { const { t } = useTranslation(); - - console.log(Constants.manifest); return ( {t("settings.labels.version", { - number: Constants.manifest.version, + number: `${Constants.manifest.version}-${Constants.manifest.extra.expover}`, })} + {Updates.releaseChannel}