Delete unused code, remove logs, add dnd package.
This commit is contained in:
@@ -15,7 +15,7 @@ export default ({ config }) => {
|
||||
projectId: IS_ROME ? "df105e21-a07f-4425-af10-2200a7704a48" : "ffe01f3a-d507-4698-82cd-da1f1cad450b"
|
||||
}
|
||||
},
|
||||
icon: IS_ROME ? "./assets/RomeIcon.png" : "./assets/logo192noa.png",
|
||||
icon: IS_ROME ? "./assets/RomeIcon.png" : "./assets/ImEXlogo192noa.png",
|
||||
ios: {
|
||||
...config.ios,
|
||||
bundleIdentifier: IS_ROME ? "com.rome.mobile" : "com.imex.imexmobile"
|
||||
@@ -55,7 +55,5 @@ export default ({ config }) => {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
console.log("New Expo Config:", JSON.stringify(newConfig, null, 2));
|
||||
return newConfig;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { checkUserSession } from "@/redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors";
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import { loadDevMessages, loadErrorMessages } from "@apollo/client/dev";
|
||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||
import {
|
||||
DarkTheme,
|
||||
@@ -28,6 +29,9 @@ import { persistor, store } from "../redux/store";
|
||||
import "../translations/i18n";
|
||||
import { registerForPushNotificationsAsync } from "../util/notificationHandler";
|
||||
|
||||
loadDevMessages();
|
||||
loadErrorMessages();
|
||||
|
||||
function AuthenticatedLayout() {
|
||||
const { t } = useTranslation();
|
||||
const paperTheme = usePaperTheme();
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
// // src/toolbar.component.js file
|
||||
// import { Ionicons } from "@expo/vector-icons";
|
||||
// import { Camera } from "expo-camera";
|
||||
// import React from "react";
|
||||
// import {
|
||||
// StyleSheet,
|
||||
// TouchableOpacity,
|
||||
// TouchableWithoutFeedback,
|
||||
// View,
|
||||
// } from "react-native";
|
||||
|
||||
// const styles = StyleSheet.create({
|
||||
// alignCenter: {
|
||||
// flex: 1,
|
||||
// alignItems: "center",
|
||||
// justifyContent: "center",
|
||||
// },
|
||||
// bottomToolbar: {
|
||||
// marginTop: "auto",
|
||||
// height: 100,
|
||||
// display: "flex",
|
||||
// justifyContent: "space-evenly",
|
||||
// alignItems: "center",
|
||||
// flexDirection: "row",
|
||||
// },
|
||||
// captureBtn: {
|
||||
// width: 60,
|
||||
// height: 60,
|
||||
// borderWidth: 2,
|
||||
// borderRadius: 60,
|
||||
// borderColor: "#FFFFFF",
|
||||
// },
|
||||
// captureBtnActive: {
|
||||
// width: 80,
|
||||
// height: 80,
|
||||
// },
|
||||
// captureBtnInternal: {
|
||||
// width: 76,
|
||||
// height: 76,
|
||||
// borderWidth: 2,
|
||||
// borderRadius: 76,
|
||||
// backgroundColor: "red",
|
||||
// borderColor: "transparent",
|
||||
// },
|
||||
// });
|
||||
|
||||
// const { FlashMode: CameraFlashModes, Type: CameraTypes } = Camera.Constants;
|
||||
|
||||
// export default function CameraControls({
|
||||
// capturing = false,
|
||||
// cameraType = CameraTypes.back,
|
||||
// flashMode = CameraFlashModes.off,
|
||||
// setFlashMode,
|
||||
// setCameraType,
|
||||
// onCaptureIn,
|
||||
// onCaptureOut,
|
||||
// onLongCapture,
|
||||
// onShortCapture,
|
||||
// }) {
|
||||
// return (
|
||||
// <View style={styles.bottomToolbar}>
|
||||
// <TouchableOpacity
|
||||
// onPress={() =>
|
||||
// setFlashMode(
|
||||
// flashMode === CameraFlashModes.on
|
||||
// ? CameraFlashModes.off
|
||||
// : CameraFlashModes.on
|
||||
// )
|
||||
// }
|
||||
// >
|
||||
// <Ionicons
|
||||
// name={flashMode == CameraFlashModes.on ? "md-flash" : "md-flash-off"}
|
||||
// color="white"
|
||||
// size={30}
|
||||
// />
|
||||
// </TouchableOpacity>
|
||||
|
||||
// <TouchableWithoutFeedback
|
||||
// onPressIn={onCaptureIn}
|
||||
// onPressOut={onCaptureOut}
|
||||
// onLongPress={onLongCapture}
|
||||
// onPress={onShortCapture}
|
||||
// disabled={capturing}
|
||||
// >
|
||||
// <View style={[styles.captureBtn, capturing && styles.captureBtnActive]}>
|
||||
// {capturing && <View style={styles.captureBtnInternal} />}
|
||||
// </View>
|
||||
// </TouchableWithoutFeedback>
|
||||
|
||||
// <TouchableOpacity
|
||||
// onPress={() =>
|
||||
// setCameraType(
|
||||
// cameraType === CameraTypes.back
|
||||
// ? CameraTypes.front
|
||||
// : CameraTypes.back
|
||||
// )
|
||||
// }
|
||||
// >
|
||||
// <Ionicons name="md-reverse-camera" color="white" size={30} />
|
||||
// </TouchableOpacity>
|
||||
// </View>
|
||||
// );
|
||||
// }
|
||||
@@ -1,184 +0,0 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, RefreshControl, View } from "react-native";
|
||||
import { Button, List, Modal, Portal, Searchbar } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
||||
import {
|
||||
selectCurrentCameraJob,
|
||||
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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
cameraJobId: selectCurrentCameraJobId,
|
||||
cameraJob: selectCurrentCameraJob,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||
});
|
||||
|
||||
export function CameraSelectJob({
|
||||
bodyshop,
|
||||
cameraJobId,
|
||||
setCameraJobId,
|
||||
cameraJob,
|
||||
setCameraJob,
|
||||
}) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
||||
},
|
||||
skip: !bodyshop,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
if (loading) return <LoadingDisplay />;
|
||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||
|
||||
const showModal = () => setVisible(true);
|
||||
const hideModal = () => setVisible(false);
|
||||
|
||||
const onRefresh = async () => {
|
||||
return refetch();
|
||||
};
|
||||
|
||||
const onChangeSearch = (query) => setSearchQuery(query);
|
||||
|
||||
const jobs = data
|
||||
? searchQuery === ""
|
||||
? data.jobs
|
||||
: data.jobs.filter(
|
||||
(j) =>
|
||||
(j.ro_number || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.ownr_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.ownr_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.plate_no || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onDismiss={hideModal}
|
||||
// eslint-disable-next-line react-native/no-color-literals
|
||||
contentContainerStyle={{
|
||||
paddingTop: 20,
|
||||
paddingBottom: 20,
|
||||
margin: 12,
|
||||
flex: 1,
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
margin: 8,
|
||||
}}
|
||||
>
|
||||
<Button onPress={() => hideModal()}>
|
||||
<Ionicons name="arrow-back" size={32} color="dodgerblue" />
|
||||
</Button>
|
||||
<Searchbar
|
||||
style={{ flex: 1 }}
|
||||
onChangeText={onChangeSearch}
|
||||
value={searchQuery}
|
||||
/>
|
||||
</View>
|
||||
<FlatList
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
data={[{ id: "temp", ro_number: "Temporary Storage" }, ...jobs]}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={(object) => (
|
||||
<List.Item
|
||||
onPress={() => {
|
||||
setCameraJobId(object.item.id);
|
||||
setCameraJob(object.item);
|
||||
hideModal();
|
||||
setSearchQuery("");
|
||||
}}
|
||||
left={() => {
|
||||
if (object.item.id !== cameraJobId) return null;
|
||||
return (
|
||||
<Ionicons
|
||||
name="checkmark-circle"
|
||||
size={24}
|
||||
color="dodgerblue"
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
titleStyle={{
|
||||
...(object.item.id === cameraJobId
|
||||
? { color: "dodgerblue" }
|
||||
: {}),
|
||||
}}
|
||||
title={`${
|
||||
object.item.ro_number ? `${object.item.ro_number} ` : ``
|
||||
}${object.item.ownr_fn || ""} ${object.item.ownr_ln || ""} ${
|
||||
object.item.ownr_co_nm || ""
|
||||
} ${
|
||||
object.item.v_model_yr ? `- ${object.item.v_model_yr}` : ""
|
||||
} ${
|
||||
object.item.v_make_desc ? `- ${object.item.v_make_desc}` : ""
|
||||
} ${
|
||||
object.item.v_model_desc
|
||||
? `- ${object.item.v_model_desc}`
|
||||
: ""
|
||||
}`}
|
||||
key={object.item.id}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<Button mode="outlined" style={{ margin: 8 }} onPress={showModal}>
|
||||
{cameraJobId
|
||||
? cameraJobId === "temp"
|
||||
? t("mediabrowser.labels.temporarystorage")
|
||||
: `${cameraJob.ro_number ? `${cameraJob.ro_number} - ` : ``}${
|
||||
cameraJob.ownr_fn || ""
|
||||
} ${cameraJob.ownr_ln || ""} ${cameraJob.ownr_co_nm || ""} - ${
|
||||
cameraJob.v_model_yr || ""
|
||||
} ${cameraJob.v_make_desc || ""} ${cameraJob.v_model_desc || ""}`
|
||||
: t("mediabrowser.labels.selectjob")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CameraSelectJob);
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
export default function ErrorDisplay({ errorMessage }) {
|
||||
return (
|
||||
<View style={{ backgroundColor: "red" }}>
|
||||
<Text>{errorMessage}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
export default function FlatListItemSeparator() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
width: "100%",
|
||||
backgroundColor: "#000",
|
||||
opacity: 0.2,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
FlatList,
|
||||
Image,
|
||||
RefreshControl,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
|
||||
import * as Sentry from "@sentry/react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
import cleanAxios from "../../util/CleanAxios";
|
||||
|
||||
export default function JobDocumentsLocalComponent({ bodyshop, job }) {
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [images, setImages] = useState([]);
|
||||
const [imgIndex, setImgIndex] = useState(0);
|
||||
useEffect(() => {
|
||||
if (job.id) {
|
||||
getPhotos({ bodyshop, jobid: job.id, setImages });
|
||||
}
|
||||
}, [job.id, bodyshop]);
|
||||
|
||||
const onRefresh = async () => {
|
||||
return getPhotos({ bodyshop, jobid: job.id, setImages });
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<FlatList
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={false} onRefresh={onRefresh} />
|
||||
}
|
||||
data={images}
|
||||
numColumns={4}
|
||||
style={{ flex: 1 }}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={(object) => (
|
||||
<TouchableOpacity
|
||||
style={{ flex: 1 / 4, aspectRatio: 1, margin: 4 }}
|
||||
onPress={async () => {
|
||||
setImgIndex(object.index);
|
||||
setPreviewVisible(true);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
style={{ flex: 1 }}
|
||||
resizeMode="cover"
|
||||
source={{
|
||||
uri: object.item.thumbUrl,
|
||||
aspectRatio: 1,
|
||||
}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
<Text>
|
||||
{images?.filter((d) => d.type?.mime?.startsWith("image")).length}
|
||||
</Text>
|
||||
|
||||
<MediaCacheOverlay
|
||||
photos={images}
|
||||
imgIndex={imgIndex}
|
||||
setImgIndex={setImgIndex}
|
||||
previewVisible={previewVisible}
|
||||
setPreviewVisible={setPreviewVisible}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
async function getPhotos({ bodyshop, jobid, setImages }) {
|
||||
let localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
|
||||
if (localmediaserverhttp.endsWith("/")) {
|
||||
localmediaserverhttp = localmediaserverhttp.slice(0, -1);
|
||||
}
|
||||
|
||||
try {
|
||||
const imagesFetch = await cleanAxios.post(
|
||||
`${localmediaserverhttp}/jobs/list`,
|
||||
{
|
||||
jobid,
|
||||
},
|
||||
{ headers: { ims_token: bodyshop.localmediatoken } }
|
||||
);
|
||||
|
||||
const normalizedImages = imagesFetch.data
|
||||
.filter((d) => d.type?.mime?.startsWith("image"))
|
||||
.map((d, idx) => {
|
||||
return {
|
||||
...d,
|
||||
// src: `${localmediaserverhttp}/${d.src}`,
|
||||
|
||||
uri: `${localmediaserverhttp}${d.src}`,
|
||||
thumbUrl: `${localmediaserverhttp}${d.thumbnail}`,
|
||||
id: idx,
|
||||
};
|
||||
});
|
||||
|
||||
setImages(normalizedImages);
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: `Error fetching photos.`,
|
||||
|
||||
text2: JSON.stringify(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
import axios from "axios";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
FlatList,
|
||||
Image,
|
||||
RefreshControl,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import env from "../../env";
|
||||
import { DetermineFileType } from "../../util/document-upload.utility";
|
||||
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
//import { splitClient } from "../screen-main/screen-main.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobDocumentsComponent);
|
||||
|
||||
export function JobDocumentsComponent({ bodyshop, job, loading, refetch }) {
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [fullphotos, setFullPhotos] = useState([]);
|
||||
const [imgIndex, setImgIndex] = useState(0);
|
||||
|
||||
const useImgproxy = splitClient?.getTreatment("Imgproxy");
|
||||
|
||||
const onRefresh = async () => {
|
||||
return refetch();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getPhotos() {
|
||||
if (useImgproxy) {
|
||||
const result = await axios.post(
|
||||
`${env.API_URL}/media/imgproxy/thumbnails`,
|
||||
{
|
||||
jobid: job.id,
|
||||
}
|
||||
);
|
||||
|
||||
setFullPhotos(
|
||||
result.data.map((doc, idx) => {
|
||||
return {
|
||||
id: idx,
|
||||
videoUrl:
|
||||
DetermineFileType(doc.type) === "video" &&
|
||||
doc.originalUrlViaProxyPath,
|
||||
source:
|
||||
DetermineFileType(doc.type) === "video"
|
||||
? { uri: doc.thumbnailUrl }
|
||||
: { uri: doc.originalUrl },
|
||||
url:
|
||||
DetermineFileType(doc.type) === "video"
|
||||
? doc.thumbnailUrl
|
||||
: doc.originalUrl,
|
||||
uri:
|
||||
DetermineFileType(doc.type) === "video"
|
||||
? doc.originalUrlViaProxyPath
|
||||
: doc.originalUrl,
|
||||
thumbUrl: doc.thumbnailUrl,
|
||||
};
|
||||
})
|
||||
);
|
||||
} else {
|
||||
setFullPhotos(
|
||||
job.documents.map((doc, idx) => {
|
||||
return {
|
||||
id: idx,
|
||||
videoUrl:
|
||||
DetermineFileType(doc.type) === "video" && GenerateSrcUrl(doc),
|
||||
source:
|
||||
DetermineFileType(doc.type) === "video"
|
||||
? { uri: GenerateThumbUrl(doc) }
|
||||
: { uri: GenerateSrcUrl(doc) },
|
||||
url:
|
||||
DetermineFileType(doc.type) === "video"
|
||||
? GenerateThumbUrl(doc)
|
||||
: GenerateSrcUrl(doc),
|
||||
uri:
|
||||
DetermineFileType(doc.type) === "video"
|
||||
? GenerateThumbUrl(doc)
|
||||
: GenerateSrcUrl(doc),
|
||||
thumbUrl: GenerateThumbUrl(doc),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getPhotos();
|
||||
}, [job.documents]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<FlatList
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
data={fullphotos}
|
||||
numColumns={4}
|
||||
style={{ flex: 1 }}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={(object) => (
|
||||
<TouchableOpacity
|
||||
style={{ flex: 1 / 4, aspectRatio: 1, margin: 4 }}
|
||||
onPress={async () => {
|
||||
setImgIndex(object.index);
|
||||
setPreviewVisible(true);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
style={{ flex: 1 }}
|
||||
resizeMode="cover"
|
||||
source={{
|
||||
uri: object.item.thumbUrl,
|
||||
aspectRatio: 1,
|
||||
}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Text
|
||||
style={{ textAlign: "center", color: useImgproxy ? "blue" : "black" }}
|
||||
>
|
||||
{fullphotos.length}
|
||||
</Text>
|
||||
|
||||
<MediaCacheOverlay
|
||||
photos={fullphotos}
|
||||
imgIndex={imgIndex}
|
||||
setImgIndex={setImgIndex}
|
||||
previewVisible={previewVisible}
|
||||
setPreviewVisible={setPreviewVisible}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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}` : ""
|
||||
}`;
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";
|
||||
import { Card, DataTable } from "react-native-paper";
|
||||
|
||||
export default function JobLines({ job, loading, refetch }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!job) {
|
||||
<Card>
|
||||
<Text>Job is not defined.</Text>
|
||||
</Card>;
|
||||
}
|
||||
const onRefresh = async () => {
|
||||
return refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<DataTable>
|
||||
<DataTable.Header>
|
||||
<DataTable.Title style={{ flex: 4 }}>
|
||||
{t("jobdetail.labels.lines_desc")}
|
||||
</DataTable.Title>
|
||||
<DataTable.Title style={{ flex: 2 }}>
|
||||
{t("jobdetail.labels.lines_lbr_ty")}
|
||||
</DataTable.Title>
|
||||
<DataTable.Title style={{ flex: 1 }}>
|
||||
{t("jobdetail.labels.lines_lb_hrs")}
|
||||
</DataTable.Title>
|
||||
<DataTable.Title style={{ flex: 2 }}>
|
||||
{t("jobdetail.labels.lines_part_type")}
|
||||
</DataTable.Title>
|
||||
<DataTable.Title style={{ flex: 1 }}>
|
||||
{t("jobdetail.labels.lines_qty")}
|
||||
</DataTable.Title>
|
||||
</DataTable.Header>
|
||||
</DataTable>
|
||||
|
||||
<FlatList
|
||||
data={job.joblines}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={(object) => (
|
||||
<DataTable.Row>
|
||||
<DataTable.Cell style={{ flex: 4 }}>
|
||||
{object.item.line_desc}
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell style={{ flex: 2 }}>
|
||||
{object.item.mod_lbr_ty &&
|
||||
t(`jobdetail.lbr_types.${object.item.mod_lbr_ty}`)}
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell style={{ flex: 1 }}>
|
||||
{object.item.mod_lb_hrs}
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell style={{ flex: 2 }}>
|
||||
{object.item.part_type &&
|
||||
t(`jobdetail.part_types.${object.item.part_type}`)}
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell style={{ flex: 1 }}>
|
||||
{object.item.part_qty}
|
||||
</DataTable.Cell>
|
||||
</DataTable.Row>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const localStyles = StyleSheet.create({});
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, List, Title } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||
});
|
||||
|
||||
export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation();
|
||||
// const _swipeableRow = useRef(null);
|
||||
|
||||
// const RenderRightAction = (progress, dragX) => {
|
||||
// const scale = dragX.interpolate({
|
||||
// inputRange: [-100, 0],
|
||||
// outputRange: [0.7, 0],
|
||||
// });
|
||||
|
||||
// return (
|
||||
// <TouchableOpacity
|
||||
// style={[styles.swipe_view, styles.swipe_view_blue]}
|
||||
// onPress={() => {
|
||||
// logImEXEvent("imexmobile_setcamerajobid_swipe");
|
||||
// setCameraJobId(item.id);
|
||||
// setCameraJob(item);
|
||||
// navigation.navigate("MediaBrowserTab");
|
||||
// _swipeableRow.current.close();
|
||||
// }}
|
||||
// >
|
||||
// <Animated.View
|
||||
// style={{
|
||||
// transform: [{ scale }],
|
||||
// }}
|
||||
// >
|
||||
// <Ionicons name="ios-camera" size={64} color="white" />
|
||||
// </Animated.View>
|
||||
// </TouchableOpacity>
|
||||
// );
|
||||
// };
|
||||
|
||||
const onPress = () => {
|
||||
logImEXEvent("imexmobile_view_job_detail");
|
||||
navigation.push("JobDetail", {
|
||||
jobId: item.id,
|
||||
title: item.ro_number || t("general.labels.na"),
|
||||
job: item,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
onPress={onPress}
|
||||
title={<Title>{item.ro_number || t("general.labels.na")}</Title>}
|
||||
description={`${item.ownr_fn || ""} ${item.ownr_ln || ""} ${
|
||||
item.ownr_co_nm || ""
|
||||
} - ${item.v_model_yr || ""} ${item.v_make_desc || ""} ${
|
||||
item.v_model_desc || ""
|
||||
}`}
|
||||
right={({ style }) => (
|
||||
<Button
|
||||
style={[style, { alignSelf: "center" }]}
|
||||
onPress={() => {
|
||||
logImEXEvent("imexmobile_setcamerajobid_row");
|
||||
setCameraJobId(item.id);
|
||||
setCameraJob(item);
|
||||
navigation.navigate("MediaBrowserTab");
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
style={[style, { alignSelf: "center" }]}
|
||||
name="add"
|
||||
size={32}
|
||||
color="dodgerblue"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobListItem);
|
||||
@@ -1,94 +0,0 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, RefreshControl, Text, View } from "react-native";
|
||||
import { Button, Searchbar, Title } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import JobListItem from "../job-list-item/job-list-item.component";
|
||||
import LoadingDisplay from "../loading-display/loading-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function JobListComponent({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
||||
},
|
||||
skip: !bodyshop,
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
|
||||
const onRefresh = async () => {
|
||||
return refetch();
|
||||
};
|
||||
const onChangeSearch = (query) => setSearchQuery(query);
|
||||
|
||||
if (loading) return <LoadingDisplay />;
|
||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||
if (data && data.jobs && data.jobs.length === 0)
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<Title>
|
||||
<Text>{t("joblist.labels.nojobs")}</Text>
|
||||
</Title>
|
||||
<Button onPress={() => refetch()}>
|
||||
{t("joblist.actions.refresh")}
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
|
||||
const jobs = data
|
||||
? searchQuery === ""
|
||||
? data.jobs
|
||||
: data.jobs.filter(
|
||||
(j) =>
|
||||
(j.ro_number || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.ownr_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.ownr_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
(j.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Searchbar
|
||||
onChangeText={onChangeSearch}
|
||||
value={searchQuery}
|
||||
placeholder={t("joblist.labels.search")}
|
||||
/>
|
||||
<FlatList
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
data={jobs}
|
||||
renderItem={(object) => <JobListItem item={object.item} />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobListComponent);
|
||||
@@ -1,46 +0,0 @@
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { DateTime } from "luxon";
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Card } from "react-native-paper";
|
||||
export default function NoteListItem({ item }) {
|
||||
return (
|
||||
<Card style={{ margin: 8 }}>
|
||||
<Card.Content>
|
||||
<View style={{ display: "flex", flex: 1 }}>
|
||||
<Text>{item.text}</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "column",
|
||||
alignSelf: "flex-end",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{item.private && (
|
||||
<AntDesign
|
||||
name="eyeo"
|
||||
style={{ margin: 4 }}
|
||||
size={24}
|
||||
color="black"
|
||||
/>
|
||||
)}
|
||||
{item.critical && (
|
||||
<AntDesign
|
||||
name="warning"
|
||||
style={{ margin: 4 }}
|
||||
size={24}
|
||||
color="tomato"
|
||||
/>
|
||||
)}
|
||||
<Text style={{ fontSize: 12 }}>{item.created_by}</Text>
|
||||
<Text style={{ fontSize: 12 }}>
|
||||
{DateTime.fromISO(item.created_at).toLocaleString(
|
||||
DateTime.DATETIME_SHORT
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, RefreshControl, Text } from "react-native";
|
||||
import { Card } from "react-native-paper";
|
||||
import JobNotesItem from "../job-notes-item/job-notes-item.component";
|
||||
export default function JobNotes({ job, loading, refetch }) {
|
||||
const { t } = useTranslation();
|
||||
if (!job) {
|
||||
<Card>
|
||||
<Text>Job is not defined.</Text>
|
||||
</Card>;
|
||||
}
|
||||
|
||||
const onRefresh = async () => {
|
||||
return refetch();
|
||||
};
|
||||
|
||||
if (job.notes.length === 0)
|
||||
return (
|
||||
<Card>
|
||||
<Card.Content>
|
||||
<Text>{t("jobdetail.labels.nojobnotes")}</Text>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
data={job.notes}
|
||||
renderItem={(object) => <JobNotesItem item={object.item} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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 || jobid === "temp",
|
||||
});
|
||||
|
||||
if (!jobid || !data) return <></>;
|
||||
|
||||
const progress =
|
||||
data.documents_aggregate.aggregate.sum.size /
|
||||
((bodyshop && bodyshop.jobsizelimit) || 1);
|
||||
|
||||
return (
|
||||
<View style={{ margin: 10 }}>
|
||||
<Text style={{ marginBottom: 5 }}>
|
||||
{t("mediabrowser.labels.storageused", {
|
||||
used: formatBytes(data.documents_aggregate.aggregate.sum.size),
|
||||
total: formatBytes((bodyshop && bodyshop.jobsizelimit) || 1),
|
||||
percent: Math.round(progress * 100),
|
||||
})}
|
||||
</Text>
|
||||
<ProgressBar style={[style]} progress={progress} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Card, Headline, Subheading } from "react-native-paper";
|
||||
import DataLabelComponent from "../../components/data-label/data-label";
|
||||
import StyleRepeater from "../style-repeater/style-repeater";
|
||||
import styles from "../styles";
|
||||
|
||||
export default function JobTombstone({ job, loading, refetch }) {
|
||||
const { t } = useTranslation();
|
||||
if (!job) {
|
||||
<Card>
|
||||
<Text>Job is not defined.</Text>
|
||||
</Card>;
|
||||
}
|
||||
const onRefresh = async () => {
|
||||
return refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.cardBackground}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
>
|
||||
<StyleRepeater childStyle={{ margin: 4 }}>
|
||||
<Card>
|
||||
<Card.Title title={t("jobdetail.labels.jobinfo")} />
|
||||
<Card.Content>
|
||||
<Headline>{job.status}</Headline>
|
||||
{job.inproduction && (
|
||||
<Subheading>{t("objects.jobs.labels.inproduction")}</Subheading>
|
||||
)}
|
||||
{job.inproduction &&
|
||||
job.production_vars &&
|
||||
!!job.production_vars.note && (
|
||||
<Subheading>{job.production_vars.note}</Subheading>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Title title={t("jobdetail.labels.claiminformation")} />
|
||||
<Card.Content style={localStyles.twoColumnCard}>
|
||||
<View style={localStyles.twoColumnCardColumn}>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.owner")}
|
||||
content={`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||
job.ownr_co_nm || ""
|
||||
}`}
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.vehicle")}
|
||||
content={
|
||||
<View>
|
||||
<Text>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
|
||||
job.v_model_desc || ""
|
||||
}`}</Text>
|
||||
<Text>{job.v_vin}</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View style={localStyles.twoColumnCardColumn}>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.ins_co_nm")}
|
||||
content={job.ins_co_nm}
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.clm_no")}
|
||||
content={job.clm_no}
|
||||
/>
|
||||
</View>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card.Title title={t("jobdetail.labels.employeeassignments")} />
|
||||
<Card.Content>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.employee_body")}
|
||||
content={`${
|
||||
(job.employee_body_rel && job.employee_body_rel.first_name) ||
|
||||
""
|
||||
} ${
|
||||
(job.employee_body_rel && job.employee_body_rel.last_name) || ""
|
||||
}`}
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.employee_prep")}
|
||||
content={`${
|
||||
(job.employee_prep_rel && job.employee_prep_rel.first_name) ||
|
||||
""
|
||||
} ${
|
||||
(job.employee_prep_rel && job.employee_prep_rel.last_name) || ""
|
||||
}`}
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.employee_refinish")}
|
||||
content={`${
|
||||
(job.employee_refinish_rel &&
|
||||
job.employee_refinish_rel.first_name) ||
|
||||
""
|
||||
} ${
|
||||
(job.employee_refinish_rel &&
|
||||
job.employee_refinish_rel.last_name) ||
|
||||
""
|
||||
}`}
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.employee_csr")}
|
||||
content={`${
|
||||
(job.employee_csr_rel && job.employee_csr_rel.first_name) || ""
|
||||
} ${
|
||||
(job.employee_csr_rel && job.employee_csr_rel.last_name) || ""
|
||||
}`}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card.Title title={t("jobdetail.labels.dates")} />
|
||||
<Card.Content style={localStyles.twoColumnCard}>
|
||||
<View style={localStyles.twoColumnCardColumn}>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.scheduled_in")}
|
||||
content={job.scheduled_in}
|
||||
dateTime
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.actual_in")}
|
||||
content={job.actual_in}
|
||||
dateTime
|
||||
/>
|
||||
</View>
|
||||
<View style={localStyles.twoColumnCardColumn}>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.scheduled_completion")}
|
||||
content={job.scheduled_completion}
|
||||
dateTime
|
||||
/>
|
||||
<DataLabelComponent
|
||||
label={t("objects.jobs.fields.scheduled_delivery")}
|
||||
content={job.scheduled_delivery}
|
||||
dateTime
|
||||
/>
|
||||
</View>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</StyleRepeater>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
twoColumnCard: { display: "flex", flexDirection: "row" },
|
||||
twoColumnCardColumn: { flex: 1 },
|
||||
status: {
|
||||
textAlign: "center",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
},
|
||||
inproduction: {
|
||||
textAlign: "center",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { BarIndicator } from "react-native-indicators";
|
||||
|
||||
export default function LoadingDisplay({ count = 5 }) {
|
||||
//TODO: This is throwing an error per expo, but it appears to be happening inside the component itself.
|
||||
return (
|
||||
<View style={{ flex: 1, alignContent: "center", justifyContent: "center" }}>
|
||||
<BarIndicator count={count} color="dodgerblue" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { SafeAreaView } from "react-native";
|
||||
import React from "react";
|
||||
|
||||
import ImageView from "react-native-image-viewing";
|
||||
|
||||
export default function MediaCacheOverlay({
|
||||
photos,
|
||||
previewVisible,
|
||||
setPreviewVisible,
|
||||
imgIndex,
|
||||
setImgIndex,
|
||||
}) {
|
||||
//const videoRef = React.useRef(null);
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<ImageView
|
||||
onRequestClose={() => setPreviewVisible(false)}
|
||||
visible={previewVisible}
|
||||
images={photos}
|
||||
imageIndex={imgIndex}
|
||||
// onImageIndexChange={(...props) => {
|
||||
// // console.log(props);
|
||||
// }}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -1,157 +0,0 @@
|
||||
// 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 { Text, View } from "react-native";
|
||||
// import { connect } from "react-redux";
|
||||
// import { createStructuredSelector } from "reselect";
|
||||
// import {
|
||||
// selectCurrentCameraJob,
|
||||
// selectCurrentCameraJobId,
|
||||
// } from "../../redux/app/app.selectors";
|
||||
// import { addPhoto } from "../../redux/photos/photos.actions";
|
||||
// import CameraControls from "../camera-controls/camera-controls.component";
|
||||
// import CameraSelectJob from "../camera-select-job/camera-select-job.component";
|
||||
|
||||
// const mapStateToProps = createStructuredSelector({
|
||||
// cameraJobId: selectCurrentCameraJobId,
|
||||
// cameraJob: selectCurrentCameraJob,
|
||||
// });
|
||||
// const mapDispatchToProps = (dispatch) => ({
|
||||
// addPhoto: (photo) => dispatch(addPhoto(photo)),
|
||||
// });
|
||||
|
||||
// export function ScreenCamera({ cameraJobId, addPhoto }) {
|
||||
// const [hasPermission, setHasPermission] = useState(null);
|
||||
// const [state, setState] = useState({
|
||||
// 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();
|
||||
// setHasPermission(status === "granted");
|
||||
// })();
|
||||
// }, []);
|
||||
|
||||
// const setFlashMode = (flashMode) => setState({ ...state, flashMode });
|
||||
// const setCameraType = (cameraType) => setState({ ...state, cameraType });
|
||||
// const handleCaptureIn = () => setState({ ...state, capturing: true });
|
||||
|
||||
// const handleCaptureOut = () => {
|
||||
// if (state.capturing) cameraRef.current.stopRecording();
|
||||
// };
|
||||
|
||||
// const handleShortCapture = async () => {
|
||||
// if (cameraRef.current) {
|
||||
// const options = {
|
||||
// quality: 0.8,
|
||||
// //base64: true,
|
||||
// skipProcessing: true,
|
||||
// };
|
||||
|
||||
// let photo = await cameraRef.current.takePictureAsync(options);
|
||||
// const filename = photo.uri.substring(photo.uri.lastIndexOf("/") + 1);
|
||||
// const newUri = FileSystem.documentDirectory + "photos/" + filename;
|
||||
|
||||
// await FileSystem.moveAsync({
|
||||
// from: photo.uri,
|
||||
// to: newUri,
|
||||
// });
|
||||
// setState({ ...state, capturing: false });
|
||||
// addPhoto({
|
||||
// ...photo,
|
||||
// id: filename,
|
||||
// uri: newUri,
|
||||
// jobId: cameraJobId,
|
||||
// video: false,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleLongCapture = async () => {
|
||||
// console.log("Taking a video!");
|
||||
// if (cameraRef.current) {
|
||||
// let video = await cameraRef.current.recordAsync();
|
||||
|
||||
// const filename = video.uri.substring(video.uri.lastIndexOf("/") + 1);
|
||||
// const newUri = FileSystem.documentDirectory + "photos/" + filename;
|
||||
|
||||
// await FileSystem.moveAsync({
|
||||
// from: video.uri,
|
||||
// to: newUri,
|
||||
// });
|
||||
// setState({ ...state, capturing: false });
|
||||
// console.log("Adding Photo", {
|
||||
// ...video,
|
||||
// id: filename,
|
||||
// uri: newUri,
|
||||
// jobId: cameraJobId,
|
||||
// video: true,
|
||||
// });
|
||||
// addPhoto({
|
||||
// ...video,
|
||||
// id: filename,
|
||||
// uri: newUri,
|
||||
// jobId: cameraJobId,
|
||||
// video: true,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// if (hasPermission === null || !state.tabHasFocus) {
|
||||
// return <View />;
|
||||
// }
|
||||
|
||||
// if (hasPermission === false) {
|
||||
// return <Text>No access to camera. Please ensure that you allow it.</Text>;
|
||||
// }
|
||||
|
||||
// const { flashMode, cameraType, capturing } = state;
|
||||
|
||||
// return (
|
||||
// <Camera
|
||||
// style={{ flex: 1, display: "flex" }}
|
||||
// type={state.cameraType}
|
||||
// ref={cameraRef}
|
||||
// ratio={"16:9"}
|
||||
// >
|
||||
// <View
|
||||
// style={{
|
||||
// flex: 1,
|
||||
// }}
|
||||
// >
|
||||
// <CameraSelectJob />
|
||||
|
||||
// <CameraControls
|
||||
// capturing={capturing}
|
||||
// flashMode={flashMode}
|
||||
// cameraType={cameraType}
|
||||
// setFlashMode={setFlashMode}
|
||||
// setCameraType={setCameraType}
|
||||
// onCaptureIn={handleCaptureIn}
|
||||
// onCaptureOut={handleCaptureOut}
|
||||
// onLongCapture={handleLongCapture}
|
||||
// onShortCapture={handleShortCapture}
|
||||
// />
|
||||
// </View>
|
||||
// </Camera>
|
||||
// );
|
||||
// }
|
||||
// export default connect(mapStateToProps, mapDispatchToProps)(ScreenCamera);
|
||||
@@ -1,101 +0,0 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import { SceneMap, TabView, TabBar } from "react-native-tab-view";
|
||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import JobDocuments from "../job-documents/job-documents.component";
|
||||
import JobLines from "../job-lines/job-lines.component";
|
||||
import JobNotes from "../job-notes/job-notes.component";
|
||||
import JobTombstone from "../job-tombstone/job-tombstone.component";
|
||||
import LoadingDisplay from "../loading-display/loading-display.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobDocumentsLocalComponent from "../job-documents/job-documents-local.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScreenJobDetail);
|
||||
|
||||
export function ScreenJobDetail({ bodyshop, route }) {
|
||||
const {
|
||||
params: { jobId },
|
||||
} = route;
|
||||
const { t } = useTranslation();
|
||||
const layout = useWindowDimensions();
|
||||
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
||||
variables: {
|
||||
id: jobId,
|
||||
},
|
||||
skip: !jobId,
|
||||
});
|
||||
|
||||
const renderTabBar = (props) => (
|
||||
<TabBar
|
||||
{...props}
|
||||
indicatorStyle={{ backgroundColor: "#ffffff" }}
|
||||
// style={{ backgroundColor: "dodgerblue" }}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderScene = SceneMap({
|
||||
job: () =>
|
||||
JobTombstone({
|
||||
job: data.jobs_by_pk,
|
||||
loading: loading,
|
||||
refetch: refetch,
|
||||
}),
|
||||
lines: () =>
|
||||
JobLines({
|
||||
job: data.jobs_by_pk,
|
||||
loading: loading,
|
||||
refetch: refetch,
|
||||
}),
|
||||
|
||||
documents: () => {
|
||||
return bodyshop.uselocalmediaserver ? (
|
||||
<JobDocumentsLocalComponent job={data.jobs_by_pk} bodyshop={bodyshop} />
|
||||
) : (
|
||||
<JobDocuments
|
||||
job={data.jobs_by_pk}
|
||||
loading={loading}
|
||||
refetch={refetch}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
notes: () =>
|
||||
JobNotes({
|
||||
job: data.jobs_by_pk,
|
||||
loading: loading,
|
||||
refetch: refetch,
|
||||
}),
|
||||
});
|
||||
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const [routes] = React.useState([
|
||||
{ key: "job", title: t("jobdetail.labels.job") },
|
||||
{ key: "lines", title: t("jobdetail.labels.lines") },
|
||||
{ key: "documents", title: t("jobdetail.labels.documents") },
|
||||
{ key: "notes", title: t("jobdetail.labels.notes") },
|
||||
]);
|
||||
|
||||
if (loading) return <LoadingDisplay />;
|
||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||
|
||||
return (
|
||||
<TabView
|
||||
style={{ flex: 1 }}
|
||||
navigationState={{ index, routes }}
|
||||
renderScene={renderScene}
|
||||
onIndexChange={setIndex}
|
||||
initialLayout={{ width: layout.width }}
|
||||
renderTabBar={renderTabBar}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import React from "react";
|
||||
import JobListComponent from "../job-list/job-list.component.jsx";
|
||||
|
||||
export default function ScreenJobList({ navigation }) {
|
||||
return <JobListComponent />;
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import i18n from "i18next";
|
||||
import moment from "moment";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
||||
import {
|
||||
checkUserSession,
|
||||
emailSignInStart,
|
||||
signOutStart,
|
||||
} from "../../redux/user/user.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import ScreenJobDetail from "../screen-job-detail/screen-job-detail.component";
|
||||
import ScreenJobList from "../screen-job-list/screen-job-list.component";
|
||||
import ScreenMediaBrowser from "../screen-media-browser/screen-media-browser.component";
|
||||
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";
|
||||
|
||||
const ActiveJobStack = createNativeStackNavigator();
|
||||
const MoreStack = createNativeStackNavigator();
|
||||
const BottomTabs = createBottomTabNavigator();
|
||||
const MediaBrowserStack = createNativeStackNavigator();
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
emailSignInStart: (email, password) =>
|
||||
dispatch(emailSignInStart({ email, password })),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||
});
|
||||
|
||||
const JobsTabNavigator = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(({ setCameraJobId, setCameraJob }) => (
|
||||
<ActiveJobStack.Navigator initialRouteName="JobList">
|
||||
<ActiveJobStack.Screen
|
||||
name="JobList"
|
||||
options={() => ({
|
||||
title: i18n.t("joblist.labels.activejobs"),
|
||||
})}
|
||||
component={ScreenJobList}
|
||||
/>
|
||||
<ActiveJobStack.Screen
|
||||
name="JobDetail"
|
||||
component={ScreenJobDetail}
|
||||
options={({ navigation, route }) => ({
|
||||
title:
|
||||
(route.params && route.params.title) ||
|
||||
i18n.t("joblist.labels.detail"),
|
||||
// eslint-disable-next-line react/display-name
|
||||
headerRight: () => (
|
||||
<Button
|
||||
onPress={() => {
|
||||
logImEXEvent("imexmobile_setcamerajobid_jobheader");
|
||||
setCameraJobId(route.params.jobId);
|
||||
setCameraJob(route.params.job);
|
||||
navigation.navigate("MediaBrowserTab");
|
||||
}}
|
||||
>
|
||||
<Ionicons name="add" size={32} color="dodgerblue" />
|
||||
</Button>
|
||||
),
|
||||
})}
|
||||
/>
|
||||
</ActiveJobStack.Navigator>
|
||||
));
|
||||
|
||||
const MediaBrowserStackNavigator = () => (
|
||||
<MediaBrowserStack.Navigator initialRouteName="MediaBrowser">
|
||||
<MediaBrowserStack.Screen
|
||||
name="MediaBrowser"
|
||||
options={{ title: i18n.t("mediabrowser.titles.mediabrowsertab") }}
|
||||
component={ScreenMediaBrowser}
|
||||
/>
|
||||
</MediaBrowserStack.Navigator>
|
||||
);
|
||||
|
||||
const MoreStackNavigator = () => (
|
||||
<MoreStack.Navigator>
|
||||
<MoreStack.Screen
|
||||
name="Settings"
|
||||
options={{
|
||||
title: i18n.t("settings.titles.settings"),
|
||||
}}
|
||||
component={ScreenSettingsComponent}
|
||||
/>
|
||||
</MoreStack.Navigator>
|
||||
);
|
||||
|
||||
const BottomTabsNavigator = () => (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={({ route }) => ({
|
||||
// eslint-disable-next-line react/display-name
|
||||
tabBarIcon: ({ color, size }) => {
|
||||
let iconName;
|
||||
if (route.name === "JobTab") {
|
||||
iconName = "list";
|
||||
} else if (route.name === "MoreTab") {
|
||||
iconName = "settings";
|
||||
} else if (route.name === "MediaBrowserTab") {
|
||||
iconName = "camera";
|
||||
} else {
|
||||
//iconName = "customerservice";
|
||||
}
|
||||
|
||||
return <Ionicons name={iconName} size={size} color={color} />;
|
||||
},
|
||||
})}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
name="JobTab"
|
||||
options={{
|
||||
title: i18n.t("joblist.titles.jobtab"),
|
||||
headerShown: false,
|
||||
}}
|
||||
component={JobsTabNavigator}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="MediaBrowserTab"
|
||||
options={{
|
||||
title: i18n.t("mediabrowser.titles.mediabrowsertab"),
|
||||
headerShown: false,
|
||||
}}
|
||||
component={MediaBrowserStackNavigator}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="MoreTab"
|
||||
options={{ title: i18n.t("more.titles.moretab"), headerShown: false }}
|
||||
component={MoreStackNavigator}
|
||||
/>
|
||||
</BottomTabs.Navigator>
|
||||
);
|
||||
|
||||
export function ScreenMainComponent({
|
||||
checkUserSession,
|
||||
currentUser,
|
||||
bodyshop,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
checkUserSession();
|
||||
}, [checkUserSession]);
|
||||
|
||||
// useEffect(() => {
|
||||
// // LogRocket.init("idt6oy/imex-mobile", {
|
||||
// // updateId: Updates.isEmbeddedLaunch ? null : Updates.updateId,
|
||||
// // expoChannel: Updates.channel,
|
||||
// // });
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<NavigationContainer>
|
||||
{currentUser.authorized === null ? (
|
||||
<ScreenSplash />
|
||||
) : currentUser.authorized ? (
|
||||
bodyshop ? (
|
||||
HasAccess(bodyshop) ? (
|
||||
<BottomTabsNavigator />
|
||||
) : (
|
||||
<ScreenSplash noAccess />
|
||||
)
|
||||
) : (
|
||||
<ScreenSplash />
|
||||
)
|
||||
) : (
|
||||
<ScreenSignIn />
|
||||
)}
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScreenMainComponent);
|
||||
|
||||
function HasAccess({ features }) {
|
||||
if (features.mobile === undefined || features.mobile === true) return true;
|
||||
if (features.mobile === false) return false;
|
||||
const d = moment(moment(features.mobile));
|
||||
if (d.isValid()) return d.isAfter(moment());
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
import Constants from "expo-constants";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleDeleteAfterUpload } from "../../redux/app/app.actions";
|
||||
import {
|
||||
selectCurrentCameraJobId,
|
||||
selectDeleteAfterUpload,
|
||||
} from "../../redux/app/app.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.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 UploadProgressLocal from "../upload-progress-local/upload-progress-local.component";
|
||||
import UploadProgress from "../upload-progress/upload-progress.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedCameraJobId: selectCurrentCameraJobId,
|
||||
bodyshop: selectBodyshop,
|
||||
deleteAfterUpload: selectDeleteAfterUpload,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
|
||||
});
|
||||
|
||||
export function ImageBrowserScreen({
|
||||
bodyshop,
|
||||
selectedCameraJobId,
|
||||
//toggleDeleteAfterUpload,
|
||||
// deleteAfterUpload,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [uploads, setUploads] = useState(null);
|
||||
const [density, setDensity] = useState(3);
|
||||
const [tick, setTick] = useState(0);
|
||||
|
||||
const forceRerender = useCallback(() => {
|
||||
setTick((tick) => tick + 1);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (Constants.platform.ios) {
|
||||
const cameraRollStatus =
|
||||
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
const cameraStatus = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (
|
||||
cameraRollStatus.status !== "granted" ||
|
||||
cameraStatus.status !== "granted"
|
||||
) {
|
||||
alert(
|
||||
"Photo and Camera permissions have not been granted. Please open the settings app and allow these permissions to upload photos."
|
||||
);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const pickImage = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ["images", "videos"],
|
||||
aspect: [4, 3],
|
||||
quality: 1,
|
||||
allowsMultipleSelection: true,
|
||||
});
|
||||
setUploads(result.assets);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.flex, styles.container]}>
|
||||
<CameraSelectJob />
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<Text style={{ margin: 10 }}>
|
||||
{t("mediabrowser.labels.localserver", {
|
||||
url: bodyshop.localmediaserverhttp,
|
||||
})}
|
||||
</Text>
|
||||
) : (
|
||||
<JobSpaceAvailable jobid={selectedCameraJobId} key={`${tick}-space`} />
|
||||
)}
|
||||
<UploadDeleteSwitch />
|
||||
|
||||
{!selectedCameraJobId && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text>{t("mediabrowser.labels.selectjobassetselector")}</Text>
|
||||
</View>
|
||||
)}
|
||||
<Button onPress={pickImage}>Media Select</Button>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<UploadProgressLocal
|
||||
uploads={uploads}
|
||||
setUploads={setUploads}
|
||||
forceRerender={forceRerender}
|
||||
/>
|
||||
) : (
|
||||
<UploadProgress
|
||||
uploads={uploads}
|
||||
setUploads={setUploads}
|
||||
forceRerender={forceRerender}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
display: "flex",
|
||||
// position: "relative",
|
||||
},
|
||||
buttonStyle: {
|
||||
//backgroundColor: "tomato",
|
||||
},
|
||||
textStyle: {
|
||||
color: "dodgerblue",
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImageBrowserScreen);
|
||||
|
||||
// // Utility to get asset ID from URI if missing
|
||||
// async function getAssetIdFromUri(uri, filename = null, maxPages = 10) {
|
||||
// let after = null;
|
||||
// let found = null;
|
||||
// let pageCount = 0;
|
||||
|
||||
// while (!found && pageCount < maxPages) {
|
||||
// const page = await MediaLibrary.getAssetsAsync({
|
||||
// first: 100,
|
||||
// mediaType: [MediaLibrary.MediaType.photo, MediaLibrary.MediaType.video],
|
||||
// after,
|
||||
// });
|
||||
|
||||
// // Try to match by URI
|
||||
// found = page.assets.find((asset) => asset.uri === uri);
|
||||
|
||||
// // Fallback: try to match by filename if not found and filename is available
|
||||
// if (!found && filename) {
|
||||
// found = page.assets.find((asset) => asset.filename === filename);
|
||||
// }
|
||||
|
||||
// after = page.endCursor;
|
||||
// pageCount++;
|
||||
// if (!after) break;
|
||||
// }
|
||||
|
||||
// return found ? found.id : null;
|
||||
// }
|
||||
@@ -1,140 +0,0 @@
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Image,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
removeAllPhotos,
|
||||
uploadAllPhotos,
|
||||
} from "../../redux/photos/photos.actions";
|
||||
import {
|
||||
selectPhotos,
|
||||
selectUploadInProgress,
|
||||
} from "../../redux/photos/photos.selectors";
|
||||
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
photos: selectPhotos,
|
||||
uploadInProgress: selectUploadInProgress,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
removeAllPhotos: () => dispatch(removeAllPhotos()),
|
||||
uploadAllphotos: () => dispatch(uploadAllPhotos()),
|
||||
});
|
||||
|
||||
export function ScreenMediaCache({
|
||||
photos,
|
||||
removeAllPhotos,
|
||||
uploadAllphotos,
|
||||
uploadInProgress,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [imgIndex, setImgIndex] = useState(0);
|
||||
|
||||
const groupedPhotos = _.groupBy(photos, "jobId");
|
||||
|
||||
const RenderJobPictures = ({ jobId, jobPhotos }) => (
|
||||
<View>
|
||||
<Text>{jobId}</Text>
|
||||
<FlatList
|
||||
data={jobPhotos}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={styles.listContentContainer}
|
||||
keyExtractor={(item) => item.id}
|
||||
numColumns={4}
|
||||
renderItem={(object) =>
|
||||
object.item.video ? (
|
||||
<Text>Video</Text>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1 / 4, //here you can use flex:1 also
|
||||
aspectRatio: 1,
|
||||
}}
|
||||
onPress={() => {
|
||||
setImgIndex(object.index);
|
||||
setPreviewVisible(true);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: object.item.uri }}
|
||||
style={{ flex: 1 }}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.actions}>
|
||||
<Button onPress={() => removeAllPhotos()}>
|
||||
<Text>{t("mediacache.actions.deleteall")}</Text>
|
||||
</Button>
|
||||
<Button onPress={() => uploadAllphotos()}>
|
||||
<Text>{t("mediacache.actions.uploadall")}</Text>
|
||||
{uploadInProgress && <ActivityIndicator />}
|
||||
</Button>
|
||||
</View>
|
||||
<FlatList
|
||||
data={groupedPhotos ? Object.keys(groupedPhotos) : []}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={styles.listContentContainer}
|
||||
keyExtractor={(item) => item}
|
||||
renderItem={(object) => (
|
||||
<RenderJobPictures
|
||||
jobPhotos={groupedPhotos[object.item]}
|
||||
jobId={object.item}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<MediaCacheOverlay
|
||||
imgIndex={imgIndex}
|
||||
setImgIndex={setImgIndex}
|
||||
previewVisible={previewVisible}
|
||||
setPreviewVisible={setPreviewVisible}
|
||||
/>
|
||||
<Text>{`${photos.length} Photos`}</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
actions: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
},
|
||||
listContentContainer: {
|
||||
//flex: 1,
|
||||
justifyContent: "flex-start",
|
||||
//flexDirection: "row",
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScreenMediaCache);
|
||||
|
||||
// <FlatList
|
||||
// data={imagesInDir}
|
||||
// style={{}}
|
||||
// keyExtractor={(item) => item.id}
|
||||
// renderItem={(object) => <Text>{object.item}</Text>}
|
||||
// />;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
export default function ScreenMessagingConversation({ navigation }) {
|
||||
return (
|
||||
<View>
|
||||
<Text></Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
export default function ScreenMessagingList() {
|
||||
return (
|
||||
<View>
|
||||
<Text>A list of conversations.</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";
|
||||
import DraggableFlatList, {
|
||||
ScaleDecorator,
|
||||
NestableScrollContainer,
|
||||
NestableDraggableFlatList,
|
||||
} from "react-native-draggable-flatlist";
|
||||
|
||||
const NUM_ITEMS = 10;
|
||||
function getColor(i) {
|
||||
const multiplier = 255 / (NUM_ITEMS - 1);
|
||||
const colorVal = i * multiplier;
|
||||
return `rgb(${colorVal}, ${Math.abs(128 - colorVal)}, ${255 - colorVal})`;
|
||||
}
|
||||
|
||||
const initialData = [...Array(NUM_ITEMS)].map((d, index) => {
|
||||
const backgroundColor = getColor(index);
|
||||
return {
|
||||
key: `item-${index}`,
|
||||
label: String(index) + "",
|
||||
height: 100,
|
||||
width: 60 + Math.random() * 40,
|
||||
backgroundColor,
|
||||
};
|
||||
});
|
||||
|
||||
const initialData2 = [...Array(NUM_ITEMS)].map((d, index) => {
|
||||
const backgroundColor = getColor(index);
|
||||
return {
|
||||
key: `item-${index}`,
|
||||
label: String(index) + "",
|
||||
height: 100,
|
||||
width: 60 + Math.random() * 40,
|
||||
backgroundColor,
|
||||
};
|
||||
});
|
||||
|
||||
export default function App() {
|
||||
const [data, setData] = useState(initialData);
|
||||
const [data2, setData2] = useState(initialData2);
|
||||
|
||||
const renderItem = ({ item, drag, isActive }) => {
|
||||
return (
|
||||
<ScaleDecorator>
|
||||
<TouchableOpacity
|
||||
onLongPress={drag}
|
||||
disabled={isActive}
|
||||
style={[
|
||||
styles.rowItem,
|
||||
{ backgroundColor: isActive ? "red" : item.backgroundColor },
|
||||
]}
|
||||
>
|
||||
<Text style={styles.text}>{item.label}</Text>
|
||||
</TouchableOpacity>
|
||||
</ScaleDecorator>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<NestableScrollContainer>
|
||||
<NestableDraggableFlatList
|
||||
data={data}
|
||||
onDragEnd={({ data }) => setData(data)}
|
||||
keyExtractor={(item) => item.key}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
<NestableDraggableFlatList
|
||||
data={data2}
|
||||
onDragEnd={({ data }) => setData2(data)}
|
||||
keyExtractor={(item) => item.key}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</NestableScrollContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
rowItem: {
|
||||
height: 100,
|
||||
width: 100,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
text: {
|
||||
color: "white",
|
||||
fontSize: 24,
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
import Constants from "expo-constants";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View, Text } from "react-native";
|
||||
import { Title, Button } from "react-native-paper";
|
||||
import { purgeStoredState } from "redux-persist";
|
||||
import SignOutButton from "../sign-out-button/sign-out-button.component";
|
||||
import * as Updates from "expo-updates";
|
||||
import * as Application from "expo-application";
|
||||
|
||||
export default function ScreenSettingsComponent() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Title>
|
||||
{t("settings.labels.version", {
|
||||
number: `${Constants.expoConfig.version}(${Application.nativeBuildVersion} - ${Constants.expoConfig.extra.expover})`,
|
||||
})}
|
||||
</Title>
|
||||
|
||||
<Text>Release Channel {Updates.channel}</Text>
|
||||
<SignOutButton />
|
||||
{/* <Button title="Purge State" onPress={() => purgeStoredState()} /> */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import { Formik } from "formik";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Image, StyleSheet, Text, View } from "react-native";
|
||||
import { Button, TextInput, Title } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import Logo from "../../assets/logo192.png";
|
||||
import { emailSignInStart } from "../../redux/user/user.actions";
|
||||
import {
|
||||
selectCurrentUser,
|
||||
selectSigningIn,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import SignInErrorAlertComponent from "../sign-in-error-alert/sign-in-error-alert.component";
|
||||
import Constants from "expo-constants";
|
||||
import * as Updates from "expo-updates";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
signingIn: selectSigningIn,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
emailSignInStart: (email, password) =>
|
||||
dispatch(emailSignInStart({ email, password })),
|
||||
});
|
||||
|
||||
export function SignIn({ emailSignInStart, signingIn }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSubmit = (values) => {
|
||||
const { email, password } = values;
|
||||
emailSignInStart(email, password);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={localStyles.content}>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
marginTop: 80,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-evenly",
|
||||
}}
|
||||
>
|
||||
<Image style={localStyles.logo} source={Logo} />
|
||||
<Title>{t("app.title")}</Title>
|
||||
</View>
|
||||
|
||||
<View style={{ flex: 1 }}>
|
||||
<Formik
|
||||
initialValues={{ email: "", password: "" }}
|
||||
onSubmit={formSubmit}
|
||||
>
|
||||
{({ handleChange, handleBlur, handleSubmit, values }) => (
|
||||
<View>
|
||||
<TextInput
|
||||
label={t("signin.fields.email")}
|
||||
mode="outlined"
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
onChangeText={handleChange("email")}
|
||||
onBlur={handleBlur("email")}
|
||||
value={values.email}
|
||||
style={[localStyles.input]}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={t("signin.fields.password")}
|
||||
mode="outlined"
|
||||
secureTextEntry={true}
|
||||
autoCorrect={false}
|
||||
autoCapitalize="none"
|
||||
onChangeText={handleChange("password")}
|
||||
onBlur={handleBlur("password")}
|
||||
value={values.password}
|
||||
style={[localStyles.input]}
|
||||
/>
|
||||
|
||||
<SignInErrorAlertComponent />
|
||||
<Button
|
||||
mode="outlined"
|
||||
loading={signingIn}
|
||||
onPress={handleSubmit}
|
||||
>
|
||||
<Text>{t("signin.actions.signin")}</Text>
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</Formik>
|
||||
</View>
|
||||
<Text style={{ padding: 10, alignSelf: "center" }}>
|
||||
{t("settings.labels.version", {
|
||||
number: Constants.expoConfig.version,
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
content: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
},
|
||||
logo: { width: 100, height: 100 },
|
||||
input: {
|
||||
margin: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActivityIndicator, Image, StyleSheet, View } from "react-native";
|
||||
import { Title, Subheading, Divider } from "react-native-paper";
|
||||
import Logo from "../../assets/logo192.png";
|
||||
import SignOutButton from "../sign-out-button/sign-out-button.component";
|
||||
|
||||
export default function ScreenSplash({ noAccess }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View style={[localStyles.container]}>
|
||||
<View style={[localStyles.logoContainer]}>
|
||||
<Image style={localStyles.logo} source={Logo} />
|
||||
<Title>{t("app.title")}</Title>
|
||||
</View>
|
||||
|
||||
{noAccess ? (
|
||||
<View style={[localStyles.logoContainer]}>
|
||||
<Subheading style={{ textAlign: "center" }}>
|
||||
{t("app.nomobileaccess")}
|
||||
</Subheading>
|
||||
<Divider />
|
||||
<SignOutButton />
|
||||
</View>
|
||||
) : (
|
||||
<ActivityIndicator color="dodgerblue" size="large" />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
const localStyles = StyleSheet.create({
|
||||
container: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
alignContent: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
logoContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
margin: 10,
|
||||
alignItems: "center",
|
||||
},
|
||||
logo: { width: 175, height: 175, margin: 20 },
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Title } from "react-native-paper";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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,
|
||||
});
|
||||
|
||||
export function SignInErrorAlertComponent({ signInError }) {
|
||||
const [errorText, setErrorText] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
let text;
|
||||
if (signInError && signInError.code)
|
||||
switch (signInError.code) {
|
||||
case "auth/user-not-found":
|
||||
text = t("signin.errors.usernotfound");
|
||||
break;
|
||||
case "auth/invalid-email":
|
||||
text = t("signin.errors.emailformat");
|
||||
break;
|
||||
case "auth/wrong-password":
|
||||
text = t("signin.errors.wrongpassword");
|
||||
break;
|
||||
default:
|
||||
text = signInError.code + " " + signInError.message;
|
||||
break;
|
||||
}
|
||||
|
||||
setErrorText(text);
|
||||
}, [signInError, setErrorText]);
|
||||
return (
|
||||
<View>
|
||||
{errorText ? <Title style={localStyles.alert}>{errorText}</Title> : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(SignInErrorAlertComponent);
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
alert: {
|
||||
color: "red",
|
||||
textAlign: "center",
|
||||
margin: 15,
|
||||
padding: 15,
|
||||
},
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function StyleRepeater({ childStyle, children }) {
|
||||
return (
|
||||
<>
|
||||
{React.Children.map(children, (child) =>
|
||||
React.cloneElement(child, {
|
||||
style: [child.props.style, childStyle],
|
||||
})
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
const cardBackgroundColor = "gainsboro";
|
||||
|
||||
export default StyleSheet.create({
|
||||
cardBackground: {
|
||||
padding: 5,
|
||||
backgroundColor: cardBackgroundColor,
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { Switch } from "react-native-paper";
|
||||
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({
|
||||
deleteAfterUpload: selectDeleteAfterUpload,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
|
||||
});
|
||||
|
||||
export function UploadDeleteSwitch({
|
||||
deleteAfterUpload,
|
||||
toggleDeleteAfterUpload,
|
||||
}) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>
|
||||
{t("mediabrowser.labels.deleteafterupload")}
|
||||
</Text>
|
||||
<Switch
|
||||
// trackColor={{ false: '#767577', true: '#81b0ff' }}
|
||||
// thumbColor={deleteAfterUpload ? 'tomato' : '#f4f3f4'}
|
||||
//ios_backgroundColor="#3e3e3e"
|
||||
onValueChange={() => {
|
||||
toggleDeleteAfterUpload();
|
||||
}}
|
||||
value={deleteAfterUpload}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
margin: 10,
|
||||
},
|
||||
|
||||
text: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UploadDeleteSwitch);
|
||||
@@ -1,218 +0,0 @@
|
||||
import * as MediaLibrary from "expo-media-library";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Modal,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
||||
import {
|
||||
selectCurrentCameraJobId,
|
||||
selectDeleteAfterUpload,
|
||||
} from "../../redux/app/app.selectors";
|
||||
import * as Sentry from "@sentry/react-native";
|
||||
|
||||
import { formatBytes } from "../../util/document-upload.utility";
|
||||
import { handleLocalUpload } from "../../util/local-document-upload.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedCameraJobId: selectCurrentCameraJobId,
|
||||
deleteAfterUpload: selectDeleteAfterUpload,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(UploadProgress);
|
||||
|
||||
export function UploadProgress({
|
||||
selectedCameraJobId,
|
||||
deleteAfterUpload,
|
||||
uploads,
|
||||
setUploads,
|
||||
forceRerender,
|
||||
}) {
|
||||
const [progress, setProgress] = useState({
|
||||
loading: false,
|
||||
uploadInProgress: false,
|
||||
speed: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
//Set the state of uploads to do.
|
||||
if (uploads) {
|
||||
beginUploads(uploads);
|
||||
setUploads(null);
|
||||
}
|
||||
}, [uploads]);
|
||||
|
||||
async function handleOnSuccess({ duration, data }) {
|
||||
//If it's not in production, show a toast with the time.
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: ` Upload completed in ${duration}.`,
|
||||
//
|
||||
// text2: duration,
|
||||
});
|
||||
if (deleteAfterUpload) {
|
||||
try {
|
||||
if (Platform.OS === "android") {
|
||||
//Create a new asset with the first file to delete.
|
||||
// console.log('Trying new delete.');
|
||||
await MediaLibrary.getPermissionsAsync(false);
|
||||
|
||||
const album = await MediaLibrary.createAlbumAsync(
|
||||
"ImEX Mobile Deleted",
|
||||
data.pop(),
|
||||
false
|
||||
);
|
||||
//Move the rest.
|
||||
if (data.length > 0) {
|
||||
const moveResult = await MediaLibrary.addAssetsToAlbumAsync(
|
||||
data,
|
||||
album,
|
||||
false
|
||||
);
|
||||
}
|
||||
const deleteResult = await MediaLibrary.deleteAlbumsAsync(album);
|
||||
|
||||
//Delete the album.
|
||||
|
||||
//This defaults to delete all assets in the album.
|
||||
} else {
|
||||
await MediaLibrary.deleteAssetsAsync(data.map((f) => f.id));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Unable to delete picture.", error);
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
}
|
||||
|
||||
logImEXEvent("imexmobile_successful_upload");
|
||||
forceRerender();
|
||||
setProgress({ ...progress, speed: 0, percent: 1, uploadInProgress: false });
|
||||
}
|
||||
|
||||
function handleOnProgress({ percent, loaded }) {
|
||||
setProgress((progress) => ({
|
||||
...progress,
|
||||
speed: loaded - progress.loaded,
|
||||
loaded: loaded,
|
||||
percent,
|
||||
}));
|
||||
}
|
||||
|
||||
function handleOnError({ assetid, error }) {
|
||||
logImEXEvent("imexmobile_upload_documents_error");
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Unable to upload documents.",
|
||||
text2: error,
|
||||
autoHide: false,
|
||||
});
|
||||
setProgress({
|
||||
speed: 0,
|
||||
percent: 1,
|
||||
uploadInProgress: false,
|
||||
});
|
||||
}
|
||||
|
||||
const beginUploads = async (data) => {
|
||||
//Validate to make sure the totals for the file sizes do not exceed the total on the job.
|
||||
setProgress({
|
||||
percent: 0,
|
||||
loaded: 0,
|
||||
uploadInProgress: true,
|
||||
start: new Date(),
|
||||
average: 0,
|
||||
});
|
||||
|
||||
await handleLocalUpload({
|
||||
files: data,
|
||||
onError: ({ assetid, error }) => handleOnError({ assetid, error }),
|
||||
onProgress: ({ percent, loaded }) =>
|
||||
handleOnProgress({ percent, loaded }),
|
||||
onSuccess: ({ duration }) => handleOnSuccess({ duration, data }),
|
||||
context: {
|
||||
jobid:
|
||||
selectedCameraJobId !== "temp" ? selectedCameraJobId : "temporary",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={progress.uploadInProgress}
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
onRequestClose={() => {
|
||||
Alert.alert("Cancel?", "Do you want to abort the upload?", [
|
||||
{
|
||||
text: "Yes",
|
||||
onPress: () => {
|
||||
setUploads(null);
|
||||
setProgress(null);
|
||||
},
|
||||
},
|
||||
{ text: "No" },
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<View style={styles.modalContainer}>
|
||||
<View style={styles.modal}>
|
||||
<ActivityIndicator style={{ alignSelf: "center", marginTop: 16 }} />
|
||||
<ProgressBar
|
||||
progress={progress.percent}
|
||||
style={{ alignSelf: "center", marginTop: 16 }}
|
||||
color={progress.percent === 1 ? "green" : "blue"}
|
||||
/>
|
||||
<Text style={{ alignSelf: "center", marginTop: 16 }}>{`${formatBytes(
|
||||
progress.speed
|
||||
)}/sec`}</Text>
|
||||
<Text
|
||||
style={{ alignSelf: "center", marginTop: 16 }}
|
||||
>{`Avg. ${formatBytes(
|
||||
progress.loaded / ((new Date() - progress.start) / 1000)
|
||||
)}/sec`}</Text>
|
||||
<Text
|
||||
style={{ alignSelf: "center", marginTop: 16 }}
|
||||
>{`Total Uploaded ${formatBytes(progress.loaded)}`}</Text>
|
||||
<Text style={{ alignSelf: "center", marginTop: 16 }}>{`Duration ${(
|
||||
(new Date() - progress.start) /
|
||||
1000
|
||||
).toFixed(1)} sec`}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
modalContainer: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
},
|
||||
modal: {
|
||||
// flex: 1,
|
||||
display: "flex",
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 20,
|
||||
padding: 18,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
},
|
||||
});
|
||||
@@ -1,426 +0,0 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { File } from "expo-file-system";
|
||||
import * as MediaLibrary from "expo-media-library";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Modal,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Divider, ProgressBar } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import * as Sentry from "@sentry/react-native";
|
||||
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 { formatBytes, handleUpload } from "../../util/document-upload.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop,
|
||||
selectedCameraJobId: selectCurrentCameraJobId,
|
||||
deleteAfterUpload: selectDeleteAfterUpload,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(UploadProgress);
|
||||
|
||||
export function UploadProgress({
|
||||
currentUser,
|
||||
bodyshop,
|
||||
selectedCameraJobId,
|
||||
deleteAfterUpload,
|
||||
uploads,
|
||||
setUploads,
|
||||
forceRerender,
|
||||
}) {
|
||||
const [progress, setProgress] = useState({
|
||||
uploadInProgress: false,
|
||||
totalToUpload: 0,
|
||||
totalUploaded: 0,
|
||||
startTime: null,
|
||||
totalFiles: 0,
|
||||
totalFilesCompleted: 0,
|
||||
currentFile: null,
|
||||
files: {}, //uri is the key, value is progress
|
||||
});
|
||||
|
||||
let filesToDelete = [];
|
||||
const client = useApolloClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (uploads) {
|
||||
onDone(uploads);
|
||||
setUploads(null);
|
||||
}
|
||||
}, [uploads]);
|
||||
|
||||
function handleOnSuccess(asset) {
|
||||
//NEEDS REDO.
|
||||
filesToDelete.push(asset);
|
||||
setProgress((progress) => ({
|
||||
...progress,
|
||||
// totalUploaded: progress.totalToUpload + asset.size,
|
||||
totalFilesCompleted: progress.totalFilesCompleted + 1,
|
||||
files: {
|
||||
...progress.files,
|
||||
[asset.uri]: {
|
||||
...progress.files[asset.uri],
|
||||
uploadEnd: new Date(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function handleOnProgress({ uri, filename }, percent, loaded) {
|
||||
//NEED REDO
|
||||
setProgress((progress) => {
|
||||
return {
|
||||
...progress,
|
||||
totalUploaded:
|
||||
progress.totalUploaded +
|
||||
(loaded - (progress.files[uri]?.loaded || 0)),
|
||||
files: {
|
||||
...progress.files,
|
||||
[uri]: {
|
||||
...progress.files[uri],
|
||||
percent,
|
||||
filename,
|
||||
speed: loaded - (progress.files[uri]?.loaded || 0),
|
||||
loaded: loaded,
|
||||
uploadStart: progress.files[uri]?.uploadStart || new Date(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
function handleOnError(error) {
|
||||
logImEXEvent("imexmobile_upload_documents_error", { error });
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Unable to upload document.",
|
||||
text2: error,
|
||||
autoHide: false,
|
||||
});
|
||||
}
|
||||
|
||||
const onDone = async (selectedFiles) => {
|
||||
setProgress((progress) => {
|
||||
return {
|
||||
...progress,
|
||||
uploadInProgress: true,
|
||||
statusText: "Preparing upload...",
|
||||
};
|
||||
});
|
||||
try {
|
||||
//Validate to make sure the totals for the file sizes do not exceed the total on the job.
|
||||
const data = [];
|
||||
const totalOfUploads = await selectedFiles.reduce(async (acc, val) => {
|
||||
//Get the size of the file based on URI.
|
||||
if (acc.fileSize) {
|
||||
return acc + acc.fileSize;
|
||||
} else {
|
||||
const info = new File(val.uri).size;
|
||||
data.push({ ...info, ...val }); //Add in the size.
|
||||
val.albumId && MediaLibrary.migrateAlbumIfNeededAsync(val.albumId);
|
||||
return (await acc) + info.size;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
if (selectedCameraJobId !== "temp") {
|
||||
const queryData = await client.query({
|
||||
query: GET_DOC_SIZE_TOTALS,
|
||||
fetchPolicy: "network-only",
|
||||
variables: {
|
||||
jobId: selectedCameraJobId,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
bodyshop.jobsizelimit -
|
||||
queryData.data.documents_aggregate.aggregate.sum.size <=
|
||||
totalOfUploads
|
||||
) {
|
||||
//No more room... abandon ship.
|
||||
setProgress((progress) => ({
|
||||
...progress,
|
||||
speed: 0,
|
||||
action: null,
|
||||
statusText: null,
|
||||
uploadInProgress: false,
|
||||
}));
|
||||
Alert.alert(
|
||||
t("mediabrowser.labels.storageexceeded_title"),
|
||||
t("mediabrowser.labels.storageexceeded")
|
||||
);
|
||||
return;
|
||||
}
|
||||
//We made it this far. We have enough space, so let's start uploading.
|
||||
setProgress((progress) => ({
|
||||
...progress,
|
||||
totalToUpload: totalOfUploads,
|
||||
totalUploaded: 0,
|
||||
totalFilesCompleted: 0,
|
||||
startTime: new Date(),
|
||||
totalFiles: data.length,
|
||||
currentFile: null,
|
||||
statusText: null,
|
||||
files: {}, //uri is the key, value is progress
|
||||
}));
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.length + 4; i = i + 4) {
|
||||
//Reset the files.
|
||||
setProgress((progress) => ({ ...progress, files: {} }));
|
||||
let proms = [];
|
||||
if (data[i]) {
|
||||
proms.push(CreateUploadProm(data[i]));
|
||||
}
|
||||
if (data[i + 1]) {
|
||||
proms.push(CreateUploadProm(data[i + 1]));
|
||||
}
|
||||
if (data[i + 2]) {
|
||||
proms.push(CreateUploadProm(data[i + 2]));
|
||||
}
|
||||
if (data[i + 3]) {
|
||||
proms.push(CreateUploadProm(data[i + 3]));
|
||||
}
|
||||
|
||||
await Promise.all(proms);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error during upload.", error, error.stack);
|
||||
Sentry.captureException(error);
|
||||
setProgress((progress) => ({
|
||||
...progress,
|
||||
speed: 0,
|
||||
action: null,
|
||||
statusText: null,
|
||||
uploadInProgress: false,
|
||||
}));
|
||||
Alert.alert(
|
||||
t("mediabrowser.labels.uploaderror_title"),
|
||||
t("mediabrowser.labels.uploaderror")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//Everything is uploaded, delete the succesful ones.
|
||||
if (deleteAfterUpload) {
|
||||
try {
|
||||
if (Platform.OS === "android") {
|
||||
//Create a new asset with the first file to delete.
|
||||
// console.log('Trying new delete.');
|
||||
await MediaLibrary.getPermissionsAsync(false);
|
||||
|
||||
const album = await MediaLibrary.createAlbumAsync(
|
||||
"ImEX Mobile Deleted",
|
||||
filesToDelete.pop(),
|
||||
false
|
||||
);
|
||||
//Move the rest.
|
||||
if (filesToDelete.length > 0) {
|
||||
const moveResult = await MediaLibrary.addAssetsToAlbumAsync(
|
||||
filesToDelete,
|
||||
album,
|
||||
false
|
||||
);
|
||||
}
|
||||
const deleteResult = await MediaLibrary.deleteAlbumsAsync(album);
|
||||
|
||||
//Delete the album.
|
||||
|
||||
//This defaults to delete all assets in the album.
|
||||
} else {
|
||||
await MediaLibrary.deleteAssetsAsync(filesToDelete.map((f) => f.id));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Unable to delete picture.", error);
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
}
|
||||
filesToDelete = [];
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: ` Upload completed.`,
|
||||
//
|
||||
// text2: duration,
|
||||
});
|
||||
//Reset state.
|
||||
|
||||
setProgress({
|
||||
uploadInProgress: false,
|
||||
totalToUpload: 0,
|
||||
totalUploaded: 0,
|
||||
totalFilesCompleted: 0,
|
||||
startTime: null,
|
||||
totalFiles: 0,
|
||||
currentFile: null,
|
||||
files: {},
|
||||
});
|
||||
|
||||
forceRerender();
|
||||
};
|
||||
|
||||
const CreateUploadProm = async (p) => {
|
||||
return handleUpload(
|
||||
{
|
||||
mediaId: p.id,
|
||||
onError: handleOnError,
|
||||
onProgress: ({ percent, loaded }) =>
|
||||
handleOnProgress(p, percent, loaded),
|
||||
onSuccess: () => handleOnSuccess(p),
|
||||
},
|
||||
{
|
||||
bodyshop: bodyshop,
|
||||
jobId: selectedCameraJobId !== "temp" ? selectedCameraJobId : null,
|
||||
uploaded_by: currentUser.email,
|
||||
photo: p,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={progress.uploadInProgress}
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
onRequestClose={() => {
|
||||
Alert.alert("Cancel?", "Do you want to abort the upload?", [
|
||||
{
|
||||
text: "Yes",
|
||||
onPress: () => {
|
||||
setUploads(null);
|
||||
setProgress({
|
||||
uploadInProgress: false,
|
||||
totalToUpload: 0,
|
||||
totalUploaded: 0,
|
||||
totalFilesCompleted: 0,
|
||||
startTime: null,
|
||||
totalFiles: 0,
|
||||
currentFile: null,
|
||||
files: {},
|
||||
});
|
||||
},
|
||||
},
|
||||
{ text: "No" },
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<View style={styles.modalContainer}>
|
||||
<View style={styles.modal}>
|
||||
{Object.keys(progress.files).map((key) => (
|
||||
<View key={key} style={styles.progressItem}>
|
||||
<Text style={styles.progressText}>
|
||||
{progress.files[key].filename}
|
||||
</Text>
|
||||
<View style={styles.progressBarContainer}>
|
||||
<ProgressBar
|
||||
progress={progress.files[key].percent}
|
||||
style={styles.progress}
|
||||
color={progress.files[key].percent === 1 ? "green" : "blue"}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text>{`${formatBytes(
|
||||
progress.files[key].loaded /
|
||||
(((progress.files[key].uploadEnd || new Date()) -
|
||||
progress.files[key].uploadStart) /
|
||||
1000)
|
||||
)}/sec`}</Text>
|
||||
{progress.files[key].percent === 1 && (
|
||||
<>
|
||||
<ActivityIndicator style={{ marginLeft: 12 }} />
|
||||
<Text style={{ marginLeft: 4 }}>Processing...</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
<View style={styles.centeredView}>
|
||||
{progress.statusText ? (
|
||||
<>
|
||||
<ActivityIndicator style={{ marginLeft: 12 }} />
|
||||
<Text style={{ marginLeft: 4 }}>{progress.statusText}</Text>
|
||||
<Divider />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text>{`${progress.totalFilesCompleted} of ${progress.totalFiles} uploaded.`}</Text>
|
||||
<Text>{`${formatBytes(progress.totalUploaded)} of ${formatBytes(
|
||||
progress.totalToUpload
|
||||
)} uploaded.`}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
modalContainer: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
},
|
||||
modal: {
|
||||
//flex: 1,
|
||||
display: "flex",
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 20,
|
||||
padding: 18,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
},
|
||||
centeredView: {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 22,
|
||||
},
|
||||
progressItem: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 12,
|
||||
marginLeft: 12,
|
||||
marginRight: 12,
|
||||
},
|
||||
progressText: {
|
||||
flex: 1,
|
||||
},
|
||||
progressBarContainer: {
|
||||
flex: 3,
|
||||
marginLeft: 12,
|
||||
marginRight: 12,
|
||||
},
|
||||
});
|
||||
@@ -47,8 +47,6 @@ export default function GlobalSearch() {
|
||||
if (!q) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
// TODO: Integrate real search endpoint
|
||||
console.log(`[GlobalSearch] (debounced placeholder) searching for: "${q}"`);
|
||||
try {
|
||||
const searchData = await axios.post(`${env.API_URL}/search`, {
|
||||
search: q,
|
||||
|
||||
@@ -105,11 +105,6 @@ export function JobDocumentsComponent({ bodyshop }) {
|
||||
setFullPhotos(normalizedImages);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Error fetching photos:",
|
||||
error.message,
|
||||
JSON.stringify(error, null, 2)
|
||||
);
|
||||
setError(error.message || "Unknown error fetching photos.");
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
@@ -144,7 +144,11 @@ function Tab({ bodyshop, currentUser, signOutStart }) {
|
||||
"Error",
|
||||
`Unable to register for notifications: ${error.message}`
|
||||
);
|
||||
console.log("Notification registration error:", error);
|
||||
console.log(
|
||||
"Notification registration error:",
|
||||
error,
|
||||
error.stack
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -161,7 +165,9 @@ function Tab({ bodyshop, currentUser, signOutStart }) {
|
||||
}`}
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
{`${t("settings.labels.signedinuser")} ${currentUser?.email || "Unknown"}`}
|
||||
{`${t("settings.labels.signedinuser")} ${
|
||||
currentUser?.email || "Unknown"
|
||||
}`}
|
||||
</Text>
|
||||
</Card.Content>
|
||||
<Card.Actions>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
|
||||
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
|
||||
*/
|
||||
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
const tintColorLight = '#0a7ea4';
|
||||
const tintColorDark = '#fff';
|
||||
|
||||
export const Colors = {
|
||||
light: {
|
||||
text: '#11181C',
|
||||
background: '#fff',
|
||||
tint: tintColorLight,
|
||||
icon: '#687076',
|
||||
tabIconDefault: '#687076',
|
||||
tabIconSelected: tintColorLight,
|
||||
},
|
||||
dark: {
|
||||
text: '#ECEDEE',
|
||||
background: '#151718',
|
||||
tint: tintColorDark,
|
||||
icon: '#9BA1A6',
|
||||
tabIconDefault: '#9BA1A6',
|
||||
tabIconSelected: tintColorDark,
|
||||
},
|
||||
};
|
||||
|
||||
export const Fonts = Platform.select({
|
||||
ios: {
|
||||
/** iOS `UIFontDescriptorSystemDesignDefault` */
|
||||
sans: 'system-ui',
|
||||
/** iOS `UIFontDescriptorSystemDesignSerif` */
|
||||
serif: 'ui-serif',
|
||||
/** iOS `UIFontDescriptorSystemDesignRounded` */
|
||||
rounded: 'ui-rounded',
|
||||
/** iOS `UIFontDescriptorSystemDesignMonospaced` */
|
||||
mono: 'ui-monospace',
|
||||
},
|
||||
default: {
|
||||
sans: 'normal',
|
||||
serif: 'serif',
|
||||
rounded: 'normal',
|
||||
mono: 'monospace',
|
||||
},
|
||||
web: {
|
||||
sans: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
||||
serif: "Georgia, 'Times New Roman', serif",
|
||||
rounded: "'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif",
|
||||
mono: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
||||
},
|
||||
});
|
||||
2
env.js
2
env.js
@@ -64,11 +64,9 @@ const ENV = {
|
||||
}
|
||||
};
|
||||
const IS_ROME = Constants?.expoConfig?.extra?.appVariant === 'ROME';
|
||||
//console.log("*** ~ Constants?.expoConfig?.extra:", Constants?.expoConfig);
|
||||
|
||||
function getEnvVars() {
|
||||
if (Updates.channel !== "production") return IS_ROME ? ENV.rometest : ENV.test;
|
||||
else return IS_ROME ? ENV.romeprod : ENV.prod;
|
||||
}
|
||||
console.log('APP_VARIANT:', Constants?.expoConfig?.extra?.appVariant, 'IS_ROME:', IS_ROME, "ENV: ", getEnvVars());
|
||||
export default getEnvVars();
|
||||
|
||||
@@ -25,34 +25,14 @@ const errorLink = onError(
|
||||
}
|
||||
);
|
||||
|
||||
const subscriptionMiddleware = {
|
||||
applyMiddleware: async (options, next) => {
|
||||
options.authToken =
|
||||
auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
next();
|
||||
},
|
||||
};
|
||||
//wsLink.subscriptionClient.use([subscriptionMiddleware]);
|
||||
|
||||
// const link = split(
|
||||
// // split based on operation type
|
||||
// ({ query }) => {
|
||||
// const definition = getMainDefinition(query);
|
||||
// // console.log(
|
||||
// // "##Intercepted GQL Transaction : " +
|
||||
// // definition.operation +
|
||||
// // "|" +
|
||||
// // // definition.name.value +
|
||||
// // "##"
|
||||
// // );
|
||||
// return (
|
||||
// definition.kind === "OperationDefinition" &&
|
||||
// definition.operation === "subscription"
|
||||
// );
|
||||
// const subscriptionMiddleware = {
|
||||
// applyMiddleware: async (options, next) => {
|
||||
// options.authToken =
|
||||
// auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
// next();
|
||||
// },
|
||||
// wsLink,
|
||||
// httpLink
|
||||
// );
|
||||
// };
|
||||
|
||||
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
return (
|
||||
@@ -84,7 +64,7 @@ const retryLink = new RetryLink({
|
||||
},
|
||||
});
|
||||
|
||||
const cache = new InMemoryCache({});
|
||||
const cache = new InMemoryCache();
|
||||
|
||||
export const client = new ApolloClient({
|
||||
//link: ApolloLink.from(middlewares),
|
||||
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "imexmobile",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.12.11",
|
||||
"@apollo/client": "^3.14.0",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
||||
@@ -57,6 +57,7 @@
|
||||
"react-native-image-viewing": "^0.2.2",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "~4.1.3",
|
||||
"react-native-reanimated-dnd": "^1.1.0",
|
||||
"react-native-safe-area-context": "~5.6.1",
|
||||
"react-native-screens": "~4.17.1",
|
||||
"react-native-tab-view": "4.1.3",
|
||||
@@ -2792,9 +2793,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@expo/vector-icons": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.2.tgz",
|
||||
"integrity": "sha512-IiBjg7ZikueuHNf40wSGCf0zS73a3guJLdZzKnDUxsauB8VWPLMeWnRIupc+7cFhLUkqyvyo0jLNlcxG5xPOuQ==",
|
||||
"version": "15.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz",
|
||||
"integrity": "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo-font": ">=14.0.4",
|
||||
@@ -13285,6 +13286,18 @@
|
||||
"react-native-worklets": ">=0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated-dnd": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated-dnd/-/react-native-reanimated-dnd-1.1.0.tgz",
|
||||
"integrity": "sha512-9ZgdAFsw2rjB/0VE3wsR9+PnBOWIoO+1s6lEiyV51ptBcoz0NaUnUb8aqGocEztnXSbySSznpOh5vy6Ijo9A3Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-native": ">=0.60.0",
|
||||
"react-native-gesture-handler": ">=2.0.0",
|
||||
"react-native-reanimated": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"lint": "expo lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.12.11",
|
||||
"@apollo/client": "^3.14.0",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
||||
@@ -72,6 +72,7 @@
|
||||
"react-native-image-viewing": "^0.2.2",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "~4.1.3",
|
||||
"react-native-reanimated-dnd": "^1.1.0",
|
||||
"react-native-safe-area-context": "~5.6.1",
|
||||
"react-native-screens": "~4.17.1",
|
||||
"react-native-tab-view": "4.1.3",
|
||||
|
||||
@@ -83,7 +83,7 @@ export function* openImagePickerAction({ payload: jobid }) {
|
||||
yield put(mediaUploadStart({ photos: result.assets, jobid }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Saga Error: open Picker", error);
|
||||
// console.log("Saga Error: open Picker", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +93,6 @@ export function* onMediaUploadStart() {
|
||||
|
||||
export function* mediaUploadStartAction({ payload: { photos, jobid } }) {
|
||||
try {
|
||||
console.log("Starting upload for", photos.length, "photos");
|
||||
|
||||
const bodyshop = yield select(selectBodyshop);
|
||||
|
||||
if (bodyshop.uselocalmediaserver) {
|
||||
@@ -130,7 +128,7 @@ export function* mediaUploadStartAction({ payload: { photos, jobid } }) {
|
||||
yield delay(100);
|
||||
}
|
||||
}
|
||||
console.log("All uploads completed. This shouldn't fire before the uploads are done.");
|
||||
|
||||
yield put(mediaUploadCompleted(photos));
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user