Skip to content

Commit

Permalink
Support for ICAO's Visible Digital Seals
Browse files Browse the repository at this point in the history
  • Loading branch information
vitorpamplona committed Oct 5, 2021
1 parent 0d07864 commit 9619941
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 1 deletion.
164 changes: 164 additions & 0 deletions app/components/VDSTestCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React, {Component} from 'react';
import { View, Image, Button, FlatList, TouchableOpacity } from 'react-native';
import { Text, Divider } from 'react-native-elements';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';

import { CardStyles as styles } from '../themes/CardStyles'

import Moment from 'moment';

const DISEASE = {
"RA01":"COVID-19",
"RA01.0":"COVID-19"
};

const VACCINE_TYPES = {
"XM68M6": "Unspecified",
"XM1NL1": "Inactivated virus",
"XM5DF6": "Live attenuated virus", 
"XM9QW8": "Non-replicating viral vector", 
"XM0CX4": "Replicating viral vector",
"XM5JC5": "Virus protein subunit",
"XM1J92": "Virus-like particle (VLP)", 
"XM6AT1": "DNA based", 
"XM0GQ8": "RNA based"
}



export default class VDSTestCard extends Component {

showQR = (card) => {
this.props.navigation.navigate({name: 'QRShow', params: {
qr: card.rawQR,
title: this.formatPerson(),
detail: this.formatCI(),
signedBy: this.formatSignedBy()
}
});
}

cert = () => {
return this.props.detail.cert ? this.props.detail.cert : this.props.detail;
}

formatDoB = () => {
if (this.cert().data.msg.pid.dob === undefined || this.cert().data.msg.pid.dob === "") return "";
return "DoB: " + Moment(this.cert().data.msg.pid.dob).format('MMM DD, YYYY')
}

formatCI = () => {
return "ID: " + this.cert().data.msg.pid.i;
}

formatUVCI = () => {
return "Vax ID: " + this.cert().data.msg.uvci;
}

formatExpiresOn = () => {
if (this.cert().exp === undefined || this.cert().exp === "") return "";
return Moment(this.cert().exp*1000).format('MMM DD, YYYY')
}

formatPerson = () => {
if (this.cert().data.msg.pid.n) {
let name = this.cert().data.msg.pid.n.replace(" ", ", ");
names = name.split(" ");
for (i=2; i<names.length; i++) {
names[i] = names[i][0];
}
return names.join(" ");
} else
return "Unkown";
}

formatSignedBy = () => {
let line = "Signed by ";
if (this.cert().data.hdr.is)
line += this.cert().data.hdr.is;
else
line += this.props.detail.pub_key.toLowerCase();

return line;
}

renderCard = () => {
return (
<View style={[styles.card, {backgroundColor:this.props.colors.primary}]}>
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center'}}>
<Text style={styles.notes}>{Moment(this.props.detail.scanDate).format('MMM DD, hh:mma')} - Vaccination</Text>
<FontAwesome5 style={styles.button} name={'trash'} onPress={() => this.props.removeItem(this.props.detail.signature)} solid/>
</View>

<View style={styles.row}>
<Text style={styles.title}>{this.formatPerson()}</Text>
</View>

<View style={styles.row}>
<Text style={styles.notes}>{this.formatDoB()}. {this.formatCI()}</Text>
</View>

<View style={styles.row}>
<Text style={styles.notes}>{this.formatUVCI()}</Text>
</View>

<Divider style={[styles.divisor, {borderBottomColor:this.props.colors.cardText}]} />

<FlatList
listKey={this.props.detail.signature+"ve"}
data={this.cert().data.msg.ve}
keyExtractor={item => (this.props.detail.signature+item.des)}
renderItem={({item}) => {
return (
<View style={styles.groupLine}>

<FlatList
listKey={this.props.detail.signature+item.des+"vd"}
data={item.vd}
keyExtractor={subitem => (this.props.detail.signature+item.des+subitem.dvc)}
renderItem={ (subitem) => {
console.log(subitem);
return (
<View style={styles.groupLine}>

<View style={{alignItems: 'center'}}>
<Text style={styles.subtitle}>{DISEASE[item.dis]} Vaccine {subitem.item.seq}/{item.vd.length}</Text>
</View>

<View style={{alignItems: 'center'}}>
<Text style={styles.notes}>{item.nam} (#{subitem.item.lot})</Text>
</View>

<View style={{alignItems: 'center'}}>
<Text style={styles.notes}>Date: {subitem.item.dvc}</Text>
</View>

<View style={{alignItems: 'center'}}>
<Text style={styles.notes}>Location: {subitem.item.adm}, {subitem.item.ctr}</Text>
</View>

<Divider style={[styles.divisor, {borderBottomColor:this.props.colors.cardText}]} />
</View>
)
}} />
</View>
)
}} />

<View style={{flexDirection:'row', alignItems: 'center', paddingRight: 10}}>
<FontAwesome5 style={styles.icon} name={'check-circle'} solid/>
<Text style={styles.notes}>{this.formatSignedBy()}</Text>
</View>
</View>
);
}


render() {
return this.props.pressable ?
( <TouchableOpacity onPress={() => this.showQR(this.props.detail)}>
{this.renderCard()}
</TouchableOpacity>
) : this.renderCard();
}
}
163 changes: 163 additions & 0 deletions app/components/VDSVaxCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import React, {Component} from 'react';
import { View, Image, Button, FlatList, TouchableOpacity } from 'react-native';
import { Text, Divider } from 'react-native-elements';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';

import { CardStyles as styles } from '../themes/CardStyles'

import Moment from 'moment';

const DISEASE = {
"RA01":"COVID-19",
"RA01.0":"COVID-19"
};

const VACCINE_TYPES = {
"XM68M6": "Unspecified",
"XM1NL1": "Inactivated virus",
"XM5DF6": "Live attenuated virus", 
"XM9QW8": "Non-replicating viral vector", 
"XM0CX4": "Replicating viral vector",
"XM5JC5": "Virus protein subunit",
"XM1J92": "Virus-like particle (VLP)", 
"XM6AT1": "DNA based", 
"XM0GQ8": "RNA based"
}



export default class VDSVaxCard extends Component {

showQR = (card) => {
this.props.navigation.navigate({name: 'QRShow', params: {
qr: card.rawQR,
title: this.formatPerson(),
detail: this.formatCI(),
signedBy: this.formatSignedBy()
}
});
}

cert = () => {
return this.props.detail.cert ? this.props.detail.cert : this.props.detail;
}

formatDoB = () => {
if (this.cert().data.msg.pid.dob === undefined || this.cert().data.msg.pid.dob === "") return "";
return "DoB: " + Moment(this.cert().data.msg.pid.dob).format('MMM DD, YYYY')
}

formatCI = () => {
return "ID: " + this.cert().data.msg.pid.i;
}

formatUVCI = () => {
return "Vax ID: " + this.cert().data.msg.uvci;
}

formatExpiresOn = () => {
if (this.cert().exp === undefined || this.cert().exp === "") return "";
return Moment(this.cert().exp*1000).format('MMM DD, YYYY')
}

formatPerson = () => {
if (this.cert().data.msg.pid.n) {
let name = this.cert().data.msg.pid.n.replace(" ", ", ");
names = name.split(" ");
for (i=2; i<names.length; i++) {
names[i] = names[i][0];
}
return names.join(" ");
} else
return "Unkown";
}

formatSignedBy = () => {
let line = "Signed by ";
if (this.cert().data.hdr.is)
line += this.cert().data.hdr.is;
else
line += this.props.detail.pub_key.toLowerCase();

return line;
}

renderCard = () => {
return (
<View style={[styles.card, {backgroundColor:this.props.colors.primary}]}>
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center'}}>
<Text style={styles.notes}>{Moment(this.props.detail.scanDate).format('MMM DD, hh:mma')} - Vaccination</Text>
<FontAwesome5 style={styles.button} name={'trash'} onPress={() => this.props.removeItem(this.props.detail.signature)} solid/>
</View>

<View style={styles.row}>
<Text style={styles.title}>{this.formatPerson()}</Text>
</View>

<View style={styles.row}>
<Text style={styles.notes}>{this.formatDoB()}. {this.formatCI()}</Text>
</View>

<View style={styles.row}>
<Text style={styles.notes}>{this.formatUVCI()}</Text>
</View>

<Divider style={[styles.divisor, {borderBottomColor:this.props.colors.cardText}]} />

<FlatList
listKey={this.props.detail.signature+"ve"}
data={this.cert().data.msg.ve}
keyExtractor={item => this.props.detail.signature+item.nam}
renderItem={({item}) => {
return (
<View style={styles.groupLine}>

<FlatList
listKey={this.props.detail.signature+item.nam+"vd"}
data={item.vd}
keyExtractor={subitem => this.props.detail.signature+item.nam+subitem.dvc}
renderItem={ (subitem) => {
return (
<View style={styles.groupLine}>

<View style={{alignItems: 'center'}}>
<Text style={styles.subtitle}>{DISEASE[item.dis]} Vaccine {subitem.item.seq}/{item.vd.length}</Text>
</View>

<View style={{alignItems: 'center'}}>
<Text style={styles.notes}>{item.nam} (#{subitem.item.lot})</Text>
</View>

<View style={{alignItems: 'center'}}>
<Text style={styles.notes}>Date: {subitem.item.dvc}</Text>
</View>

<View style={{alignItems: 'center'}}>
<Text style={styles.notes}>Location: {subitem.item.adm}, {subitem.item.ctr}</Text>
</View>

<Divider style={[styles.divisor, {borderBottomColor:this.props.colors.cardText}]} />
</View>
)
}} />
</View>
)
}} />

<View style={{flexDirection:'row', alignItems: 'center', paddingRight: 10}}>
<FontAwesome5 style={styles.icon} name={'check-circle'} solid/>
<Text style={styles.notes}>{this.formatSignedBy()}</Text>
</View>
</View>
);
}


render() {
return this.props.pressable ?
( <TouchableOpacity onPress={() => this.showQR(this.props.detail)}>
{this.renderCard()}
</TouchableOpacity>
) : this.renderCard();
}
}
6 changes: 6 additions & 0 deletions app/screens/Entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import PassKeyCard from './../components/PassKeyCard';
import SHCCard from './../components/SHCCard';
import DCCCard from './../components/DCCCard';
import DCCUYCard from './../components/DCCUYCard';
import VDSVaxCard from './../components/VDSVaxCard';
import VDSTestCard from './../components/VDSTestCard';

import { listCards, removeCard } from './../utils/StorageManager';

Expand Down Expand Up @@ -121,6 +123,10 @@ function Entry({ navigation }) {
return <View style={styles.listItem}><DCCCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>
if (item.format === "DCC" && item.type === "UY")
return <View style={styles.listItem}><DCCUYCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>
if (item.format === "VDS" && item.type === "icao.vacc")
return <View style={styles.listItem}><VDSVaxCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>
if (item.format === "VDS" && item.type === "icao.test")
return <View style={styles.listItem}><VDSTestCard detail={item} colors={colors} navigation={navigation} removeItem={removeItem} pressable/></View>


}} />
Expand Down
7 changes: 6 additions & 1 deletion app/screens/QRReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {importPCF} from '../utils/ImportPCF';
import {importDivoc} from '../utils/ImportDivoc';
import {importSHC} from '../utils/ImportSHC';
import {importDCC} from '../utils/ImportDCC';
import {importVDS} from '../utils/ImportVDS';

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

Expand Down Expand Up @@ -65,7 +66,11 @@ function QRReader({ navigation }) {
}

if (e.data && e.data.startsWith("{")) {
await checkResult(await importDivoc(e.data));
if (e.data.includes("icao")) {
await checkResult(await importVDS(e.data));
} else {
await checkResult(await importDivoc(e.data));
}
return;
}

Expand Down
4 changes: 4 additions & 0 deletions app/screens/QRResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import PassKeyCard from './../components/PassKeyCard';
import SHCCard from './../components/SHCCard';
import DCCCard from './../components/DCCCard';
import DCCUYCard from './../components/DCCUYCard';
import VDSVAXCard from './../components/VDSVaxCard';
import VDSTESTCard from './../components/VDSTestCard';

import { removeCard } from './../utils/StorageManager';

Expand Down Expand Up @@ -56,6 +58,8 @@ function QRResult({ navigation, route }) {
{ qr.type === "FHIRBundle" && <SHCCard detail={qr} colors={colors} removeItem={removeItem} /> }
{ qr.type === "DCC" && <DCCCard detail={qr} colors={colors} removeItem={removeItem} /> }
{ qr.type === "UY" && <DCCUYCard detail={qr} colors={colors} removeItem={removeItem} /> }
{ qr.type === "icao.vacc" && <VDSVAXCard detail={qr} colors={colors} removeItem={removeItem} /> }
{ qr.type === "icao.test" && <VDSTESTCard detail={qr} colors={colors} removeItem={removeItem} /> }
</View>
<TouchableOpacity
style={[styles.button, {backgroundColor: colors.primary}]}
Expand Down
Loading

0 comments on commit 9619941

Please sign in to comment.