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 0a16a7b4f9
9 changed files with 2053 additions and 111 deletions

67
.gitignore vendored
View File

@ -533,3 +533,70 @@ buck-out/
# Expo # Expo
.expo/* .expo/*
web-build/ 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 {BarCodeEvent, BarCodeScanner, PermissionResponse} from 'expo-barcode-scanner';
import React, {ReactElement, useEffect, useState} from 'react'; // @ts-ignore
import {AppRegistry, SafeAreaView, View} from 'react-native'; 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 { import {
Appbar, Appbar,
Button, DarkTheme, Button,
DefaultTheme, DefaultTheme,
HelperText, HelperText,
Provider as PaperProvider, Provider as PaperProvider,
Snackbar, Subheading, Snackbar,
Subheading,
TextInput, TextInput,
Title Title
} from 'react-native-paper'; } from 'react-native-paper';
import QRCode from 'react-native-qrcode-svg';
import {expo as appExpo} from './app.json'; import {expo as appExpo} from './app.json';
import {CancelButton} from './components/Common'; import {CancelButton} from './components/Common';
import {BarCodeDisplay, PrintButton, PrintingMessage} from './components/Print'; 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 {colors, styles} from './components/Styles';
import {BarcodeScannerAppState} from './models/BarcodeScannerAppState'; import {BarcodeScannerAppState} from './models/BarcodeScannerAppState';
import {ElementProps, StateProps} from './models/ElementProps'; import {ElementProps, StateProps} from './models/ElementProps';
import {Sample} from './models/Sample';
const firebaseConfig = {
apiKey: 'api_key_goes_here',
authDomain: 'auth_domain_goes_here',
databaseURL: 'database_url_goes_here',
projectId: 'project_id_goes_here',
storageBucket: 'storage_bucket_goes_here',
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
]);
const db = firebase.firestore();
const samplesCollection = db.collection('samples');
const theme = { const theme = {
...DefaultTheme, ...DefaultTheme,
@ -26,10 +57,13 @@ const theme = {
export default function Main() { export default function Main() {
const [appState, setAppState] = useState<BarcodeScannerAppState>(BarcodeScannerAppState.INITIAL); const [appState, setAppState] = useState<BarcodeScannerAppState>(BarcodeScannerAppState.INITIAL);
const [barCodeData, setBarCodeData] = useState<string>(''); const [sampleId, setSampleId] = useState<string>('');
const [date, setDate] = useState<Date>(new Date()); const [barCodeId, setBarCodeId] = useState<string>('');
const [sampleDate, setSampleDate] = useState<Date>(new Date());
const [locationStr, setLocationStr] = useState<string>('4321'); const [locationStr, setLocationStr] = useState<string>('4321');
const [svgQrCode, setSvgQrCode] = useState<any>();
const [errorMessage, setErrorMessage] = useState<string>(''); const [errorMessage, setErrorMessage] = useState<string>('');
const [samples, setSamples] = useState<Sample[]>([]);
useEffect(() => { useEffect(() => {
BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => { BarCodeScanner.requestPermissionsAsync().then((value: PermissionResponse) => {
@ -39,9 +73,26 @@ export default function Main() {
setAppState(BarcodeScannerAppState.ERROR); 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 = () => { const _scan = () => {
setErrorMessage(''); setErrorMessage('');
setAppState(BarcodeScannerAppState.SCANNING); setAppState(BarcodeScannerAppState.SCANNING);
@ -51,6 +102,7 @@ export default function Main() {
setAppState(BarcodeScannerAppState.INPUT); setAppState(BarcodeScannerAppState.INPUT);
}; };
const _print = () => setAppState(BarcodeScannerAppState.PRINTING); const _print = () => setAppState(BarcodeScannerAppState.PRINTING);
const _printed = () => setAppState(BarcodeScannerAppState.PRINTED);
const _home = () => setAppState(BarcodeScannerAppState.DEFAULT); const _home = () => setAppState(BarcodeScannerAppState.DEFAULT);
const _settings = () => setAppState(BarcodeScannerAppState.SETTINGS); const _settings = () => setAppState(BarcodeScannerAppState.SETTINGS);
@ -62,15 +114,31 @@ export default function Main() {
const pattern = /^[\d]{14}$|^[\d]{9}$/; const pattern = /^[\d]{14}$|^[\d]{9}$/;
if (pattern.test(barCodeString)) { if (pattern.test(barCodeString)) {
const cardId = e.data.slice(0, 9); const cardId = e.data.slice(0, 9);
setBarCodeData(cardId); setBarCodeId(cardId);
setDate(new Date()); setSampleDate(new Date());
setAppState(BarcodeScannerAppState.SCANNED); 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 { } else {
setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`); setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`);
setAppState(BarcodeScannerAppState.ERROR); 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 { function ErrorMessage(props: ElementProps): ReactElement {
return <View style={styles.fullScreen}> return <View style={styles.fullScreen}>
<View style={styles.container}> <View style={styles.container}>
@ -114,7 +182,8 @@ export default function Main() {
<Title style={styles.headingInverse}>Settings</Title> <Title style={styles.headingInverse}>Settings</Title>
<View style={{marginBottom: 10}}> <View style={{marginBottom: 10}}>
<Subheading style={{color: DefaultTheme.colors.text, marginBottom: 60}}> <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> </Subheading>
<TextInput <TextInput
label="Location #" label="Location #"
@ -157,16 +226,45 @@ export default function Main() {
<InputIdButton onClicked={_inputIdNumber}/> <InputIdButton onClicked={_inputIdNumber}/>
</View>; </View>;
case BarcodeScannerAppState.PRINTED: 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/>; return <SuccessMessage/>;
case BarcodeScannerAppState.PRINTING: case BarcodeScannerAppState.PRINTING:
return <PrintingMessage return <View style={styles.container}>
onCancel={_home} <PrintingMessage
id={barCodeData} onCancel={_printed}
date={date} location={locationStr} id={sampleId}
/>; barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
svg={svgQrCode}
/>
</View>;
case BarcodeScannerAppState.SCANNED: case BarcodeScannerAppState.SCANNED:
return <View style={styles.container}> return <View style={styles.container}>
<BarCodeDisplay id={barCodeData} date={date} location={locationStr}/> <BarCodeDisplay
id={sampleId}
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
svg={svgQrCode}
/>
<ActionButtons/> <ActionButtons/>
</View>; </View>;
case BarcodeScannerAppState.SCANNING: case BarcodeScannerAppState.SCANNING:

View File

@ -1,9 +1,9 @@
import React, {ReactElement, useEffect, useState} from 'react'; import React, {ReactElement, useEffect, useState} from 'react';
import {Text, View} from 'react-native'; import {Text, View} from 'react-native';
// @ts-ignore
import Barcode from 'react-native-barcode-builder';
import {Button, Title} from 'react-native-paper'; import {Button, Title} from 'react-native-paper';
import QRCode from 'react-native-qrcode-svg';
import {BarCodeProps, ButtonProps, PrintingProps} from '../models/ElementProps'; import {BarCodeProps, ButtonProps, PrintingProps} from '../models/ElementProps';
import {Sample} from '../models/Sample';
import {colors, styles} from './Styles'; import {colors, styles} from './Styles';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import * as Print from 'expo-print'; import * as Print from 'expo-print';
@ -15,44 +15,18 @@ enum PrintStatus {
DONE = 'DONE', 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 _save = (props: PrintingProps): Promise<void> => {
const storageKey = _propsToDataString(props); const storageVal: Sample = {
const storageVal = {
id: props.id, id: props.id,
date: props.date, barcodeId: props.barCodeId,
location: props.location, createdAt: props.date,
locationId: props.location,
}; };
console.log('storageKey', storageKey); return AsyncStorage.setItem(props.id, JSON.stringify(storageVal));
console.log('storageVal', storageVal);
return AsyncStorage.setItem(storageKey, JSON.stringify(storageVal));
} }
const _print = (props: PrintingProps): Promise<void> => { const _print = (props: PrintingProps): Promise<void> => {
const dataStr = _propsToDataString(props); console.log('props.svg', props.svg);
return Print.printAsync({ return Print.printAsync({
html: ` html: `
<style> <style>
@ -90,13 +64,11 @@ const _print = (props: PrintingProps): Promise<void> => {
} }
</style> </style>
<div class="box"> <div class="box">
<p>ID#: ${props.id}</p> <p>ID#: ${props.barCodeId}</p>
<p>Date: ${props.date.toLocaleDateString()} ${props.date.toLocaleTimeString()}</p> <p>Date: ${props.date.toLocaleDateString()} ${props.date.toLocaleTimeString()}</p>
<p>Loc#: ${props.location}</p> <p>Loc#: ${props.location}</p>
<svg width="190" height="90" id="barCode"> ${props.svg}
${_renderBarCodeRects(props)} <p>${props.id}</p>
</svg>
<p>${dataStr}</p>
</div> </div>
`, `,
}); });
@ -142,11 +114,15 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
} }
} }
return <View style={styles.container}> return <View style={styles.container}>
<View style={styles.preview}> <View style={styles.preview}>
<BarCodeDisplay id={props.id} date={props.date} location={props.location} /> <BarCodeDisplay
<BarCodeDisplay id={props.id} date={props.date} location={props.location} /> id={props.id}
barCodeId={props.barCodeId}
date={props.date}
location={props.location}
svg={props.svg}
/>
</View> </View>
<View style={styles.container}> <View style={styles.container}>
<Title style={styles.heading}>{statusStr}</Title> <Title style={styles.heading}>{statusStr}</Title>
@ -165,11 +141,11 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
export const BarCodeDisplay = (props: BarCodeProps): ReactElement => { export const BarCodeDisplay = (props: BarCodeProps): ReactElement => {
const data = _propsToDataString(props); console.log('BarCodeDisplay props.svg', props.svg);
return <View style={styles.printPreview}> return <View style={styles.printPreview}>
<Text style={styles.label}>ID#: {props.id}</Text> <Text style={styles.label}>ID#: {props.id}</Text>
<Text style={styles.label}>Date: {props.date.toLocaleDateString()}, {props.date.toLocaleTimeString()}</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}>Location {props.location}</Text>
<Barcode width={1.1} height={40} text={data} value={data} format={'CODE128'}/> <QRCode value={props.id} />
</View>; </View>;
} }

View File

@ -11,8 +11,10 @@ export interface StateProps extends ElementProps {
export interface BarCodeProps extends ElementProps { export interface BarCodeProps extends ElementProps {
id: string; id: string;
barCodeId: string;
date: Date; date: Date;
location: string; location: string;
svg: any;
} }
export interface ButtonProps extends ElementProps { 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-splash-screen": "^0.5.0",
"expo-status-bar": "^1.0.2", "expo-status-bar": "^1.0.2",
"expo-updates": "~0.2.10", "expo-updates": "~0.2.10",
"firebase": "7.9.0",
"jsbarcode": "^3.11.0", "jsbarcode": "^3.11.0",
"react": "~16.11.0", "react": "~16.11.0",
"react-dom": "~16.11.0", "react-dom": "~16.11.0",
"react-native": "~0.62.2", "react-native": "~0.62.2",
"react-native-barcode-builder": "github:cdesch/react-native-barcode-builder#master",
"react-native-canvas": "^0.1.37", "react-native-canvas": "^0.1.37",
"react-native-easy-grid": "^0.2.2", "react-native-easy-grid": "^0.2.2",
"react-native-gesture-handler": "~1.6.1", "react-native-gesture-handler": "~1.6.1",
"react-native-html-to-pdf": "^0.8.0", "react-native-html-to-pdf": "^0.8.0",
"react-native-paper": "^4.1.0", "react-native-paper": "^4.1.0",
"react-native-print": "^0.6.0", "react-native-print": "^0.6.0",
"react-native-qrcode-svg": "^6.0.6",
"react-native-reanimated": "~1.9.0", "react-native-reanimated": "~1.9.0",
"react-native-screens": "~2.9.0", "react-native-screens": "~2.9.0",
"react-native-share": "^3.7.0", "react-native-share": "^3.7.0",

910
yarn.lock

File diff suppressed because it is too large Load Diff