Prints QR Code

Former-commit-id: d6f5be66263cc38ba56f0dd5e268ef0facd80800
This commit is contained in:
Aaron Louie 2020-09-03 18:27:44 -04:00
parent 4e320b6619
commit 7cf182fe11
7 changed files with 215 additions and 65 deletions

21
App.tsx
View File

@ -18,7 +18,6 @@ import {
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';
@ -61,7 +60,6 @@ export default function Main() {
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[]>([]);
@ -114,16 +112,13 @@ export default function Main() {
const pattern = /^[\d]{14}$|^[\d]{9}$/;
if (pattern.test(barCodeString)) {
const cardId = e.data.slice(0, 9);
setBarCodeId(cardId);
setSampleDate(new Date());
setAppState(BarcodeScannerAppState.SCANNED);
setSampleId([barCodeId, format(sampleDate, 'yyyyMMddHHmm'), locationStr].join('-'));
const newSampleDate = new Date();
const newSampleId = [cardId, format(newSampleDate, 'yyyyMMddHHmm'), locationStr].join('-');
console.log('sampleId', sampleId);
new QRCode({value: sampleId, ecl: 'H', getRef: c => {
setSvgQrCode(c);
console.log('svgQrCode', svgQrCode);
}});
setSampleId(newSampleId);
setBarCodeId(cardId);
setSampleDate(newSampleDate);
setAppState(BarcodeScannerAppState.SCANNED);
} else {
setErrorMessage(`The barcode data "${e.data}" is not from a valid ID card.`);
setAppState(BarcodeScannerAppState.ERROR);
@ -196,7 +191,7 @@ export default function Main() {
Location number must be exactly 4 digits. No other characters are allowed.
</HelperText>
<Button
icon="save"
icon="content-save"
mode="contained"
color={colors.primary}
style={{marginBottom: 10}}
@ -253,7 +248,6 @@ export default function Main() {
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
svg={svgQrCode}
/>
</View>;
case BarcodeScannerAppState.SCANNED:
@ -263,7 +257,6 @@ export default function Main() {
barCodeId={barCodeId}
date={sampleDate}
location={locationStr}
svg={svgQrCode}
/>
<ActionButtons/>
</View>;

View File

@ -1,3 +1,6 @@
import AsyncStorage from '@react-native-community/async-storage';
import {format} from 'date-fns';
import * as Print from 'expo-print';
import React, {ReactElement, useEffect, useState} from 'react';
import {Text, View} from 'react-native';
import {Button, Title} from 'react-native-paper';
@ -5,9 +8,8 @@ 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';
import {format} from 'date-fns'
const qrcode = require('qrcode');
enum PrintStatus {
SAVING = 'SAVING',
@ -25,14 +27,25 @@ const _save = (props: PrintingProps): Promise<void> => {
return AsyncStorage.setItem(props.id, JSON.stringify(storageVal));
}
const _print = (props: PrintingProps): Promise<void> => {
console.log('props.svg', props.svg);
const _print = async (props: PrintingProps): Promise<void> => {
const svgString = await qrcode.toString(props.id, {
width: 72, // 20mm
height: 72,
margin: 10,
errorCorrectionLevel: 'high',
type: 'svg',
color: {
light: '#ffffff00',
dark: '#000',
}
});
return Print.printAsync({
html: `
<style>
@media print {
@page {
size: 2in 1.25in;
size: 28.6mm;
margin: 0;
}
@ -41,35 +54,61 @@ const _print = (props: PrintingProps): Promise<void> => {
padding: 0;
}
div.box {
width: 2in;
height: 1.25in;
.circle {
position: absolute;
top: 0;
left: 0;
width: 28.6mm;
height: 28.6mm;
color: #000;
text-align: center;
margin: 0;
padding: 0;
border-radius: 28.6mm;
background-color: transparent;
}
div.box p {
font-size: 10pt;
.circle .date,
.circle .time,
.circle .location,
.circle .barCodeId {
position: absolute;
margin: 0;
padding: 0;
font-size: 6pt;
font-family: monospace;
text-align: center;
line-height: 1;
}
.circle .date { top: 3.5mm; left: 0; width: 100%; }
.circle .time { top: 11mm; left: 1.5mm; width: 4mm; }
.circle .location { top: 11mm; right: 1.5mm; width: 4mm; }
.circle .barCodeId { bottom: 3mm; left: 0; width: 100%; }
svg {
position: absolute;
bottom: 0;
top: 0;
left: 0;
width: 28.6mm;
height: 28.6mm;
}
}
</style>
<div class="box">
<p>ID#: ${props.barCodeId}</p>
<p>Date: ${props.date.toLocaleDateString()} ${props.date.toLocaleTimeString()}</p>
<p>Loc#: ${props.location}</p>
${props.svg}
<p>${props.id}</p>
<div class="circle" />
${svgString}
<div class="date">${format(props.date, 'yyyy-MM-dd')}</div>
<div class="time">
T<br />
${format(props.date, 'HH')}<br />
${format(props.date, 'mm')}
</div>
<div class="location">
L<br />
${props.location.slice(0, 2)}<br />
${props.location.slice(2)}<br />
</div>
<div class="barCodeId">#${props.barCodeId}</div>
`,
});
}
@ -121,12 +160,11 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
barCodeId={props.barCodeId}
date={props.date}
location={props.location}
svg={props.svg}
/>
</View>
<View style={styles.container}>
<Title style={styles.heading}>{statusStr}</Title>
<RetryButton />
<RetryButton/>
<Button
icon="cancel"
mode={printStatus === PrintStatus.DONE ? 'contained' : 'text'}
@ -141,11 +179,10 @@ export const PrintingMessage = (props: PrintingProps): ReactElement => {
export const BarCodeDisplay = (props: BarCodeProps): ReactElement => {
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>
<QRCode value={props.id} />
<QRCode value={props.id}/>
</View>;
}

View File

@ -1,6 +1,6 @@
import {BarCodeEvent, BarCodeScanner} from 'expo-barcode-scanner';
import {BarCodeScanner} from 'expo-barcode-scanner';
import React, {ReactElement, useState} from 'react';
import {View, Text} from 'react-native';
import {Text, View} from 'react-native';
import {Button, DefaultTheme, HelperText, Subheading, TextInput, Title} from 'react-native-paper';
import {ButtonProps, ScannerProps} from '../models/ElementProps';
import {colors, styles} from './Styles';
@ -26,7 +26,7 @@ export const Scanner = (props: ScannerProps): ReactElement => {
/>
</View>
<View style={styles.centerMiddle}>
<View style={styles.captureBox} />
<View style={styles.captureBox}/>
</View>
<Text style={styles.subtitle}>
Hold your ID card up, with the barcode facing the camera. Keep the card in the green box.
@ -50,7 +50,7 @@ export const Scanner = (props: ScannerProps): ReactElement => {
</View>;
}
return <ScanCamera key={componentKey} />;
return <ScanCamera key={componentKey}/>;
};
export const ScanButton = (props: ButtonProps): ReactElement => {
@ -81,7 +81,6 @@ export const IdNumberInput = (props: ScannerProps): ReactElement => {
};
const onSubmit = () => {
console.log('onSubmit inputStr =', inputStr);
props.onScanned({type: '', data: inputStr});
}
@ -89,7 +88,8 @@ export const IdNumberInput = (props: ScannerProps): ReactElement => {
<Title style={styles.headingInverse}>Settings</Title>
<View style={{marginBottom: 10}}>
<Subheading style={{color: DefaultTheme.colors.text, marginBottom: 60}}>
Please double check that you have entered the number correctly. Entering an incorrect ID number will prevent patients from receiving their test results.
Please double check that you have entered the number correctly. Entering an incorrect ID number will prevent
patients from receiving their test results.
</Subheading>
<TextInput
label="ID #"

View File

@ -1,6 +1,5 @@
import {StyleSheet} from 'react-native';
import {DefaultTheme, DarkTheme} from 'react-native-paper';
import {DarkTheme, DefaultTheme} from 'react-native-paper';
export const colors = {
...DarkTheme.colors,

View File

@ -14,7 +14,6 @@ export interface BarCodeProps extends ElementProps {
barCodeId: string;
date: Date;
location: string;
svg: any;
}
export interface ButtonProps extends ElementProps {

158
package-lock.json generated
View File

@ -5953,6 +5953,11 @@
"integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==",
"dev": true
},
"dijkstrajs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs="
},
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@ -13059,6 +13064,131 @@
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qrcode": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
"integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==",
"requires": {
"buffer": "^5.4.3",
"buffer-alloc": "^1.2.0",
"buffer-from": "^1.1.1",
"dijkstrajs": "^1.0.1",
"isarray": "^2.0.1",
"pngjs": "^3.3.0",
"yargs": "^13.2.4"
},
"dependencies": {
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}
},
"yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"requires": {
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"qrcode-svg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/qrcode-svg/-/qrcode-svg-1.1.0.tgz",
"integrity": "sha512-XyQCIXux1zEIA3NPb0AeR8UMYvXZzWEhgdBgBjH9gO7M48H9uoHzviNz8pXw3UzrAcxRRRn9gxHewAVK7bn9qw=="
},
"qs": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
@ -13389,25 +13519,6 @@
"use-subscription": "^1.0.0"
}
},
"react-native-barcode-builder": {
"version": "github:cdesch/react-native-barcode-builder#30d6699c3c7f8ed590fc38cbedbae0a5b73b6008",
"from": "github:cdesch/react-native-barcode-builder#master",
"requires": {
"jsbarcode": "^3.8.0",
"react-native-svg": "^9.13.3"
},
"dependencies": {
"react-native-svg": {
"version": "9.13.6",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-9.13.6.tgz",
"integrity": "sha512-vjjuJhEhQCwWjqsgWyGy6/C/LIBM2REDxB40FU1PMhi8T3zQUwUHnA6M15pJKlQG8vaZyA+QnLyIVhjtujRgig==",
"requires": {
"css-select": "^2.0.2",
"css-tree": "^1.0.0-alpha.37"
}
}
}
},
"react-native-canvas": {
"version": "0.1.37",
"resolved": "https://registry.npmjs.org/react-native-canvas/-/react-native-canvas-0.1.37.tgz",
@ -13455,6 +13566,15 @@
"resolved": "https://registry.npmjs.org/react-native-print/-/react-native-print-0.6.0.tgz",
"integrity": "sha512-lWGI5JoB/crLRlukuB7FMmfjSOwC8Cia9JHVEFjpnQWGtnRSLY+oRYwgFLzll7XPi7LqUNohFKT+jsRMA/1bRg=="
},
"react-native-qrcode-svg": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/react-native-qrcode-svg/-/react-native-qrcode-svg-6.0.6.tgz",
"integrity": "sha512-b+/teD+xj17VDujJzf956U2+9mX+gKwVJss2aqmhEIyjP7+TVOuE08D3UkzfOCWXE8gppcUTTz5gkY1NXgfwyQ==",
"requires": {
"prop-types": "^15.5.10",
"qrcode": "^1.3.2"
}
},
"react-native-reanimated": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.9.0.tgz",

View File

@ -17,6 +17,8 @@
"expo-updates": "~0.2.10",
"firebase": "7.9.0",
"jsbarcode": "^3.11.0",
"qrcode": "^1.4.4",
"qrcode-svg": "^1.1.0",
"react": "~16.11.0",
"react-dom": "~16.11.0",
"react-native": "~0.62.2",