Skip to content

Commit 9497fcb

Browse files
committed
Including SmartHealth Cards
1 parent afc62cd commit 9497fcb

File tree

9 files changed

+532
-1
lines changed

9 files changed

+532
-1
lines changed

app/components/SHCCard.js

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import React, {Component} from 'react';
2+
import { StyleSheet, View, Image, Button, FlatList, TouchableOpacity } from 'react-native';
3+
import { Text, Card, Divider } from 'react-native-elements';
4+
import AsyncStorage from '@react-native-async-storage/async-storage';
5+
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
6+
7+
import Moment from 'moment';
8+
9+
import { CardStyles as styles } from '../themes/CardStyles'
10+
11+
const VACCINE_CODES = {
12+
54 :'adenovirus, type 4',
13+
55 :'adenovirus, type 7',
14+
82 :'adenovirus, unspecified formulation',
15+
24 :'anthrax',
16+
19 :'BCG',
17+
27 :'botulinum antitoxin',
18+
26 :'cholera, unspecified formulation',
19+
29 :'CMVIG',
20+
56 :'dengue fever',
21+
12 :'diphtheria antitoxin',
22+
28 :'DT (pediatric)',
23+
20 :'DTaP',
24+
106 :'DTaP, 5 pertussis antigens',
25+
107 :'DTaP, unspecified formulation',
26+
110 :'DTaP-Hep B-IPV',
27+
50 :'DTaP-Hib',
28+
120 :'DTaP-Hib-IPV',
29+
130 :'DTaP-IPV',
30+
1 :'DTP',
31+
22 :'DTP-Hib',
32+
102 :'DTP-Hib-Hep B',
33+
57 :'hantavirus',
34+
52 :'Hep A, adult',
35+
83 :'Hep A, ped/adol, 2 dose',
36+
84 :'Hep A, ped/adol, 3 dose',
37+
31 :'Hep A, pediatric, unspecified formulation',
38+
85 :'Hep A, unspecified formulation',
39+
104 :'Hep A-Hep B',
40+
30 :'HBIG',
41+
8 :'Hep B, adolescent or pediatric',
42+
42 :'Hep B, adolescent/high risk infant',
43+
43 :'Hep B, adult',
44+
44 :'Hep B, dialysis',
45+
45 :'Hep B, unspecified formulation',
46+
58 :'Hep C',
47+
59 :'Hep E',
48+
60 :'herpes simplex 2',
49+
46 :'Hib (PRP-D)',
50+
47 :'Hib (HbOC)',
51+
48 :'Hib (PRP-T)',
52+
49 :'Hib (PRP-OMP)',
53+
17 :'Hib, unspecified formulation',
54+
51 :'Hib-Hep B',
55+
61 :'HIV',
56+
118 :'HPV, bivalent',
57+
62 :'HPV, quadrivalent',
58+
86 :'IG',
59+
87 :'IGIV',
60+
14 :'IG, unspecified formulation',
61+
111 :'influenza, live, intranasal',
62+
15 :'influenza, split (incl. purified surface antigen)',
63+
16 :'influenza, whole',
64+
88 :'influenza, unspecified formulation',
65+
123 :'influenza, H5N1-1203',
66+
10 :'IPV',
67+
2 :'OPV',
68+
89 :'polio, unspecified formulation',
69+
39 :'Japanese encephalitis SC',
70+
63 :'Junin virus',
71+
64 :'leishmaniasis',
72+
65 :'leprosy',
73+
66 :'Lyme disease',
74+
3 :'MMR',
75+
4 :'M/R',
76+
94 :'MMRV',
77+
67 :'malaria',
78+
5 :'measles',
79+
68 :'melanoma',
80+
32 :'meningococcal MPSV4',
81+
103 :'meningococcal C conjugate',
82+
114 :'meningococcal MCV4P',
83+
108 :'meningococcal ACWY, unspecified formulation',
84+
7 :'mumps',
85+
69 :'parainfluenza-3',
86+
11 :'pertussis',
87+
23 :'plague',
88+
33 :'pneumococcal polysaccharide PPV23',
89+
100 :'pneumococcal conjugate PCV 7',
90+
109 :'pneumococcal, unspecified formulation',
91+
70 :'Q fever',
92+
18 :'rabies, intramuscular injection',
93+
40 :'rabies, intradermal injection',
94+
90 :'rabies, unspecified formulation',
95+
72 :'rheumatic fever',
96+
73 :'Rift Valley fever',
97+
34 :'RIG',
98+
119 :'rotavirus, monovalent',
99+
122 :'rotavirus, unspecified formulation',
100+
116 :'rotavirus, pentavalent',
101+
74 :'rotavirus, tetravalent',
102+
71 :'RSV-IGIV',
103+
93 :'RSV-MAb',
104+
6 :'rubella',
105+
38 :'rubella/mumps',
106+
76 :'Staphylococcus bacterio lysate',
107+
113 :'Td (adult), 5 Lf tetanus toxoid, preservative free, adsorbed',
108+
9 :'Td (adult), 2 Lf tetanus toxoid, preservative free, adsorbed',
109+
115 :'Tdap',
110+
35 :'tetanus toxoid, adsorbed',
111+
112 :'tetanus toxoid, unspecified formulation',
112+
77 :'tick-borne encephalitis',
113+
13 :'TIG',
114+
95 :'TST-OT tine test',
115+
96 :'TST-PPD intradermal',
116+
97 :'TST-PPD tine test',
117+
98 :'TST, unspecified formulation',
118+
78 :'tularemia vaccine',
119+
91 :'typhoid, unspecified formulation',
120+
25 :'typhoid, oral',
121+
41 :'typhoid, parenteral',
122+
53 :'typhoid, parenteral, AKD (U.S. military)',
123+
101 :'typhoid, ViCPs',
124+
75 :'vaccinia (smallpox)',
125+
105 :'vaccinia (smallpox) diluted',
126+
79 :'vaccinia immune globulin',
127+
21 :'varicella',
128+
81 :'VEE, inactivated',
129+
80 :'VEE, live',
130+
92 :'VEE, unspecified formulation',
131+
36 :'VZIG',
132+
117 :'VZIG (IND)',
133+
37 :'yellow fever',
134+
121 :'zoster live',
135+
998 :'no vaccine administered',
136+
999 :'unknown',
137+
99 :'RESERVED - do not use',
138+
133 :'Pneumococcal conjugate PCV 13',
139+
134 :'Japanese Encephalitis IM',
140+
137 :'HPV, unspecified formulation',
141+
136 :'Meningococcal MCV4O',
142+
135 :'Influenza, high dose seasonal',
143+
131 :'typhus, historical',
144+
132 :'DTaP-IPV-HIB-HEP B, historical',
145+
128 :'Novel Influenza-H1N1-09, all formulations',
146+
125 :'Novel Influenza-H1N1-09, nasal',
147+
126 :'Novel influenza-H1N1-09, preservative-free',
148+
127 :'Novel influenza-H1N1-09',
149+
138 :'Td (adult)',
150+
139 :'Td(adult) unspecified formulation',
151+
140 :'Influenza, seasonal, injectable, preservative free',
152+
129 :'Japanese Encephalitis, unspecified formulation',
153+
141 :'Influenza, seasonal, injectable',
154+
142 :'tetanus toxoid, not adsorbed',
155+
143 :'Adenovirus types 4 and 7',
156+
144 :'influenza, seasonal, intradermal, preservative free',
157+
145 :'RSV-MAb (new)',
158+
146 :'DTaP,IPV,Hib,HepB',
159+
147 :'meningococcal MCV4, unspecified formulation',
160+
148 :'Meningococcal C/Y-HIB PRP',
161+
149 :'influenza, live, intranasal, quadrivalent',
162+
150 :'influenza, injectable, quadrivalent, preservative free',
163+
151 :'influenza nasal, unspecified formulation',
164+
152 :'Pneumococcal Conjugate, unspecified formulation',
165+
153 :'Influenza, injectable, MDCK, preservative free',
166+
154 :'Hep A, IG',
167+
155 :'influenza, recombinant, injectable, preservative free',
168+
156 :'Rho(D)-IG',
169+
157 :'Rho(D) -IG IM',
170+
158 :'influenza, injectable, quadrivalent',
171+
159 :'Rho(D) - Unspecified formulation',
172+
160 :'Influenza A monovalent (H5N1), ADJUVANTED-2013',
173+
801 :'AS03 Adjuvant',
174+
161 :'Influenza, injectable,quadrivalent, preservative free, pediatric',
175+
162 :'meningococcal B, recombinant',
176+
163 :'meningococcal B, OMV',
177+
164 :'meningococcal B, unspecified',
178+
165 :'HPV9',
179+
166 :'influenza, intradermal, quadrivalent, preservative free',
180+
167 :'meningococcal, unknown serogroups',
181+
168 :'influenza, trivalent, adjuvanted',
182+
169 :'Hep A, live attenuated',
183+
170 :'DTAP/IPV/HIB - non-US',
184+
171 :'Influenza, injectable, MDCK, preservative free, quadrivalent',
185+
172 :'cholera, WC-rBS',
186+
173 :'cholera, BivWC',
187+
174 :'cholera, live attenuated',
188+
175 :'Rabies - IM Diploid cell culture',
189+
176 :'Rabies - IM fibroblast culture',
190+
177 :'PCV10',
191+
178 :'OPV bivalent',
192+
179 :'OPV ,monovalent, unspecified',
193+
180 :'tetanus immune globulin',
194+
181 :'anthrax immune globulin',
195+
182 :'OPV, Unspecified',
196+
183 :'Yellow fever vaccine - alt',
197+
184 :'Yellow fever, unspecified formulation',
198+
185 :'influenza, recombinant, quadrivalent,injectable, preservative free',
199+
186 :'Influenza, injectable, MDCK, quadrivalent, preservative',
200+
187 :'zoster recombinant',
201+
188 :'zoster, unspecified formulation',
202+
189 :'HepB-CpG',
203+
190 :'Typhoid conjugate vaccine (TCV)',
204+
191 :'meningococcal A polysaccharide (non-US)',
205+
192 :'meningococcal AC polysaccharide (non-US)',
206+
193 :'Hep A-Hep B, pediatric/adolescent',
207+
194 :'Influenza, Southern Hemisphere',
208+
195 :'DT, IPV adsorbed',
209+
196 :'Td, adsorbed, preservative free, adult use, Lf unspecified',
210+
197 :'influenza, high-dose, quadrivalent',
211+
200 :'influenza, Southern Hemisphere, pediatric, preservative free',
212+
201 :'influenza, Southern Hemisphere, preservative free',
213+
202 :'influenza, Southern Hemisphere, quadrivalent, with preservative',
214+
198 :'DTP-hepB-Hib Pentavalent Non-US',
215+
203 :'meningococcal polysaccharide (groups A, C, Y, W-135) TT conjugate',
216+
205 :'Influenza vaccine, quadrivalent, adjuvanted',
217+
206 :'Smallpox monkeypox vaccine (National Stockpile)',
218+
207 :'COVID-19, mRNA, LNP-S, PF, 100 mcg/0.5 mL dose',
219+
208 :'COVID-19, mRNA, LNP-S, PF, 30 mcg/0.3 mL dose',
220+
213 :'SARS-COV-2 (COVID-19) vaccine, UNSPECIFIED',
221+
210 :'COVID-19 vaccine, vector-nr, rS-ChAdOx1, PF, 0.5 mL ',
222+
212 :'COVID-19 vaccine, vector-nr, rS-Ad26, PF, 0.5 mL',
223+
204 :'Ebola Zaire vaccine, live, recombinant, 1mL dose',
224+
214 :'Ebola, unspecified',
225+
211 :'COVID-19 vaccine, Subunit, rS-nanoparticle+Matrix-M1 Adjuvant, PF, 0.5 mL'
226+
}
227+
228+
229+
export default class SHCCard extends Component {
230+
231+
patientRecords = () => {
232+
return this.props.detail.cert.vc.credentialSubject.fhirBundle.entry.filter(entry => entry.resource.resourceType === "Patient");
233+
}
234+
235+
otherRecords = () => {
236+
return this.props.detail.cert.vc.credentialSubject.fhirBundle.entry.filter(entry => entry.resource.resourceType !== "Patient");
237+
}
238+
239+
patientName = () => {
240+
let pat = this.patientRecords();
241+
return pat[0].resource.name[0].given.join(' ') + " " + pat[0].resource.name[0].family
242+
}
243+
244+
patientDob = () => {
245+
let pat = this.patientRecords();
246+
return new Date(pat[0].resource.birthDate);
247+
}
248+
249+
issuerName = (card) => {
250+
if (card.pub_key.toLowerCase() === "https://myvaccinerecord.cdph.ca.gov/creds")
251+
return "State of California"
252+
return card.pub_key.toLowerCase();
253+
}
254+
255+
issuedAt = (card) => {
256+
return Moment(new Date(parseInt(card.cert.nbf)*1000)).format('MMM DD, YYYY');
257+
}
258+
259+
showQR = (card) => {
260+
this.props.navigation.navigate({name: 'QRShow', params: {
261+
qr: card.rawQR,
262+
title: this.patientName(),
263+
detail: "DoB: " + Moment(this.patientDob()).format('MMM DD, YYYY'),
264+
signedBy: this.issuerName(card) + " on " + this.issuedAt(card)
265+
}
266+
});
267+
}
268+
269+
renderCard = () => {
270+
return (
271+
<View style={[styles.card, {backgroundColor:this.props.colors.primary}]}>
272+
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center'}}>
273+
<Text style={styles.notes}>{Moment(this.props.detail.scanDate).format('MMM DD, hh:mma')} - Vaccine Record</Text>
274+
<FontAwesome5 style={styles.button} name={'trash'} onPress={() => this.props.removeItem(this.props.detail.signature)} solid/>
275+
</View>
276+
277+
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center'}}>
278+
<Text style={styles.title}>{this.patientName()}</Text>
279+
</View>
280+
281+
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center'}}>
282+
<Text style={styles.notes}>DoB: {Moment(this.patientDob()).format('MMM DD, YYYY')}</Text>
283+
</View>
284+
285+
<Divider style={[styles.divisor, {borderBottomColor:this.props.colors.cardText}]} />
286+
287+
<FlatList
288+
data={this.otherRecords()}
289+
keyExtractor={item => item.fullUrl}
290+
renderItem={({item}) => {
291+
return (
292+
<View style={styles.groupLine}>
293+
<View style={styles.row}>
294+
<Text style={styles.notes}>{item.resource.resourceType} at {item.resource.occurrenceDateTime}</Text>
295+
</View>
296+
297+
<View style={styles.row}>
298+
<Text style={styles.notes}>
299+
Vaccine: {VACCINE_CODES[item.resource.vaccineCode.coding[0].code]}, Lot #{item.resource.lotNumber}
300+
</Text>
301+
</View>
302+
</View>
303+
)
304+
}} />
305+
306+
<Divider style={[styles.divisor, {borderBottomColor:this.props.colors.cardText}]} />
307+
308+
<View style={{flexDirection:'row', alignItems: 'center'}}>
309+
<FontAwesome5 style={styles.icon} name={'check-circle'} solid/>
310+
<Text style={styles.notes}>{this.issuerName(this.props.detail)} on {this.issuedAt(this.props.detail)}</Text>
311+
</View>
312+
</View>
313+
);
314+
}
315+
316+
317+
render() {
318+
return this.props.pressable ?
319+
( <TouchableOpacity onPress={() => this.showQR(this.props.detail)}>
320+
{this.renderCard()}
321+
</TouchableOpacity>
322+
) : this.renderCard();
323+
}
324+
}

