diff --git a/.gitignore b/.gitignore index 7afbcce..fca016c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ yarn-error.log # macOS .DS_Store + + +*.ipa +*.aab \ No newline at end of file diff --git a/App.js b/App.js index c08772f..76bde8a 100644 --- a/App.js +++ b/App.js @@ -8,12 +8,23 @@ import ScreenMainComponent from "./components/screen-main/screen-main.component" import { logImEXEvent } from "./firebase/firebase.analytics"; import { client } from "./graphql/client"; import { persistor, store } from "./redux/store"; +import "intl"; +import "intl/locale-data/jsonp/en"; import "./translations/i18n"; - +import "expo-asset"; +import Toast from "react-native-toast-message"; +import { SafeAreaProvider } from "react-native-safe-area-context"; Sentry.init({ dsn: "https://8d6c3de1940a4e4f8b81cf4d2150bdea@o492140.ingest.sentry.io/5558869", enableInExpoDevelopment: true, - // debug: true, // Sentry will try to print out useful debugging information if something goes wrong with sending an event. Set this to `false` in production. + tracesSampleRate: 0.2, + integrations: [ + new Sentry.Native.ReactNativeTracing({ + tracingOrigins: ["localhost", "imex.online", "cloudinary.com", /^\//], + // ... other options + }), + ], + debug: true, // Sentry will try to print out useful debugging information if something goes wrong with sending an event. Set this to `false` in production. }); Sentry.Native.nativeCrash(); @@ -22,7 +33,7 @@ const theme = { ...DefaultTheme, colors: { ...DefaultTheme.colors, - primary: "dodgerblue", + primary: "#1890ff", accent: "tomato", }, }; @@ -34,15 +45,18 @@ export default class App extends React.Component { render() { return ( - - - - - - - - - + + + + + + + + + + + + ); } } diff --git a/app.json b/app.json index 4c48a38..2fe0462 100644 --- a/app.json +++ b/app.json @@ -2,24 +2,25 @@ "expo": { "name": "ImEX Mobile", "slug": "imexmobile", - "version": "1.2.3", - "extra": { "expover": "1" }, + "version": "1.3.7", + "extra": { + "expover": "6" + }, "orientation": "default", "icon": "./assets/logo192noa.png", "ios": { "supportsTablet": true, "bundleIdentifier": "com.imex.imexmobile", - "buildNumber": "1.2.3", + "buildNumber": "6", "googleServicesFile": "./GoogleService-Info.plist" }, "android": { "package": "com.imex.imexmobile", - "versionCode": 1020300, + "versionCode": 1100019, "googleServicesFile": "./google-services.json" }, "splash": { "image": "./assets/Splash.png", - "backgroundColor": "#efefef" }, "notification": { @@ -29,7 +30,6 @@ "fallbackToCacheTimeout": 0 }, "assetBundlePatterns": ["**/*"], - "web": { "favicon": "./assets/logo192noa.png", "config": { @@ -57,6 +57,17 @@ } } ] - } + }, + "plugins": [ + "sentry-expo", + [ + "expo-media-library", + { + "photosPermission": "Allow $(PRODUCT_NAME) to access your photos.", + "savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.", + "isAccessMediaLocationEnabled": "true" + } + ] + ] } } diff --git a/babel-translations.babel b/babel-translations.babel index 6bdcb8f..ec488f3 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -24,6 +24,27 @@ app + + nomobileaccess + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + title false @@ -1216,6 +1237,27 @@ + + search + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -1341,6 +1383,27 @@ + + localserver + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + nomedia false diff --git a/components/job-documents/job-documents.component.jsx b/components/job-documents/job-documents.component.jsx index e5946c7..ef692b5 100644 --- a/components/job-documents/job-documents.component.jsx +++ b/components/job-documents/job-documents.component.jsx @@ -20,14 +20,24 @@ export default function JobDocumentsComponent({ job, loading, refetch }) { const fullphotos = useMemo( () => - job.documents.map((doc) => { + 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), }; }), [job.documents] diff --git a/components/job-list/job-list.component.jsx b/components/job-list/job-list.component.jsx index bac93dd..56db494 100644 --- a/components/job-list/job-list.component.jsx +++ b/components/job-list/job-list.component.jsx @@ -1,7 +1,8 @@ import { useQuery } from "@apollo/client"; import React from "react"; -import { RefreshControl, View, Text } from "react-native"; -import { FlatList } from "react-native-gesture-handler"; +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"; @@ -9,8 +10,7 @@ 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"; -import { Title, Button, Searchbar } from "react-native-paper"; -import { useTranslation } from "react-i18next"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -23,6 +23,7 @@ export function JobListComponent({ bodyshop }) { statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], }, skip: !bodyshop, + notifyOnNetworkStatusChange: true, }); const onRefresh = async () => { @@ -32,7 +33,6 @@ export function JobListComponent({ bodyshop }) { if (loading) return ; if (error) return ; - if (data && data.jobs && data.jobs.length === 0) return ( @@ -74,7 +74,11 @@ export function JobListComponent({ bodyshop }) { return ( - + diff --git a/components/local-upload-progress/local-upload-progress.component.jsx b/components/local-upload-progress/local-upload-progress.component.jsx new file mode 100644 index 0000000..4729cd6 --- /dev/null +++ b/components/local-upload-progress/local-upload-progress.component.jsx @@ -0,0 +1,192 @@ +import * as MediaLibrary from "expo-media-library"; +import React, { useEffect, useState } from "react"; + +import { + ActivityIndicator, + Alert, + Modal, + 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-expo"; + +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 { + await MediaLibrary.deleteAssetsAsync(data); + } catch (error) { + console.log("Unable to delete picture.", error); + Sentry.Native.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 ( + { + Alert.alert("Cancel?", "Do you want to abort the upload?", [ + { + text: "Yes", + onPress: () => { + setUploads(null); + setProgress(null); + }, + }, + { text: "No" }, + ]); + }} + > + + + + + {`${formatBytes( + progress.speed + )}/sec`} + {`Avg. ${formatBytes( + progress.loaded / ((new Date() - progress.start) / 1000) + )}/sec`} + {`Total Uploaded ${formatBytes(progress.loaded)}`} + {`Duration ${( + (new Date() - progress.start) / + 1000 + ).toFixed(1)} sec`} + + + + ); +} +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, + }, +}); diff --git a/components/media-cache-overlay/media-cache-overlay.component.jsx b/components/media-cache-overlay/media-cache-overlay.component.jsx index 3d113fb..3489dc5 100644 --- a/components/media-cache-overlay/media-cache-overlay.component.jsx +++ b/components/media-cache-overlay/media-cache-overlay.component.jsx @@ -1,13 +1,7 @@ -import { Ionicons } from "@expo/vector-icons"; -import { Video } from "expo-av"; -import React, { useState } from "react"; -import { - Dimensions, - Modal, - SafeAreaView, - TouchableOpacity, -} from "react-native"; -import Gallery from "react-native-image-gallery"; +import { SafeAreaView } from "react-native"; +import React from "react"; + +import ImageView from "react-native-image-viewing"; export default function MediaCacheOverlay({ photos, @@ -16,61 +10,73 @@ export default function MediaCacheOverlay({ imgIndex, setImgIndex, }) { - const [currentIndex, setcurrentIndex] = useState(0); - const [dragging, setDragging] = useState(false); - - const videoRef = React.useRef(null); + //const videoRef = React.useRef(null); return ( - setPreviewVisible(false)} - onRequestClose={() => setPreviewVisible(false)} - visible={previewVisible} - transparent={false} - > - - setcurrentIndex(position)} - onPageScrollStateChanged={(state) => - state === "idle" ? setDragging(false) : setDragging(true) - } - /> - setPreviewVisible(false)} - > - - - {!dragging && photos[currentIndex] && photos[currentIndex].videoUrl && ( - { - await videoRef.current.loadAsync( - { uri: photos[currentIndex].videoUrl }, - {}, - false - ); - videoRef.current.presentFullscreenPlayer(); - }} - > - - - )} - - + + setPreviewVisible(false)} + visible={previewVisible} + images={photos} + imageIndex={imgIndex} + onImageIndexChange={(...props) => { + console.log(props); + }} + /> + ); + + // return ( + // setPreviewVisible(false)} + // onRequestClose={() => setPreviewVisible(false)} + // visible={previewVisible} + // transparent={false} + // > + // + // setcurrentIndex(position)} + // onPageScrollStateChanged={(state) => + // state === "idle" ? setDragging(false) : setDragging(true) + // } + // /> + // setPreviewVisible(false)} + // > + // + // + // {!dragging && photos[currentIndex] && photos[currentIndex].videoUrl && ( + // { + // await videoRef.current.loadAsync( + // { uri: photos[currentIndex].videoUrl }, + // {}, + // false + // ); + // videoRef.current.presentFullscreenPlayer(); + // }} + // > + // + // + // )} + // + // + // ); } diff --git a/components/screen-job-detail/screen-job-detail.component.jsx b/components/screen-job-detail/screen-job-detail.component.jsx index 04fdc0c..47a1f80 100644 --- a/components/screen-job-detail/screen-job-detail.component.jsx +++ b/components/screen-job-detail/screen-job-detail.component.jsx @@ -10,8 +10,18 @@ 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"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect(mapStateToProps, mapDispatchToProps)(ScreenJobDetail); -export default function ScreenJobDetail({ route }) { +export function ScreenJobDetail({ bodyshop, route }) { const { params: { jobId }, } = route; @@ -45,12 +55,16 @@ export default function ScreenJobDetail({ route }) { loading: loading, refetch: refetch, }), - documents: () => - JobDocuments({ - job: data.jobs_by_pk, - loading: loading, - refetch: refetch, - }), + ...(bodyshop.uselocalmediaserver + ? {} + : { + documents: () => + JobDocuments({ + job: data.jobs_by_pk, + loading: loading, + refetch: refetch, + }), + }), notes: () => JobNotes({ job: data.jobs_by_pk, @@ -63,7 +77,9 @@ export default function ScreenJobDetail({ route }) { 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") }, + ...(bodyshop.uselocalmediaserver + ? [] + : [{ key: "documents", title: t("jobdetail.labels.documents") }]), { key: "notes", title: t("jobdetail.labels.notes") }, ]); diff --git a/components/screen-main/screen-main.component.jsx b/components/screen-main/screen-main.component.jsx index 4728390..605a1fd 100644 --- a/components/screen-main/screen-main.component.jsx +++ b/components/screen-main/screen-main.component.jsx @@ -3,9 +3,9 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import i18n from "i18next"; +import moment from "moment"; import React, { useEffect } from "react"; import { Button } from "react-native-paper"; -import { SafeAreaView } from "react-native-safe-area-context"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.analytics"; @@ -107,6 +107,15 @@ const MoreStackNavigator = () => ( const BottomTabsNavigator = () => ( ({ + // tabBarActiveTintColor: "dodgerblue", + // tabBarInactiveTintColor: "slategrey", + // tabBarStyle: [ + // { + // display: "flex", + // }, + // null, + // ], + // eslint-disable-next-line react/display-name tabBarIcon: ({ color, size }) => { let iconName; @@ -123,24 +132,27 @@ const BottomTabsNavigator = () => ( return ; }, })} - tabBarOptions={{ - activeTintColor: "dodgerblue", - inactiveTintColor: "slategrey", - }} > @@ -156,24 +168,33 @@ export function ScreenMainComponent({ }, [checkUserSession]); return ( - - - {currentUser.authorized === null ? ( - - ) : currentUser.authorized ? ( - bodyshop ? ( + + {currentUser.authorized === null ? ( + + ) : currentUser.authorized ? ( + bodyshop ? ( + HasAccess(bodyshop) ? ( ) : ( - + ) ) : ( - - )} - - + + ) + ) : ( + + )} + ); } export default connect( mapStateToProps, mapDispatchToProps )(ScreenMainComponent); + +function HasAccess({ features }) { + if (features.mobile === undefined) return true; + if (features.mobile === false) return false; + const d = moment(moment(features.mobile)); + if (d.isValid()) return d.isAfter(moment()); +} diff --git a/components/screen-media-browser/screen-media-browser.component.jsx b/components/screen-media-browser/screen-media-browser.component.jsx index fd12739..6810a35 100644 --- a/components/screen-media-browser/screen-media-browser.component.jsx +++ b/components/screen-media-browser/screen-media-browser.component.jsx @@ -1,6 +1,6 @@ import { Ionicons } from "@expo/vector-icons"; import { AssetsSelector } from "expo-images-picker"; -import React, { useCallback, useState } from "react"; +import React, { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { StyleSheet, Text, View } from "react-native"; import { connect } from "react-redux"; @@ -11,12 +11,16 @@ import CameraSelectJob from "../camera-select-job/camera-select-job.component"; import JobSpaceAvailable from "../job-space-available/job-space-available.component"; import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component"; import UploadProgress from "../upload-progress/upload-progress.component"; +import { MediaType } from "expo-media-library"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import LocalUploadProgress from "../local-upload-progress/local-upload-progress.component"; const mapStateToProps = createStructuredSelector({ selectedCameraJobId: selectCurrentCameraJobId, + bodyshop: selectBodyshop, }); -export function ImageBrowserScreen({ selectedCameraJobId }) { +export function ImageBrowserScreen({ bodyshop, selectedCameraJobId }) { const { t } = useTranslation(); const [uploads, setUploads] = useState(null); const [tick, setTick] = useState(0); @@ -29,10 +33,105 @@ export function ImageBrowserScreen({ selectedCameraJobId }) { if (data.length !== 0) setUploads(data); }; + const widgetErrors = useMemo( + () => ({ + errorTextColor: "black", + errorMessages: { + hasErrorWithPermissions: "Please Allow media gallery permissions.", + hasErrorWithLoading: "There was an error while loading images.", + hasErrorWithResizing: "There was an error while loading images.", + hasNoAssets: "No images found.", + }, + }), + [] + ); + + const widgetSettings = useMemo( + () => ({ + getImageMetaData: false, // true might perform slower results but gives meta data and absolute path for ios users + initialLoad: 100, + assetsType: [MediaType.photo, MediaType.video], + minSelection: 1, + // maxSelection: 3, + portraitCols: 4, + landscapeCols: 4, + }), + [] + ); + + const widgetResize = useMemo( + () => ({ + width: 50, + compress: 0.7, + base64: false, + saveTo: "jpeg", + }), + [] + ); + + const _textStyle = { + color: "white", + }; + + const _buttonStyle = { + backgroundColor: "orange", + borderRadius: 5, + }; + + const widgetNavigator = useMemo( + () => ({ + Texts: { + finish: t("mediabrowser.actions.upload"), + back: t("mediabrowser.actions.refresh"), + selected: "selected", + }, + midTextColor: "black", + minSelection: 1, + buttonTextStyle: styles.textStyle, + buttonStyle: styles.buttonStyle, + onBack: () => { + forceRerender(); + }, + onSuccess: onDone, + }), + [] + ); + + const widgetStyles = useMemo( + () => ({ + margin: 2, + bgColor: "white", + spinnerColor: "blue", + widgetWidth: 99, + videoIcon: { + Component: Ionicons, + iconName: "ios-videocam", + color: "white", + size: 20, + }, + selectedIcon: { + Component: Ionicons, + iconName: "ios-checkmark-circle-outline", + color: "white", + bg: "rgba(35,35,35, 0.75)", + size: 32, + }, + }), + [] + ); + return ( - + {bodyshop.uselocalmediaserver ? ( + + {t("mediabrowser.labels.localserver", { + url: bodyshop.localmediaserverhttp, + })} + + ) : ( + + )} {!selectedCameraJobId && ( { - forceRerender(); - }, - doneFunction: onDone, - }, - - noAssets: { - Component: function NoAsset() { - return ( - - - - {t("mediabrowser.labels.nomedia")} - - - ); - }, - }, - }} + Settings={widgetSettings} + Errors={widgetErrors} + Styles={widgetStyles} + Navigator={widgetNavigator} + /> + )} + {bodyshop.uselocalmediaserver ? ( + + ) : ( + )} - ); } @@ -130,3 +189,60 @@ const styles = StyleSheet.create({ }); export default connect(mapStateToProps, null)(ImageBrowserScreen); + +// options={{ +// assetsType: ["photo", "video"], +// margin: 3, +// portraitCols: 4, +// landscapeCols: 6, +// widgetWidth: 100, +// widgetBgColor: "white", +// selectedBgColor: "#adadad", +// spinnerColor: "#c8c8c8", +// videoIcon: { +// Component: Ionicons, +// iconName: "ios-videocam", +// color: "white", +// size: 20, +// }, +// selectedIcon: { +// Component: Ionicons, +// iconName: "ios-checkmark-circle-outline", +// color: "white", +// bg: "rgba(35,35,35, 0.75)", +// size: 32, +// }, +// defaultTopNavigator: { +// continueText: t("mediabrowser.actions.upload"), +// goBackText: t("mediabrowser.actions.refresh"), +// buttonStyle: styles.buttonStyle, +// textStyle: styles.textStyle, +// backFunction: () => { +// forceRerender(); +// }, +// doneFunction: onDone, +// }, + +// noAssets: { +// Component: function NoAsset() { +// return ( +// +// +// +// {t("mediabrowser.labels.nomedia")} +// +// +// ); +// }, +// }, +// }} diff --git a/components/screen-settings/screen-settings.component.jsx b/components/screen-settings/screen-settings.component.jsx index 32ce074..8696345 100644 --- a/components/screen-settings/screen-settings.component.jsx +++ b/components/screen-settings/screen-settings.component.jsx @@ -1,8 +1,8 @@ import Constants from "expo-constants"; import React from "react"; import { useTranslation } from "react-i18next"; -import { Button, View, Text } from "react-native"; -import { Title } from "react-native-paper"; +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"; @@ -25,9 +25,9 @@ export default function ScreenSettingsComponent() { })} - {Updates.releaseChannel} + Release Channel {Updates.releaseChannel} - - - {t("settings.labels.version", { - number: Constants.manifest.version, - })} - {`${process.env.NODE_ENV || ""} ${Updates.releaseChannel || ""}`} - - - )} - + + + + + + )} + + + + {t("settings.labels.version", { + number: Constants.manifest.version, + })} + ); } diff --git a/components/screen-splash/screen-splash.component.jsx b/components/screen-splash/screen-splash.component.jsx index 78fcb7c..0f138d0 100644 --- a/components/screen-splash/screen-splash.component.jsx +++ b/components/screen-splash/screen-splash.component.jsx @@ -1,10 +1,11 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, Image, StyleSheet, View } from "react-native"; -import { Title } from "react-native-paper"; +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() { +export default function ScreenSplash({ noAccess }) { const { t } = useTranslation(); return ( @@ -13,7 +14,17 @@ export default function ScreenSplash() { {t("app.title")} - + {noAccess ? ( + + + {t("app.nomobileaccess")} + + + + + ) : ( + + )} ); } @@ -28,7 +39,7 @@ const localStyles = StyleSheet.create({ logoContainer: { display: "flex", flexDirection: "column", - + margin: 10, alignItems: "center", }, logo: { width: 175, height: 175, margin: 20 }, diff --git a/components/sign-out-button/sign-out-button.component.jsx b/components/sign-out-button/sign-out-button.component.jsx index 86a23f2..0baa8b8 100644 --- a/components/sign-out-button/sign-out-button.component.jsx +++ b/components/sign-out-button/sign-out-button.component.jsx @@ -16,6 +16,7 @@ export function SignOutButton({ signOutStart }) { const { t } = useTranslation(); return (