Syncs database when connected to the network.
This commit is contained in:
parent
b1cae17dcd
commit
10ceb3e337
63
App.tsx
63
App.tsx
|
@ -1,8 +1,8 @@
|
||||||
// @refresh reset
|
// @refresh reset
|
||||||
import AsyncStorage from '@react-native-community/async-storage';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
|
import NetInfo, {NetInfoState, NetInfoStateType} from '@react-native-community/netinfo';
|
||||||
import {format, parse} from 'date-fns';
|
import {format, parse} from 'date-fns';
|
||||||
import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
|
import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
|
||||||
// @ts-ignore
|
|
||||||
import * as firebase from 'firebase';
|
import * as firebase from 'firebase';
|
||||||
import 'firebase/firestore';
|
import 'firebase/firestore';
|
||||||
import React, {ReactElement, useCallback, useEffect, useState} from 'react';
|
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 {IdNumberInput, InputIdButton, ScanButton, Scanner} from './components/Scan';
|
||||||
import {SettingsScreen} from './components/Settings';
|
import {SettingsScreen} from './components/Settings';
|
||||||
import {colors, styles} from './components/Styles';
|
import {colors, styles} from './components/Styles';
|
||||||
|
import {sendDataToFirebase, SyncMessage} from './components/Sync';
|
||||||
|
import {dateFormat, firebaseConfig} from './config/default';
|
||||||
import {BarcodeScannerAppState} from './models/BarcodeScannerAppState';
|
import {BarcodeScannerAppState} from './models/BarcodeScannerAppState';
|
||||||
import {CameraType, ElementProps, StateProps} from './models/ElementProps';
|
import {CameraType, ElementProps, StateProps} from './models/ElementProps';
|
||||||
import {LineCount} from './models/LineCount';
|
import {LineCount} from './models/LineCount';
|
||||||
import {Sample} from './models/Sample';
|
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.
|
// Initialize Firebase if not already initialized.
|
||||||
if (firebase.apps.length === 0) {
|
if (firebase.apps.length === 0) {
|
||||||
firebase.initializeApp(firebaseConfig);
|
firebase.initializeApp(firebaseConfig);
|
||||||
|
@ -37,13 +29,12 @@ if (firebase.apps.length === 0) {
|
||||||
|
|
||||||
YellowBox.ignoreWarnings([
|
YellowBox.ignoreWarnings([
|
||||||
'Setting a timer for a long period of time', // Ignore Firebase timer warnings
|
'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 db = firebase.firestore();
|
||||||
const samplesCollection = db.collection('samples');
|
const samplesCollection = db.collection('samples');
|
||||||
const countsCollection = db.collection('counts');
|
const countsCollection = db.collection('counts');
|
||||||
const dateFormat = 'yyyyMMddHHmm';
|
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
...DefaultTheme,
|
...DefaultTheme,
|
||||||
|
@ -61,6 +52,7 @@ export default function Main() {
|
||||||
const [lineCounts, setLineCounts] = useState<LineCount[]>([]);
|
const [lineCounts, setLineCounts] = useState<LineCount[]>([]);
|
||||||
const [cameraType, setCameraType] = useState<CameraType>('back');
|
const [cameraType, setCameraType] = useState<CameraType>('back');
|
||||||
const [numCopies, setNumCopies] = useState<number>(0);
|
const [numCopies, setNumCopies] = useState<number>(0);
|
||||||
|
const [isConnected, setIsConnected] = useState<boolean>(false);
|
||||||
|
|
||||||
const defaultsInitializers = {
|
const defaultsInitializers = {
|
||||||
'default.cameraType': (s: string) => setCameraType(s as CameraType),
|
'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) => {
|
BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => {
|
||||||
if (value.granted) {
|
if (value.granted) {
|
||||||
setAppState(BarcodeScannerAppState.DEFAULT);
|
setAppState(BarcodeScannerAppState.DEFAULT);
|
||||||
|
@ -182,10 +180,6 @@ export default function Main() {
|
||||||
setLineCounts((previousLineCounts) => previousLineCounts.concat(newLineCounts));
|
setLineCounts((previousLineCounts) => previousLineCounts.concat(newLineCounts));
|
||||||
}, [lineCounts]);
|
}, [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 => {
|
const ErrorMessage = (props: ElementProps): ReactElement => {
|
||||||
return <View style={styles.fullScreen}>
|
return <View style={styles.fullScreen}>
|
||||||
|
@ -208,10 +202,6 @@ export default function Main() {
|
||||||
>Loading...</Snackbar>;
|
>Loading...</Snackbar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SuccessMessage = (props: ElementProps): ReactElement => {
|
|
||||||
return <Title>Your barcode label has printed successfully.</Title>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ActionButtons = (props: ElementProps): ReactElement => {
|
const ActionButtons = (props: ElementProps): ReactElement => {
|
||||||
return <View>
|
return <View>
|
||||||
<PrintButton onClicked={_print}/>
|
<PrintButton onClicked={_print}/>
|
||||||
|
@ -230,28 +220,13 @@ export default function Main() {
|
||||||
<InputLineCountButton onClicked={_inputLineCount}/>
|
<InputLineCountButton onClicked={_inputLineCount}/>
|
||||||
</View>;
|
</View>;
|
||||||
case BarcodeScannerAppState.PRINTED:
|
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.
|
return <SyncMessage
|
||||||
|
isConnected={isConnected}
|
||||||
// Upload any changes to Firebase
|
samplesCollection={samplesCollection}
|
||||||
AsyncStorage.getAllKeys().then(keys => {
|
countsCollection={countsCollection}
|
||||||
const newSamples = keys
|
onCancel={_home}
|
||||||
.filter(s => /^[\d]{9}-[\d]{12}-[\d]{4}$/.test(s))
|
onSync={_home}
|
||||||
.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/>;
|
|
||||||
case BarcodeScannerAppState.PRINTING:
|
case BarcodeScannerAppState.PRINTING:
|
||||||
return <View style={styles.container}>
|
return <View style={styles.container}>
|
||||||
<PrintingMessage
|
<PrintingMessage
|
||||||
|
@ -325,7 +300,7 @@ export default function Main() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaperProvider theme={theme}>
|
<PaperProvider theme={theme}>
|
||||||
<Appbar.Header dark={true}>
|
<Appbar.Header dark={true} style={isConnected ? styles.connected : styles.disconnected}>
|
||||||
<Appbar.Content title={`${appExpo.description} #${locationStr}`}/>
|
<Appbar.Content title={`${appExpo.description} #${locationStr}`}/>
|
||||||
<Appbar.Action icon="home" onPress={_home}/>
|
<Appbar.Action icon="home" onPress={_home}/>
|
||||||
<Appbar.Action icon="settings" onPress={_settings}/>
|
<Appbar.Action icon="settings" onPress={_settings}/>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<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" />
|
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
#Thu Sep 10 15:14:20 EDT 2020
|
#Fri Sep 11 17:23:10 EDT 2020
|
||||||
VERSION_NAME=1.0.4
|
VERSION_NAME=1.0.7
|
||||||
VERSION_BUILD=13
|
VERSION_BUILD=16
|
||||||
VERSION_CODE=8
|
VERSION_CODE=11
|
||||||
|
|
|
@ -17,10 +17,10 @@ export const Scanner = (props: ScannerProps): ReactElement => {
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.centerMiddle}>
|
<View style={styles.centerMiddle}>
|
||||||
<View style={styles.captureBox}/>
|
<View style={styles.captureBox}/>
|
||||||
</View>
|
|
||||||
<Subheading style={styles.shadow}>
|
<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.
|
Instruct the patient to hold their card up with the barcode facing the camera. Keep the barcode in the orange box.
|
||||||
</Subheading>
|
</Subheading>
|
||||||
|
</View>
|
||||||
<View style={styles.centerMiddle}>
|
<View style={styles.centerMiddle}>
|
||||||
<Button
|
<Button
|
||||||
mode="text"
|
mode="text"
|
||||||
|
|
|
@ -46,6 +46,12 @@ export const styles = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
connected: {
|
||||||
|
backgroundColor: colors.primary
|
||||||
|
},
|
||||||
|
disconnected: {
|
||||||
|
backgroundColor: '#333333'
|
||||||
|
},
|
||||||
container: {
|
container: {
|
||||||
..._common.container,
|
..._common.container,
|
||||||
..._common.dark,
|
..._common.dark,
|
||||||
|
|
|
@ -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>;
|
||||||
|
}
|
|
@ -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';
|
|
@ -1,8 +1,9 @@
|
||||||
import {BarCodeScannedCallback} from 'expo-barcode-scanner';
|
import {BarCodeScannedCallback} from 'expo-barcode-scanner';
|
||||||
import {BarcodeScannerAppState} from './BarcodeScannerAppState';
|
import {BarcodeScannerAppState} from './BarcodeScannerAppState';
|
||||||
|
import * as firebase from 'firebase';
|
||||||
|
import 'firebase/firestore';
|
||||||
|
|
||||||
export declare type CameraType = number | 'front' | 'back' | undefined;
|
export declare type CameraType = number | 'front' | 'back' | undefined;
|
||||||
export declare type CheckedStatus ='checked' | 'unchecked' | undefined;
|
|
||||||
|
|
||||||
export interface ElementProps {
|
export interface ElementProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -42,6 +43,14 @@ export interface ScannerProps extends ElementProps {
|
||||||
cameraType: CameraType;
|
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 {
|
export interface PrintingProps extends BarCodeProps {
|
||||||
numCopies: number;
|
numCopies: number;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
|
Loading…
Reference in New Issue