app/screens/Entry.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import VaccineCard from './../components/VaccineCard';
1515
import CouponCard from './../components/CouponCard';
1616
import StatusCard from './../components/StatusCard';
1717
import PassKeyCard from './../components/PassKeyCard';
18+
import SHCCard from './../components/SHCCard';
19+
1820
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
1921
import AsyncStorage from '@react-native-async-storage/async-storage';
2022
import { SearchBar } from 'react-native-elements';
@@ -139,6 +141,8 @@ function Entry({ navigation }) {
139141
return <View style={styles.listItem}><PassKeyCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>
140142
if (item.type === "COWIN")
141143
return <View style={styles.listItem}><CowinCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>
144+
if (item.type === "FHIRBundle")
145+
return <View style={styles.listItem}><SHCCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>
142146
}} />
143147
</View>
144148
</SafeAreaView>

app/screens/QRReader.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {useTheme} from '../themes/ThemeProvider';
77

88
import {importPCF} from '../utils/ImportPCF';
99
import {importDivoc} from '../utils/ImportDivoc';
10+
import {importSHC} from '../utils/ImportSHC';
1011

1112
const screenHeight = Math.round(Dimensions.get('window').height);
1213

@@ -41,6 +42,11 @@ function QRReader({ navigation }) {
4142
return;
4243
}
4344

