// @refresh reset import AsyncStorage from '@react-native-community/async-storage'; import {format, parse} from 'date-fns'; import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner'; // @ts-ignore import * as firebase from 'firebase'; import 'firebase/firestore'; import React, {ReactElement, useCallback, useEffect, useState} from 'react'; import {AppRegistry, SafeAreaView, View, YellowBox} from 'react-native'; import {Appbar, DefaultTheme, Provider as PaperProvider, Snackbar, Title,} from 'react-native-paper'; import {expo as appExpo} from './app.json'; import {CancelButton} from './components/Common'; import {InputLineCountButton, InputLineCountScreen} from './components/LineCount'; import {BarCodeDisplay, PrintButton, PrintingMessage} from './components/Print'; import {IdNumberInput, InputIdButton, ScanButton, Scanner} from './components/Scan'; import {SettingsScreen} from './components/Settings'; import {colors, styles} from './components/Styles'; import {BarcodeScannerAppState} from './models/BarcodeScannerAppState'; import {CameraType, ElementProps, StateProps} from './models/ElementProps'; import {LineCount} from './models/LineCount'; import {Sample} from './models/Sample'; const firebaseConfig = { apiKey: 'api_key_goes_here', authDomain: "uva-covid19-testing-kiosk.firebaseapp.com", databaseURL: "https://uva-covid19-testing-kiosk.firebaseio.com", projectId: 'project_id_goes_here', storageBucket: "uva-covid19-testing-kiosk.appspot.com", messagingSenderId: 'sender_id_goes_here', appId: 'app_id_goes_here' }; // Initialize Firebase if not already initialized. if (firebase.apps.length === 0) { firebase.initializeApp(firebaseConfig); } YellowBox.ignoreWarnings([ 'Setting a timer for a long period of time', // Ignore Firebase timer warnings 'Remote debugger is in a background tab', // Ignore Firebase timer warnings ]); const db = firebase.firestore(); const samplesCollection = db.collection('samples'); const countsCollection = db.collection('counts'); const dateFormat = 'yyyyMMddHHmm'; const theme = { ...DefaultTheme, colors: colors, } export default function Main() { const [appState, setAppState] = useState(BarcodeScannerAppState.INITIAL); const [sampleId, setSampleId] = useState(''); const [barCodeId, setBarCodeId] = useState(''); const [sampleDate, setSampleDate] = useState(new Date()); const [locationStr, setLocationStr] = useState('4321'); const [errorMessage, setErrorMessage] = useState(''); const [samples, setSamples] = useState([]); const [lineCounts, setLineCounts] = useState([]); const [cameraType, setCameraType] = useState('back'); useEffect(() => { BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => { if (value.granted) { setAppState(BarcodeScannerAppState.DEFAULT); } else { setAppState(BarcodeScannerAppState.ERROR); } }); const unsubscribeSamples = samplesCollection.onSnapshot(querySnapshot => { // Transform and sort the data returned from Firebase const samplesFirestore = querySnapshot .docChanges() .filter(({type}) => type === 'added') .map(({doc}) => { const sample = doc.data(); return {...sample, createdAt: sample.createdAt.toDate()} as Sample; }) .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); appendSamples(samplesFirestore); }); const unsubscribeCounts = countsCollection.onSnapshot(querySnapshot => { // Transform and sort the data returned from Firebase const lineCountsFirestore = querySnapshot .docChanges() .filter(({type}) => type === 'added') .map(({doc}) => { const lineCount = doc.data(); return {...lineCount, createdAt: lineCount.createdAt.toDate()} as LineCount; }) .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); appendLineCounts(lineCountsFirestore); }); return () => { unsubscribeSamples() unsubscribeCounts() } }, []); const _doNothing = () => { }; const _scan = () => { setErrorMessage(''); setAppState(BarcodeScannerAppState.SCANNING); }; const _inputIdNumber = () => { setErrorMessage(''); setAppState(BarcodeScannerAppState.INPUT); }; const _inputLineCount = () => { setErrorMessage(''); setAppState(BarcodeScannerAppState.COUNT); }; const _print = () => setAppState(BarcodeScannerAppState.PRINTING); const _printed = () => setAppState(BarcodeScannerAppState.PRINTED); const _home = () => setAppState(BarcodeScannerAppState.DEFAULT); const _settings = () => setAppState(BarcodeScannerAppState.SETTINGS); const handleBarCodeScanned = (e: BarCodeEvent) => { // Make sure the data is the right length. // Scanned barcodes will be exactly 14 digits long. // Manually-entered ID numbers will be exactly 9 digits long. const barCodeString = e.data; const pattern = /^[\d]{14}$|^[\d]{9}$/; console.log('barCodeString', barCodeString); if (pattern.test(barCodeString)) { const cardId = e.data.slice(0, 9); const newSampleDate = new Date(); const newSampleId = [cardId, format(newSampleDate, dateFormat), locationStr].join('-'); setSampleId(newSampleId); setBarCodeId(cardId); setSampleDate(newSampleDate); setAppState(BarcodeScannerAppState.SCANNED); } else { setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`); setAppState(BarcodeScannerAppState.ERROR); } }; const handleLineCountSubmitted = (newCount: number) => { const now = new Date(); const newId = `${locationStr}-${format(now, dateFormat)}`; const newData: LineCount = { id: newId, lineCount: newCount, locationId: locationStr, createdAt: now, }; sendDataToFirebase([newData], countsCollection); } const appendSamples = useCallback((newSamples) => { setSamples((previousSamples) => previousSamples.concat(newSamples)); }, [samples]); const appendLineCounts = useCallback((newLineCounts) => { setLineCounts((previousLineCounts) => previousLineCounts.concat(newLineCounts)); }, [lineCounts]); const sendDataToFirebase = async (newData: Array, collection: firebase.firestore.CollectionReference) => { const writes = newData.map((s: Sample|LineCount) => collection.doc(s.id).set(s)); await Promise.all(writes); } const ErrorMessage = (props: ElementProps): ReactElement => { return {errorMessage === '' ? 'Something went wrong. Try again.' : errorMessage} } const LoadingMessage = (props: ElementProps): ReactElement => { return Loading...; } const SuccessMessage = (props: ElementProps): ReactElement => { return Your barcode label has printed successfully.; } const ActionButtons = (props: ElementProps): ReactElement => { return } function App(props: StateProps): ReactElement { switch (props.appState) { case BarcodeScannerAppState.INITIAL: return ; case BarcodeScannerAppState.DEFAULT: return ; case BarcodeScannerAppState.PRINTED: // TODO: Detect when user is online. If online, sync data with Firebase. If not online, just go home. Alternatively, set up a timer that periodically syncs data with the database. // Upload any changes to Firebase AsyncStorage.getAllKeys().then(keys => { const newSamples = keys .filter(s => /^[\d]{9}-[\d]{12}-[\d]{4}$/.test(s)) .map(s => { const propsArray = s.split('-'); return { id: s, barcodeId: propsArray[0], createdAt: parse(propsArray[1], dateFormat, new Date()), locationId: propsArray[2], } as Sample; }); sendDataToFirebase(newSamples, samplesCollection).then(() => { return _home; }); }); return ; case BarcodeScannerAppState.PRINTING: return ; case BarcodeScannerAppState.SCANNED: return ; case BarcodeScannerAppState.SCANNING: return ; case BarcodeScannerAppState.INPUT: return ; case BarcodeScannerAppState.COUNT: return ; case BarcodeScannerAppState.SETTINGS: return { setCameraType(newCameraType); setLocationStr(newLocationStr); _home(); }} onCancel={_home} />; default: return ; } } return ( ); }; AppRegistry.registerComponent(appExpo.name, () => Main);