2020-09-02 21:09:49 +00:00
|
|
|
// @refresh reset
|
|
|
|
import AsyncStorage from '@react-native-community/async-storage';
|
2020-09-11 21:23:26 +00:00
|
|
|
import NetInfo, {NetInfoState, NetInfoStateType} from '@react-native-community/netinfo';
|
2020-09-11 21:38:53 +00:00
|
|
|
import {format} from 'date-fns';
|
2020-08-29 03:40:55 +00:00
|
|
|
import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
|
2020-09-02 21:09:49 +00:00
|
|
|
import * as firebase from 'firebase';
|
|
|
|
import 'firebase/firestore';
|
2020-09-13 12:08:27 +00:00
|
|
|
import React, {ReactElement, useEffect, useState} from 'react';
|
2020-09-06 03:16:46 +00:00
|
|
|
import {AppRegistry, SafeAreaView, View, YellowBox} from 'react-native';
|
2020-09-11 21:38:53 +00:00
|
|
|
import {Appbar, Provider as PaperProvider, Snackbar,} from 'react-native-paper';
|
2020-09-01 02:12:32 +00:00
|
|
|
import {expo as appExpo} from './app.json';
|
2020-08-29 03:40:55 +00:00
|
|
|
import {CancelButton} from './components/Common';
|
2020-09-06 03:16:46 +00:00
|
|
|
import {InputLineCountButton, InputLineCountScreen} from './components/LineCount';
|
2020-08-29 03:40:55 +00:00
|
|
|
import {BarCodeDisplay, PrintButton, PrintingMessage} from './components/Print';
|
2020-09-13 12:08:27 +00:00
|
|
|
import {IdNumberInput, InitialsInput, InputIdButton, ScanButton, Scanner} from './components/Scan';
|
2020-09-06 03:16:46 +00:00
|
|
|
import {SettingsScreen} from './components/Settings';
|
2020-09-11 21:38:53 +00:00
|
|
|
import {styles, theme} from './components/Styles';
|
2020-09-11 21:23:26 +00:00
|
|
|
import {sendDataToFirebase, SyncMessage} from './components/Sync';
|
2020-09-14 17:41:22 +00:00
|
|
|
import {firebaseConfig, defaults} from './config/default';
|
2020-08-29 03:40:55 +00:00
|
|
|
import {BarcodeScannerAppState} from './models/BarcodeScannerAppState';
|
2020-09-04 21:50:50 +00:00
|
|
|
import {CameraType, ElementProps, StateProps} from './models/ElementProps';
|
2020-09-06 03:16:46 +00:00
|
|
|
import {LineCount} from './models/LineCount';
|
2020-09-02 21:09:49 +00:00
|
|
|
import {Sample} from './models/Sample';
|
|
|
|
|
|
|
|
YellowBox.ignoreWarnings([
|
|
|
|
'Setting a timer for a long period of time', // Ignore Firebase timer warnings
|
2020-09-11 21:23:26 +00:00
|
|
|
'Remote debugger is in a background tab', // Ignore remote debugger warnings
|
2020-09-02 21:09:49 +00:00
|
|
|
]);
|
|
|
|
|
2020-09-11 21:38:53 +00:00
|
|
|
// Initialize Firebase if not already initialized.
|
|
|
|
if (firebase.apps.length === 0) {
|
|
|
|
firebase.initializeApp(firebaseConfig);
|
|
|
|
}
|
|
|
|
|
2020-09-02 21:09:49 +00:00
|
|
|
const db = firebase.firestore();
|
2020-09-14 17:41:22 +00:00
|
|
|
const samplesCollection = db.collection(defaults.samplesCollection);
|
|
|
|
const countsCollection = db.collection(defaults.countsCollection);
|
2020-08-29 03:40:55 +00:00
|
|
|
|
2020-09-01 19:55:23 +00:00
|
|
|
export default function Main() {
|
2020-08-29 03:40:55 +00:00
|
|
|
const [appState, setAppState] = useState<BarcodeScannerAppState>(BarcodeScannerAppState.INITIAL);
|
2020-09-02 21:09:49 +00:00
|
|
|
const [sampleId, setSampleId] = useState<string>('');
|
|
|
|
const [barCodeId, setBarCodeId] = useState<string>('');
|
|
|
|
const [sampleDate, setSampleDate] = useState<Date>(new Date());
|
2020-09-14 17:41:22 +00:00
|
|
|
const [locationStr, setLocationStr] = useState<string>(defaults.locationId);
|
2020-09-02 16:13:16 +00:00
|
|
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
2020-09-02 21:09:49 +00:00
|
|
|
const [samples, setSamples] = useState<Sample[]>([]);
|
2020-09-06 03:16:46 +00:00
|
|
|
const [lineCounts, setLineCounts] = useState<LineCount[]>([]);
|
2020-09-14 17:41:22 +00:00
|
|
|
const [cameraType, setCameraType] = useState<CameraType>(defaults.cameraType);
|
|
|
|
const [numCopies, setNumCopies] = useState<number>(defaults.numCopies);
|
2020-09-11 21:23:26 +00:00
|
|
|
const [isConnected, setIsConnected] = useState<boolean>(false);
|
2020-09-13 12:08:27 +00:00
|
|
|
const [initials, setInitials] = useState<string>('');
|
2020-09-10 22:23:55 +00:00
|
|
|
|
|
|
|
const defaultsInitializers = {
|
|
|
|
'default.cameraType': (s: string) => setCameraType(s as CameraType),
|
|
|
|
'default.numCopies': (s: string) => setNumCopies(parseInt(s, 10)),
|
|
|
|
'default.locationStr': (s: string) => setLocationStr(s),
|
|
|
|
};
|
2020-08-29 03:40:55 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
2020-09-11 21:38:53 +00:00
|
|
|
|
|
|
|
// Retrieve previous stored settings, if they exist.
|
2020-09-10 22:23:55 +00:00
|
|
|
AsyncStorage.multiGet(Object.keys(defaultsInitializers)).then(storedDefaults => {
|
|
|
|
storedDefaults.forEach(d => {
|
|
|
|
if (d[1] !== null) {
|
|
|
|
// @ts-ignore
|
|
|
|
defaultsInitializers[d[0]](d[1]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-09-11 21:38:53 +00:00
|
|
|
// Watch for changes to internet connectivity.
|
2020-09-14 17:41:22 +00:00
|
|
|
// TODO: Set up a timer that periodically syncs data with the database if connected.
|
2020-09-11 21:23:26 +00:00
|
|
|
NetInfo.addEventListener((state: NetInfoState) => {
|
|
|
|
if (state.type === NetInfoStateType.wifi) {
|
|
|
|
setIsConnected(!!(state.isConnected && state.isInternetReachable));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-09-11 21:38:53 +00:00
|
|
|
// Ask for permission to use the camera.
|
2020-08-29 03:40:55 +00:00
|
|
|
BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => {
|
|
|
|
if (value.granted) {
|
|
|
|
setAppState(BarcodeScannerAppState.DEFAULT);
|
|
|
|
} else {
|
|
|
|
setAppState(BarcodeScannerAppState.ERROR);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
2020-09-11 21:38:53 +00:00
|
|
|
// State event handlers
|
|
|
|
const _doNothing = () => {};
|
2020-09-02 16:13:16 +00:00
|
|
|
const _scan = () => {
|
|
|
|
setErrorMessage('');
|
|
|
|
setAppState(BarcodeScannerAppState.SCANNING);
|
|
|
|
};
|
|
|
|
const _inputIdNumber = () => {
|
|
|
|
setErrorMessage('');
|
2020-09-13 12:08:27 +00:00
|
|
|
setAppState(BarcodeScannerAppState.INPUT_ID);
|
2020-09-02 16:13:16 +00:00
|
|
|
};
|
2020-09-06 03:16:46 +00:00
|
|
|
const _inputLineCount = () => {
|
|
|
|
setErrorMessage('');
|
2020-09-13 12:08:27 +00:00
|
|
|
setAppState(BarcodeScannerAppState.INPUT_LINE_COUNT);
|
2020-09-06 03:16:46 +00:00
|
|
|
};
|
2020-09-01 02:12:32 +00:00
|
|
|
const _print = () => setAppState(BarcodeScannerAppState.PRINTING);
|
2020-09-14 17:41:22 +00:00
|
|
|
const _sync = () => setAppState(BarcodeScannerAppState.SYNC);
|
2020-09-01 02:12:32 +00:00
|
|
|
const _home = () => setAppState(BarcodeScannerAppState.DEFAULT);
|
2020-09-01 19:55:23 +00:00
|
|
|
const _settings = () => setAppState(BarcodeScannerAppState.SETTINGS);
|
2020-09-01 02:12:32 +00:00
|
|
|
|
2020-08-29 03:40:55 +00:00
|
|
|
const handleBarCodeScanned = (e: BarCodeEvent) => {
|
2020-09-01 19:55:23 +00:00
|
|
|
// Make sure the data is the right length.
|
2020-09-02 16:13:16 +00:00
|
|
|
// Scanned barcodes will be exactly 14 digits long.
|
|
|
|
// Manually-entered ID numbers will be exactly 9 digits long.
|
2020-09-01 19:55:23 +00:00
|
|
|
const barCodeString = e.data;
|
2020-09-02 16:13:16 +00:00
|
|
|
const pattern = /^[\d]{14}$|^[\d]{9}$/;
|
2020-09-04 21:50:50 +00:00
|
|
|
console.log('barCodeString', barCodeString);
|
2020-09-01 19:55:23 +00:00
|
|
|
if (pattern.test(barCodeString)) {
|
2020-09-02 16:13:16 +00:00
|
|
|
const cardId = e.data.slice(0, 9);
|
2020-09-03 22:27:44 +00:00
|
|
|
const newSampleDate = new Date();
|
2020-09-02 21:09:49 +00:00
|
|
|
setBarCodeId(cardId);
|
2020-09-03 22:27:44 +00:00
|
|
|
setSampleDate(newSampleDate);
|
2020-09-13 12:08:27 +00:00
|
|
|
setAppState(BarcodeScannerAppState.INPUT_INITIALS);
|
2020-09-01 19:55:23 +00:00
|
|
|
} else {
|
2020-09-02 16:13:16 +00:00
|
|
|
setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`);
|
2020-09-01 19:55:23 +00:00
|
|
|
setAppState(BarcodeScannerAppState.ERROR);
|
|
|
|
}
|
2020-08-29 03:40:55 +00:00
|
|
|
};
|
|
|
|
|
2020-09-13 12:08:27 +00:00
|
|
|
const handleInitialsInput = (newInitials: string) => {
|
|
|
|
setInitials(newInitials);
|
2020-09-14 17:41:22 +00:00
|
|
|
const newSampleId = [barCodeId, newInitials, format(sampleDate, defaults.dateEncodedFormat), locationStr].join('-');
|
2020-09-13 12:08:27 +00:00
|
|
|
setSampleId(newSampleId);
|
|
|
|
setAppState(BarcodeScannerAppState.SCANNED);
|
|
|
|
};
|
|
|
|
|
2020-09-06 03:16:46 +00:00
|
|
|
const handleLineCountSubmitted = (newCount: number) => {
|
|
|
|
const now = new Date();
|
2020-09-14 17:41:22 +00:00
|
|
|
const newId = `${locationStr}-${format(now, defaults.dateEncodedFormat)}`;
|
2020-09-06 03:16:46 +00:00
|
|
|
const newData: LineCount = {
|
|
|
|
id: newId,
|
|
|
|
lineCount: newCount,
|
|
|
|
locationId: locationStr,
|
|
|
|
createdAt: now,
|
|
|
|
};
|
2020-09-14 17:41:22 +00:00
|
|
|
|
|
|
|
AsyncStorage.setItem(newData.id, JSON.stringify(newData)).then(_sync);
|
2020-09-06 03:16:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const ErrorMessage = (props: ElementProps): ReactElement => {
|
2020-09-01 19:55:23 +00:00
|
|
|
return <View style={styles.fullScreen}>
|
|
|
|
<View style={styles.container}>
|
|
|
|
<ScanButton onClicked={_scan}/>
|
2020-09-02 16:13:16 +00:00
|
|
|
<InputIdButton onClicked={_inputIdNumber}/>
|
2020-09-01 19:55:23 +00:00
|
|
|
</View>
|
|
|
|
<Snackbar
|
|
|
|
visible={appState === BarcodeScannerAppState.ERROR}
|
|
|
|
onDismiss={_doNothing}
|
|
|
|
style={styles.error}
|
2020-09-02 16:13:16 +00:00
|
|
|
>{errorMessage === '' ? 'Something went wrong. Try again.' : errorMessage}</Snackbar>
|
2020-09-01 19:55:23 +00:00
|
|
|
</View>
|
2020-08-29 03:40:55 +00:00
|
|
|
}
|
|
|
|
|
2020-09-06 03:16:46 +00:00
|
|
|
const LoadingMessage = (props: ElementProps): ReactElement => {
|
2020-09-01 19:55:23 +00:00
|
|
|
return <Snackbar
|
|
|
|
visible={appState === BarcodeScannerAppState.INITIAL}
|
|
|
|
onDismiss={_doNothing}
|
|
|
|
>Loading...</Snackbar>;
|
2020-08-29 03:40:55 +00:00
|
|
|
}
|
|
|
|
|
2020-09-06 03:16:46 +00:00
|
|
|
const ActionButtons = (props: ElementProps): ReactElement => {
|
2020-09-01 19:55:23 +00:00
|
|
|
return <View>
|
2020-09-01 02:12:32 +00:00
|
|
|
<PrintButton onClicked={_print}/>
|
|
|
|
<CancelButton onClicked={_home}/>
|
|
|
|
</View>
|
|
|
|
}
|
|
|
|
|
2020-09-14 17:41:22 +00:00
|
|
|
const AppContent = (props: StateProps): ReactElement => {
|
2020-08-29 03:40:55 +00:00
|
|
|
switch (props.appState) {
|
|
|
|
case BarcodeScannerAppState.INITIAL:
|
|
|
|
return <LoadingMessage/>;
|
|
|
|
case BarcodeScannerAppState.DEFAULT:
|
2020-09-01 19:55:23 +00:00
|
|
|
return <View style={styles.container}>
|
|
|
|
<ScanButton onClicked={_scan}/>
|
2020-09-02 16:13:16 +00:00
|
|
|
<InputIdButton onClicked={_inputIdNumber}/>
|
2020-09-06 03:16:46 +00:00
|
|
|
<InputLineCountButton onClicked={_inputLineCount}/>
|
2020-09-01 19:55:23 +00:00
|
|
|
</View>;
|
2020-09-14 17:41:22 +00:00
|
|
|
case BarcodeScannerAppState.SYNC:
|
2020-09-11 21:23:26 +00:00
|
|
|
return <SyncMessage
|
|
|
|
isConnected={isConnected}
|
|
|
|
samplesCollection={samplesCollection}
|
|
|
|
countsCollection={countsCollection}
|
|
|
|
onCancel={_home}
|
|
|
|
onSync={_home}
|
|
|
|
/>;
|
2020-08-29 03:40:55 +00:00
|
|
|
case BarcodeScannerAppState.PRINTING:
|
2020-09-02 21:09:49 +00:00
|
|
|
return <View style={styles.container}>
|
|
|
|
<PrintingMessage
|
2020-09-10 22:23:55 +00:00
|
|
|
numCopies={numCopies}
|
2020-09-14 17:41:22 +00:00
|
|
|
onCancel={_sync}
|
2020-09-02 21:09:49 +00:00
|
|
|
id={sampleId}
|
|
|
|
barCodeId={barCodeId}
|
|
|
|
date={sampleDate}
|
|
|
|
location={locationStr}
|
2020-09-13 12:08:27 +00:00
|
|
|
initials={initials}
|
2020-09-02 21:09:49 +00:00
|
|
|
/>
|
|
|
|
</View>;
|
2020-08-29 03:40:55 +00:00
|
|
|
case BarcodeScannerAppState.SCANNED:
|
2020-09-01 02:12:32 +00:00
|
|
|
return <View style={styles.container}>
|
2020-09-02 21:09:49 +00:00
|
|
|
<BarCodeDisplay
|
|
|
|
id={sampleId}
|
|
|
|
barCodeId={barCodeId}
|
|
|
|
date={sampleDate}
|
|
|
|
location={locationStr}
|
2020-09-13 12:08:27 +00:00
|
|
|
initials={initials}
|
2020-09-02 21:09:49 +00:00
|
|
|
/>
|
2020-09-01 19:55:23 +00:00
|
|
|
<ActionButtons/>
|
2020-08-29 03:40:55 +00:00
|
|
|
</View>;
|
|
|
|
case BarcodeScannerAppState.SCANNING:
|
2020-09-01 19:55:23 +00:00
|
|
|
return <Scanner
|
|
|
|
onScanned={handleBarCodeScanned}
|
|
|
|
onCancel={_home}
|
2020-09-04 21:50:50 +00:00
|
|
|
cameraType={cameraType}
|
2020-09-01 19:55:23 +00:00
|
|
|
/>;
|
2020-09-13 12:08:27 +00:00
|
|
|
case BarcodeScannerAppState.INPUT_ID:
|
2020-09-02 16:13:16 +00:00
|
|
|
return <IdNumberInput
|
|
|
|
onScanned={handleBarCodeScanned}
|
|
|
|
onCancel={_home}
|
2020-09-04 21:50:50 +00:00
|
|
|
cameraType={undefined}
|
2020-09-02 16:13:16 +00:00
|
|
|
/>;
|
2020-09-13 12:08:27 +00:00
|
|
|
case BarcodeScannerAppState.INPUT_INITIALS:
|
|
|
|
return <InitialsInput
|
|
|
|
onSave={handleInitialsInput}
|
|
|
|
onCancel={_home}
|
|
|
|
/>;
|
|
|
|
case BarcodeScannerAppState.INPUT_LINE_COUNT:
|
2020-09-06 03:16:46 +00:00
|
|
|
return <InputLineCountScreen
|
|
|
|
onSave={handleLineCountSubmitted}
|
|
|
|
onCancel={_home}
|
|
|
|
/>;
|
2020-09-01 19:55:23 +00:00
|
|
|
case BarcodeScannerAppState.SETTINGS:
|
2020-09-06 03:16:46 +00:00
|
|
|
return <SettingsScreen
|
|
|
|
cameraType={cameraType}
|
2020-09-10 22:23:55 +00:00
|
|
|
numCopies={numCopies}
|
2020-09-06 03:16:46 +00:00
|
|
|
locationStr={locationStr}
|
2020-09-10 22:23:55 +00:00
|
|
|
onSave={(newCameraType: CameraType, newNumCopies: number, newLocationStr: string) => {
|
2020-09-06 03:16:46 +00:00
|
|
|
setCameraType(newCameraType);
|
2020-09-10 22:23:55 +00:00
|
|
|
setNumCopies(newNumCopies);
|
2020-09-06 03:16:46 +00:00
|
|
|
setLocationStr(newLocationStr);
|
2020-09-10 22:23:55 +00:00
|
|
|
|
|
|
|
AsyncStorage.multiSet([
|
|
|
|
['default.cameraType', newCameraType as string],
|
|
|
|
['default.locationStr', newLocationStr],
|
|
|
|
['default.numCopies', newNumCopies.toString()],
|
|
|
|
]).then(() => {
|
|
|
|
console.log('New defaults stored.');
|
|
|
|
AsyncStorage.multiGet(Object.keys(defaultsInitializers)).then(storedDefaults => {
|
|
|
|
console.log('stored defaults after saving Settings:', storedDefaults);
|
|
|
|
});
|
|
|
|
});
|
2020-09-06 03:16:46 +00:00
|
|
|
_home();
|
|
|
|
}}
|
|
|
|
onCancel={_home}
|
|
|
|
/>;
|
2020-08-29 03:40:55 +00:00
|
|
|
default:
|
|
|
|
return <ErrorMessage/>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2020-09-01 02:12:32 +00:00
|
|
|
<PaperProvider theme={theme}>
|
2020-09-11 21:23:26 +00:00
|
|
|
<Appbar.Header dark={true} style={isConnected ? styles.connected : styles.disconnected}>
|
2020-09-01 19:55:23 +00:00
|
|
|
<Appbar.Content title={`${appExpo.description} #${locationStr}`}/>
|
|
|
|
<Appbar.Action icon="home" onPress={_home}/>
|
|
|
|
<Appbar.Action icon="settings" onPress={_settings}/>
|
2020-09-01 02:12:32 +00:00
|
|
|
</Appbar.Header>
|
2020-09-01 19:55:23 +00:00
|
|
|
<SafeAreaView style={styles.safeAreaView}>
|
2020-09-14 17:41:22 +00:00
|
|
|
<AppContent appState={appState}/>
|
2020-09-01 02:12:32 +00:00
|
|
|
</SafeAreaView>
|
|
|
|
</PaperProvider>
|
2020-08-29 03:40:55 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-09-01 02:12:32 +00:00
|
|
|
AppRegistry.registerComponent(appExpo.name, () => Main);
|