45+
if (e.data && e.data.startsWith("shc:")) {
46+
await checkResult(await importSHC(e.data));
47+
return;
48+
}
49+
4450
if ((e.data && e.data.startsWith("PK")) || (e.data == null && e.rawData)) {
4551
if (!e.rawData) {
4652
showErrorMessage("Phone/OS is unable to read Binary QRs");
@@ -57,7 +63,7 @@ function QRReader({ navigation }) {
5763
return;
5864
}
5965

60-
showErrorMessage("Not a Health Passport" + e.data);
66+
showErrorMessage("Not a supported QR" + e.data);
6167
return;
6268
}
6369

app/screens/QRResult.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import VaccineCard from './../components/VaccineCard';
1212
import CouponCard from './../components/CouponCard';
1313
import StatusCard from './../components/StatusCard';
1414
import PassKeyCard from './../components/PassKeyCard';
15+
import SHCCard from './../components/SHCCard';
1516

1617
const screenWidth = Math.round(Dimensions.get('window').width)-50;
1718

@@ -42,6 +43,7 @@ function QRResult({ navigation, route }) {
4243
{ qr.type === "STATUS" && <StatusCard detail={qr} colors={colors} /> }
4344
{ qr.type === "PASSKEY" && <PassKeyCard detail={qr} colors={colors} /> }
4445
{ qr.type === "COWIN" && <CowinCard detail={qr} colors={colors} /> }
46+
{ qr.type === "FHIRBundle" && <SHCCard detail={qr} colors={colors} /> }
4547
</View>
4648
<TouchableOpacity
4749
style={[styles.button, {backgroundColor: colors.primary}]}
@@ -85,6 +87,7 @@ const styles = StyleSheet.create({
8587
paddingRight: 25,
8688
},
8789
verifiedPill: {
90+
marginTop: -70,
8891
borderRadius: 100,
8992
marginBottom: 30,
9093
paddingLeft: 15,

0 commit comments

Comments
 (0)