Syncs database when connected to the network.

This commit is contained in:
Aaron Louie 2020-09-11 17:23:26 -04:00
parent b1cae17dcd
commit 10ceb3e337
9 changed files with 731 additions and 660 deletions

63
App.tsx
View File

@ -1,8 +1,8 @@
// @refresh reset
import AsyncStorage from '@react-native-community/async-storage';
import NetInfo, {NetInfoState, NetInfoStateType} from '@react-native-community/netinfo';
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';
@ -15,21 +15,13 @@ 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 {sendDataToFirebase, SyncMessage} from './components/Sync';
import {dateFormat, firebaseConfig} from './config/default';
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);
@ -37,13 +29,12 @@ if (firebase.apps.length === 0) {
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
'Remote debugger is in a background tab', // Ignore remote debugger warnings
]);
const db = firebase.firestore();
const samplesCollection = db.collection('samples');
const countsCollection = db.collection('counts');
const dateFormat = 'yyyyMMddHHmm';
const theme = {
...DefaultTheme,
@ -61,6 +52,7 @@ export default function Main() {
const [lineCounts, setLineCounts] = useState<LineCount[]>([]);
const [cameraType, setCameraType] = useState<CameraType>('back');
const [numCopies, setNumCopies] = useState<number>(0);
const [isConnected, setIsConnected] = useState<boolean>(false);
const defaultsInitializers = {
'default.cameraType': (s: string) => setCameraType(s as CameraType),
@ -79,6 +71,12 @@ export default function Main() {
});
});
NetInfo.addEventListener((state: NetInfoState) => {
if (state.type === NetInfoStateType.wifi) {
setIsConnected(!!(state.isConnected && state.isInternetReachable));
}
});
BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => {
if (value.granted) {
setAppState(BarcodeScannerAppState.DEFAULT);
@ -182,10 +180,6 @@ export default function Main() {
setLineCounts((previousLineCounts) => previousLineCounts.concat(newLineCounts));
}, [lineCounts]);
const sendDataToFirebase = async (newData: Array<Sample|LineCount>, 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 <View style={styles.fullScreen}>
@ -208,10 +202,6 @@ export default function Main() {
>Loading...</Snackbar>;
}
const SuccessMessage = (props: ElementProps): ReactElement => {
return <Title>Your barcode label has printed successfully.</Title>;
}
const ActionButtons = (props: ElementProps): ReactElement => {
return <View>
<PrintButton onClicked={_print}/>
@ -230,28 +220,13 @@ export default function Main() {
<InputLineCountButton onClicked={_inputLineCount}/>
</View>;
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 <SuccessMessage/>;
return <SyncMessage
isConnected={isConnected}
samplesCollection={samplesCollection}
countsCollection={countsCollection}
onCancel={_home}
onSync={_home}
/>;
case BarcodeScannerAppState.PRINTING:
return <View style={styles.container}>
<PrintingMessage
@ -325,7 +300,7 @@ export default function Main() {
return (
<PaperProvider theme={theme}>
<Appbar.Header dark={true}>
<Appbar.Header dark={true} style={isConnected ? styles.connected : styles.disconnected}>
<Appbar.Content title={`${appExpo.description} #${locationStr}`}/>
<Appbar.Action icon="home" onPress={_home}/>
<Appbar.Action icon="settings" onPress={_settings}/>

View File

@ -2,6 +2,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
</manifest>

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
#Thu Sep 10 15:14:20 EDT 2020
VERSION_NAME=1.0.4
VERSION_BUILD=13
VERSION_CODE=8
#Fri Sep 11 17:23:10 EDT 2020
VERSION_NAME=1.0.7
VERSION_BUILD=16
VERSION_CODE=11

View File

@ -17,10 +17,10 @@ export const Scanner = (props: ScannerProps): ReactElement => {
</View>
<View style={styles.centerMiddle}>
<View style={styles.captureBox}/>
<Subheading style={styles.shadow}>
Instruct the patient to hold their card up with the barcode facing the camera. Keep the barcode in the orange box.
</Subheading>
</View>
<Subheading style={styles.shadow}>
Instruct the patient to hold their card up with the barcode facing the camera. Keep the barcode in the orange box.
</Subheading>
<View style={styles.centerMiddle}>
<Button
mode="text"

View File

@ -46,6 +46,12 @@ export const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
connected: {
backgroundColor: colors.primary
},
disconnected: {
backgroundColor: '#333333'
},
container: {
..._common.container,
..._common.dark,

59
components/Sync.tsx Normal file
View File

@ -0,0 +1,59 @@
import AsyncStorage from '@react-native-community/async-storage';
import {parse} from 'date-fns';
import React, {ReactElement, useEffect, useState} from 'react';
import {View} from 'react-native';
import {Title} from 'react-native-paper';
import {dateFormat} from '../config/default';
import {SyncProps} from '../models/ElementProps';
import {LineCount} from '../models/LineCount';
import {Sample} from '../models/Sample';
import {CancelButton} from './Common';
import {styles} from './Styles';
// @ts-ignore
import * as firebase from 'firebase';
import 'firebase/firestore';
export const sendDataToFirebase = async (newData: Array<Sample | LineCount>, collection: firebase.firestore.CollectionReference) => {
const writes = newData.map((s: Sample | LineCount) => collection.doc(s.id).set(s));
await Promise.all(writes);
}
export const SyncMessage = (props: SyncProps): ReactElement => {
const [syncStatus, setSyncStatus] = useState<string>('Syncing data...');
useEffect(() => {
// 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
if (props.isConnected) {
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, props.samplesCollection).then(() => {
setSyncStatus('Data synced.');
props.onSync();
});
});
} else {
setSyncStatus('Device is not online. Skipping sync...');
props.onCancel();
}
});
return <View style={styles.container}>
<View style={styles.centerMiddle}>
<Title style={styles.heading}>{syncStatus}</Title>
<CancelButton onClicked={props.onCancel} />
</View>
</View>;
}

11
config/default.tsx Normal file
View File

@ -0,0 +1,11 @@
export 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'
};
export const dateFormat = 'yyyyMMddHHmm';

View File

@ -1,8 +1,9 @@
import {BarCodeScannedCallback} from 'expo-barcode-scanner';
import {BarcodeScannerAppState} from './BarcodeScannerAppState';
import * as firebase from 'firebase';
import 'firebase/firestore';
export declare type CameraType = number | 'front' | 'back' | undefined;
export declare type CheckedStatus ='checked' | 'unchecked' | undefined;
export interface ElementProps {
title?: string;
@ -42,6 +43,14 @@ export interface ScannerProps extends ElementProps {
cameraType: CameraType;
}
export interface SyncProps extends ElementProps {
isConnected: boolean;
onSync: () => void;
onCancel: () => void;
samplesCollection: firebase.firestore.CollectionReference<firebase.firestore.DocumentData>;
countsCollection: firebase.firestore.CollectionReference<firebase.firestore.DocumentData>;
}
export interface PrintingProps extends BarCodeProps {
numCopies: number;
onCancel: () => void;