Adds initials input screen. Encodes initials into QR Code

This commit is contained in:
Aaron Louie 2020-09-13 08:08:27 -04:00
parent dfbdfc2725
commit 632bb0c324
10 changed files with 101 additions and 24 deletions

32
App.tsx
View File

@ -5,14 +5,14 @@ import {format} from 'date-fns';
import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
import * as firebase from 'firebase';
import 'firebase/firestore';
import React, {ReactElement, useCallback, useEffect, useState} from 'react';
import React, {ReactElement, useEffect, useState} from 'react';
import {AppRegistry, SafeAreaView, View, YellowBox} from 'react-native';
import {Appbar, Provider as PaperProvider, Snackbar,} 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 {IdNumberInput, InitialsInput, InputIdButton, ScanButton, Scanner} from './components/Scan';
import {SettingsScreen} from './components/Settings';
import {styles, theme} from './components/Styles';
import {sendDataToFirebase, SyncMessage} from './components/Sync';
@ -48,6 +48,7 @@ export default function Main() {
const [cameraType, setCameraType] = useState<CameraType>('back');
const [numCopies, setNumCopies] = useState<number>(0);
const [isConnected, setIsConnected] = useState<boolean>(false);
const [initials, setInitials] = useState<string>('');
const defaultsInitializers = {
'default.cameraType': (s: string) => setCameraType(s as CameraType),
@ -92,11 +93,11 @@ export default function Main() {
};
const _inputIdNumber = () => {
setErrorMessage('');
setAppState(BarcodeScannerAppState.INPUT);
setAppState(BarcodeScannerAppState.INPUT_ID);
};
const _inputLineCount = () => {
setErrorMessage('');
setAppState(BarcodeScannerAppState.COUNT);
setAppState(BarcodeScannerAppState.INPUT_LINE_COUNT);
};
const _print = () => setAppState(BarcodeScannerAppState.PRINTING);
const _printed = () => setAppState(BarcodeScannerAppState.PRINTED);
@ -113,18 +114,22 @@ export default function Main() {
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);
setAppState(BarcodeScannerAppState.INPUT_INITIALS);
} else {
setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`);
setAppState(BarcodeScannerAppState.ERROR);
}
};
const handleInitialsInput = (newInitials: string) => {
setInitials(newInitials);
const newSampleId = [barCodeId, newInitials, format(sampleDate, dateFormat), locationStr].join('-');
setSampleId(newSampleId);
setAppState(BarcodeScannerAppState.SCANNED);
};
const handleLineCountSubmitted = (newCount: number) => {
const now = new Date();
const newId = `${locationStr}-${format(now, dateFormat)}`;
@ -192,6 +197,7 @@ export default function Main() {
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
initials={initials}
/>
</View>;
case BarcodeScannerAppState.SCANNED:
@ -201,6 +207,7 @@ export default function Main() {
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
initials={initials}
/>
<ActionButtons/>
</View>;
@ -210,13 +217,18 @@ export default function Main() {
onCancel={_home}
cameraType={cameraType}
/>;
case BarcodeScannerAppState.INPUT:
case BarcodeScannerAppState.INPUT_ID:
return <IdNumberInput
onScanned={handleBarCodeScanned}
onCancel={_home}
cameraType={undefined}
/>;
case BarcodeScannerAppState.COUNT:
case BarcodeScannerAppState.INPUT_INITIALS:
return <InitialsInput
onSave={handleInitialsInput}
onCancel={_home}
/>;
case BarcodeScannerAppState.INPUT_LINE_COUNT:
return <InputLineCountScreen
onSave={handleLineCountSubmitted}
onCancel={_home}

View File

@ -16,7 +16,7 @@ public class MainActivity extends ReactActivity {
super.onCreate(savedInstanceState);
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, false);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, true);
}

View File

@ -7,5 +7,7 @@
</style>
<style name="Theme.App.SplashScreen" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/splashscreen</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowLightStatusBar">false</item>
</style>
</resources>
</resources>

View File

@ -8,7 +8,7 @@
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"resizeMode": "cover",
"backgroundColor": "#ffffff"
},
"updates": {
@ -35,4 +35,4 @@
"package": "com.sartography.uvacovid19testingkiosk"
}
}
}
}

View File

@ -5,6 +5,7 @@ import React, {ReactElement, useEffect, useState} from 'react';
import {Text, View} from 'react-native';
import {Button, Title} from 'react-native-paper';
import QRCode from 'react-native-qrcode-svg';
import {dateDisplayFormat} from '../config/default';
import {BarCodeProps, ButtonProps, PrintingProps} from '../models/ElementProps';
import {Sample} from '../models/Sample';
import {colors, styles} from './Styles';
@ -201,6 +202,7 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
barCodeId={props.barCodeId}
date={props.date}
location={props.location}
initials={props.initials}
/>
</View>
<View style={styles.container}>
@ -218,12 +220,11 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
</View>;
}
export const BarCodeDisplay = (props: BarCodeProps): ReactElement => {
return <View style={styles.printPreview}>
<Text style={styles.label}>ID#: {props.id}</Text>
<Text style={styles.label}>Date: {props.date.toLocaleDateString()}, {props.date.toLocaleTimeString()}</Text>
<Text style={styles.label}>Location {props.location}</Text>
<Text style={styles.label}>ID #: {props.id}</Text>
<Text style={styles.label}>Date: {format(props.date, dateDisplayFormat)}</Text>
<Text style={styles.label}>Location #: {props.location}</Text>
<QRCode value={props.id}/>
</View>;
}

View File

@ -2,7 +2,7 @@ import {BarCodeScanner} from 'expo-barcode-scanner';
import React, {ReactElement, useState} from 'react';
import {Text, View} from 'react-native';
import {Button, DefaultTheme, HelperText, Subheading, TextInput, Title} from 'react-native-paper';
import {ButtonProps, ScannerProps} from '../models/ElementProps';
import {ButtonProps, ElementProps, InputInitialsProps, ScannerProps} from '../models/ElementProps';
import {colors, styles} from './Styles';
@ -100,3 +100,49 @@ export const IdNumberInput = (props: ScannerProps): ReactElement => {
</View>
</View>;
};
export const InitialsInput = (props: InputInitialsProps): ReactElement => {
const [inputStr, setInputStr] = useState<string>('');
const pattern = /^[A-Za-z]{1,5}$/;
const hasErrors = () => {
return !pattern.test(inputStr);
};
const onSubmit = () => {
props.onSave(inputStr);
}
return <View style={styles.settings}>
<Title style={styles.headingInverse}>Enter Patient's Initials</Title>
<View style={{marginBottom: 10}}>
<Subheading style={{color: DefaultTheme.colors.text, marginBottom: 60}}>
Prompt the patient to show their name on their ID card. Enter the initials of their first name, middle name (if applicable), and last name.
</Subheading>
<TextInput
label="Initials"
value={inputStr}
onChangeText={inputStr => setInputStr(inputStr.toLowerCase())}
mode="outlined"
theme={DefaultTheme}
/>
<HelperText type="error" visible={hasErrors()}>
Please enter letters only.
</HelperText>
<Button
icon="check"
mode="contained"
color={colors.primary}
style={{marginBottom: 10}}
disabled={hasErrors()}
onPress={onSubmit}
>Submit</Button>
<Button
icon="cancel"
mode="outlined"
color={colors.primary}
onPress={props.onCancel}
>Cancel</Button>
</View>
</View>;
};

View File

@ -9,7 +9,6 @@ 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';
@ -22,10 +21,14 @@ 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.
// TODO: Alternatively, set up a timer that periodically syncs data with the database.
// Upload any changes to Firebase
// Detect when user is online. If online, sync data with Firebase.
if (props.isConnected) {
// Get the collection subscription
const unsubscribe = props.samplesCollection.onSnapshot(q => {}, e => {});
// Upload any changes to Firebase
AsyncStorage.getAllKeys().then(keys => {
const newSamples = keys
.filter(s => /^[\d]{9}-[\d]{12}-[\d]{4}$/.test(s))
@ -40,11 +43,16 @@ export const SyncMessage = (props: SyncProps): ReactElement => {
});
sendDataToFirebase(newSamples, props.samplesCollection).then(() => {
// TODO: Delete stored keys in AsyncStorage
setSyncStatus('Data synced.');
props.onSync();
});
});
return () => unsubscribe();
} else {
// If not online, just go home.
setSyncStatus('Device is not online. Skipping sync...');
props.onCancel();
}

View File

@ -9,3 +9,4 @@ export const firebaseConfig = {
};
export const dateFormat = 'yyyyMMddHHmm';
export const dateDisplayFormat = 'MM/dd/yyyy, hh:mm aa';

View File

@ -3,8 +3,9 @@ export enum BarcodeScannerAppState {
DEFAULT = 'DEFAULT',
SCANNING = 'SCANNING',
SCANNED = 'SCANNED',
INPUT = 'INPUT',
COUNT = 'COUNT',
INPUT_ID = 'INPUT_ID',
INPUT_INITIALS = 'INPUT_INITIALS',
INPUT_LINE_COUNT = 'INPUT_LINE_COUNT',
PRINTING = 'PRINTING',
PRINTED = 'PRINTED',
ERROR = 'ERROR',

View File

@ -18,12 +18,18 @@ export interface BarCodeProps extends ElementProps {
barCodeId: string;
date: Date;
location: string;
initials: string;
}
export interface ButtonProps extends ElementProps {
onClicked: () => void;
}
export interface InputInitialsProps extends ElementProps {
onSave: (newInitials: string) => void;
onCancel: () => void;
}
export interface InputLineCountScreenProps extends ElementProps {
onSave: (newCount: number) => void;
onCancel: () => void;