WIP: Displays QRCode instead of 1D barcode

Former-commit-id: 5a7dd6b7582fa2dea2131d4997eb6d0b8234eead
This commit is contained in:
Aaron Louie 2020-09-02 17:09:49 -04:00
parent 5729def6ca
commit 4e320b6619
9 changed files with 2053 additions and 111 deletions

67
.gitignore vendored
View File

@ -533,3 +533,70 @@ buck-out/
# Expo
.expo/*
web-build/
# The following contents were automatically generated by expo-cli during eject
# ----------------------------------------------------------------------------
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifacts
*.jsbundle
# CocoaPods
/ios/Pods/
# Expo
.expo/*
web-build/

130
App.tsx
View File

@ -1,16 +1,24 @@
// @refresh reset
import AsyncStorage from '@react-native-community/async-storage';
import {format, parse} from 'date-fns';
import {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
import React, {ReactElement, useEffect, useState} from 'react';
import {AppRegistry, SafeAreaView, View} from 'react-native';
// @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,
Button, DarkTheme,
Button,
DefaultTheme,
HelperText,
Provider as PaperProvider,
Snackbar, Subheading,
Snackbar,
Subheading,
TextInput,
Title
} from 'react-native-paper';
import QRCode from 'react-native-qrcode-svg';
import {expo as appExpo} from './app.json';
import {CancelButton} from './components/Common';
import {BarCodeDisplay, PrintButton, PrintingMessage} from './components/Print';
@ -18,6 +26,29 @@ import {IdNumberInput, InputIdButton, ScanButton, Scanner} from './components/Sc
import {colors, styles} from './components/Styles';
import {BarcodeScannerAppState} from './models/BarcodeScannerAppState';
import {ElementProps, StateProps} from './models/ElementProps';
import {Sample} from './models/Sample';
const firebaseConfig = {
apiKey: "AIzaSyCZHvaAQJKGiU1McxqgbrH-_KPV92JofUA",
authDomain: "uva-covid19-testing-kiosk.firebaseapp.com",
databaseURL: "https://uva-covid19-testing-kiosk.firebaseio.com",
projectId: "uva-covid19-testing-kiosk",
storageBucket: "uva-covid19-testing-kiosk.appspot.com",
messagingSenderId: "452622162774",
appId: "1:452622162774:web:077dc57e8aa59cc5b954f7"
};
// 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
]);
const db = firebase.firestore();
const samplesCollection = db.collection('samples');
const theme = {
...DefaultTheme,
@ -26,10 +57,13 @@ const theme = {
export default function Main() {
const [appState, setAppState] = useState<BarcodeScannerAppState>(BarcodeScannerAppState.INITIAL);
const [barCodeData, setBarCodeData] = useState<string>('');
const [date, setDate] = useState<Date>(new Date());
const [sampleId, setSampleId] = useState<string>('');
const [barCodeId, setBarCodeId] = useState<string>('');
const [sampleDate, setSampleDate] = useState<Date>(new Date());
const [locationStr, setLocationStr] = useState<string>('4321');
const [svgQrCode, setSvgQrCode] = useState<any>();
const [errorMessage, setErrorMessage] = useState<string>('');
const [samples, setSamples] = useState<Sample[]>([]);
useEffect(() => {
BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => {
@ -39,9 +73,26 @@ export default function Main() {
setAppState(BarcodeScannerAppState.ERROR);
}
});
const unsubscribe = 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);
});
return () => unsubscribe()
}, []);
const _doNothing = () => {};
const _doNothing = () => {
};
const _scan = () => {
setErrorMessage('');
setAppState(BarcodeScannerAppState.SCANNING);
@ -51,6 +102,7 @@ export default function Main() {
setAppState(BarcodeScannerAppState.INPUT);
};
const _print = () => setAppState(BarcodeScannerAppState.PRINTING);
const _printed = () => setAppState(BarcodeScannerAppState.PRINTED);
const _home = () => setAppState(BarcodeScannerAppState.DEFAULT);
const _settings = () => setAppState(BarcodeScannerAppState.SETTINGS);
@ -62,15 +114,31 @@ export default function Main() {
const pattern = /^[\d]{14}$|^[\d]{9}$/;
if (pattern.test(barCodeString)) {
const cardId = e.data.slice(0, 9);
setBarCodeData(cardId);
setDate(new Date());
setBarCodeId(cardId);
setSampleDate(new Date());
setAppState(BarcodeScannerAppState.SCANNED);
setSampleId([barCodeId, format(sampleDate, 'yyyyMMddHHmm'), locationStr].join('-'));
console.log('sampleId', sampleId);
new QRCode({value: sampleId, ecl: 'H', getRef: c => {
setSvgQrCode(c);
console.log('svgQrCode', svgQrCode);
}});
} else {
setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`);
setAppState(BarcodeScannerAppState.ERROR);
}
};
const appendSamples = useCallback((newSamples) => {
setSamples((previousSamples) => previousSamples.concat(newSamples));
}, [samples]);
const sendDataToFirebase = async (newSamples: Sample[]) => {
const writes = newSamples.map(s => samplesCollection.doc(s.id).set(s));
await Promise.all(writes);
}
function ErrorMessage(props: ElementProps): ReactElement {
return <View style={styles.fullScreen}>
<View style={styles.container}>
@ -114,7 +182,8 @@ export default function Main() {
<Title style={styles.headingInverse}>Settings</Title>
<View style={{marginBottom: 10}}>
<Subheading style={{color: DefaultTheme.colors.text, marginBottom: 60}}>
Please do NOT change this unless you know what you are doing. Entering an incorrect location number may prevent patients from getting accurate info about their test results.
Please do NOT change this unless you know what you are doing. Entering an incorrect location number may
prevent patients from getting accurate info about their test results.
</Subheading>
<TextInput
label="Location #"
@ -157,16 +226,45 @@ export default function Main() {
<InputIdButton onClicked={_inputIdNumber}/>
</View>;
case BarcodeScannerAppState.PRINTED:
// 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], 'yyyyMMddHHmm', new Date()),
locationId: propsArray[2],
} as Sample;
});
sendDataToFirebase(newSamples);
});
_home();
return <SuccessMessage/>;
case BarcodeScannerAppState.PRINTING:
return <PrintingMessage
onCancel={_home}
id={barCodeData}
date={date} location={locationStr}
/>;
return <View style={styles.container}>
<PrintingMessage
onCancel={_printed}
id={sampleId}
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
svg={svgQrCode}
/>
</View>;
case BarcodeScannerAppState.SCANNED:
return <View style={styles.container}>
<BarCodeDisplay id={barCodeData} date={date} location={locationStr}/>
<BarCodeDisplay
id={sampleId}
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
svg={svgQrCode}
/>
<ActionButtons/>
</View>;
case BarcodeScannerAppState.SCANNING:

View File

@ -1,9 +1,9 @@
import React, {ReactElement, useEffect, useState} from 'react';
import {Text, View} from 'react-native';
// @ts-ignore
import Barcode from 'react-native-barcode-builder';
import {Button, Title} from 'react-native-paper';
import QRCode from 'react-native-qrcode-svg';
import {BarCodeProps, ButtonProps, PrintingProps} from '../models/ElementProps';
import {Sample} from '../models/Sample';
import {colors, styles} from './Styles';
import AsyncStorage from '@react-native-community/async-storage';
import * as Print from 'expo-print';
@ -15,44 +15,18 @@ enum PrintStatus {
DONE = 'DONE',
}
const _renderBarCodeRects = (props: PrintingProps): string => {
const dataStr = _propsToDataString(props);
const rects: string[] = [];
// TODO: set base width from label width in pixels
// 2 inches = 190 pixels
// for now, just guesstimate.
const baseWidth = 7;
for (let i = 0; i < dataStr.length; i++) {
// TODO: Convert dataStr to barcode rectangles with x, y, width, height
// barcodejs library has some useful stuff?
// Or maybe somehow use something in the guts of react-native-barcode-builder?
// For now, just put in some dummy x values and widths.
rects.push(`<rect width="${Math.floor(Math.random() * baseWidth)}" height="20" x="${baseWidth * i}" y="70" fill="black" />`);
}
return rects.join(' ')
}
const _propsToDataString = (props: BarCodeProps): string => {
return props.id + format(props.date, 'yyyymmdd') + props.location;
}
const _save = (props: PrintingProps): Promise<void> => {
const storageKey = _propsToDataString(props);
const storageVal = {
const storageVal: Sample = {
id: props.id,
date: props.date,
location: props.location,
barcodeId: props.barCodeId,
createdAt: props.date,
locationId: props.location,
};
console.log('storageKey', storageKey);
console.log('storageVal', storageVal);
return AsyncStorage.setItem(storageKey, JSON.stringify(storageVal));
return AsyncStorage.setItem(props.id, JSON.stringify(storageVal));
}
const _print = (props: PrintingProps): Promise<void> => {
const dataStr = _propsToDataString(props);
console.log('props.svg', props.svg);
return Print.printAsync({
html: `
<style>
@ -90,13 +64,11 @@ const _print = (props: PrintingProps): Promise<void> => {
}
</style>
<div class="box">
<p>ID#: ${props.id}</p>
<p>ID#: ${props.barCodeId}</p>
<p>Date: ${props.date.toLocaleDateString()} ${props.date.toLocaleTimeString()}</p>
<p>Loc#: ${props.location}</p>
<svg width="190" height="90" id="barCode">
${_renderBarCodeRects(props)}
</svg>
<p>${dataStr}</p>
${props.svg}
<p>${props.id}</p>
</div>
`,
});
@ -142,11 +114,15 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
}
}
return <View style={styles.container}>
<View style={styles.preview}>
<BarCodeDisplay id={props.id} date={props.date} location={props.location} />
<BarCodeDisplay id={props.id} date={props.date} location={props.location} />
<BarCodeDisplay
id={props.id}
barCodeId={props.barCodeId}
date={props.date}
location={props.location}
svg={props.svg}
/>
</View>
<View style={styles.container}>
<Title style={styles.heading}>{statusStr}</Title>
@ -165,11 +141,11 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
export const BarCodeDisplay = (props: BarCodeProps): ReactElement => {
const data = _propsToDataString(props);
console.log('BarCodeDisplay props.svg', props.svg);
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>
<Barcode width={1.1} height={40} text={data} value={data} format={'CODE128'}/>
<QRCode value={props.id} />
</View>;
}

View File

@ -11,8 +11,10 @@ export interface StateProps extends ElementProps {
export interface BarCodeProps extends ElementProps {
id: string;
barCodeId: string;
date: Date;
location: string;
svg: any;
}
export interface ButtonProps extends ElementProps {

6
models/Sample.tsx Normal file
View File

@ -0,0 +1,6 @@
export interface Sample {
id: string;
barcodeId: string;
locationId: string;
createdAt: Date;
}

980
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,17 +15,18 @@
"expo-splash-screen": "^0.5.0",
"expo-status-bar": "^1.0.2",
"expo-updates": "~0.2.10",
"firebase": "7.9.0",
"jsbarcode": "^3.11.0",
"react": "~16.11.0",
"react-dom": "~16.11.0",
"react-native": "~0.62.2",
"react-native-barcode-builder": "github:cdesch/react-native-barcode-builder#master",
"react-native-canvas": "^0.1.37",
"react-native-easy-grid": "^0.2.2",
"react-native-gesture-handler": "~1.6.1",
"react-native-html-to-pdf": "^0.8.0",
"react-native-paper": "^4.1.0",
"react-native-print": "^0.6.0",
"react-native-qrcode-svg": "^6.0.6",
"react-native-reanimated": "~1.9.0",
"react-native-screens": "~2.9.0",
"react-native-share": "^3.7.0",

910
yarn.lock

File diff suppressed because it is too large Load Diff