mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 13:38:01 -05:00
Added Swipe Gesture & details card
This commit is contained in:
@@ -12,12 +12,6 @@ export const DeviceInfoList: React.FC<DeviceInfoProps> = (props) => {
|
||||
label={props.of === 'receiver' ? t('requestedBy') : t('sentBy')}
|
||||
text={props.deviceInfo.deviceName}
|
||||
/>
|
||||
<TextItem divider label={t('name')} text={props.deviceInfo.name} />
|
||||
<TextItem
|
||||
divider
|
||||
label={t('deviceRefNumber')}
|
||||
text={props.deviceInfo.deviceId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,7 +16,8 @@ export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
|
||||
<Overlay
|
||||
isVisible={props.isVisible}
|
||||
overlayStyle={styles.overlay}
|
||||
onBackdropPress={props.onBackdropPress}>
|
||||
onBackdropPress={props.onBackdropPress}
|
||||
onShow={props.onShow}>
|
||||
<Column padding="24" width={Dimensions.get('screen').width * 0.8}>
|
||||
{props.title && (
|
||||
<Text weight="semibold" margin="0 0 12 0">
|
||||
@@ -38,4 +39,5 @@ interface MessageOverlayProps {
|
||||
message?: string;
|
||||
hasProgress?: boolean;
|
||||
onBackdropPress?: () => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
|
||||
303
components/NewVcDetails.tsx
Normal file
303
components/NewVcDetails.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import React from 'react';
|
||||
import * as DateFnsLocale from '../lib/date-fns/locale';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, StyleSheet } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import { VC, CredentialSubject } from '../types/vc';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
import { TextItem } from './ui/TextItem';
|
||||
import { useReceiveVcModal } from '../screens/Request/ReceiveVcModalController';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
successTag: {
|
||||
backgroundColor: Colors.Green,
|
||||
height: 43,
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingLeft: 6,
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 18,
|
||||
margin: 6,
|
||||
// borderRadius: 0,
|
||||
// borderWidth: 0,
|
||||
// marginBottom: 0,
|
||||
},
|
||||
logo: {
|
||||
height: 48,
|
||||
width: 40,
|
||||
marginRight: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 2,
|
||||
padding: 10,
|
||||
},
|
||||
detailes: {
|
||||
width: 290,
|
||||
marginLeft: 110,
|
||||
marginTop: 0,
|
||||
},
|
||||
labelPart: {
|
||||
margin: 0,
|
||||
borderRadius: 0,
|
||||
borderWidth: 0,
|
||||
width: 240,
|
||||
borderBottomWidth: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
const VerifiedIcon: React.FC = () => {
|
||||
return (
|
||||
<Icon
|
||||
name="check-circle"
|
||||
color={Colors.Green}
|
||||
size={14}
|
||||
containerStyle={{ marginStart: 4, bottom: 1 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewVcDetails: React.FC<VcDetailsProps> = (props) => {
|
||||
const { t, i18n } = useTranslation('VcDetails');
|
||||
const controller = useReceiveVcModal();
|
||||
|
||||
return (
|
||||
<Column fill>
|
||||
<Row style={styles.successTag}>
|
||||
<Icon name="check-circle" color={Colors.White} size={40} />
|
||||
<Text margin="0 10" color={Colors.White}>
|
||||
{controller.vcLabel.singular} Received
|
||||
</Text>
|
||||
</Row>
|
||||
|
||||
<Column>
|
||||
<Column style={styles.header}>
|
||||
<Column>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('fullName')}
|
||||
</Text>
|
||||
<Text weight="bold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.fullName
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
<Image
|
||||
source={require('../assets/mosip-logo.png')}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Row style={styles.container}>
|
||||
<ListItem bottomDivider>
|
||||
<ListItem.Content>
|
||||
<ListItem.Subtitle>{t('photo')}</ListItem.Subtitle>
|
||||
<ListItem.Content>
|
||||
<Image
|
||||
source={
|
||||
props.vc?.credential.biometrics?.face
|
||||
? { uri: props.vc?.credential.biometrics.face }
|
||||
: require('../assets/placeholder-photo.png')
|
||||
}
|
||||
style={{
|
||||
width: 128,
|
||||
height: 180,
|
||||
resizeMode: 'cover',
|
||||
borderRadius: 5,
|
||||
marginTop: -25,
|
||||
marginLeft: -14,
|
||||
padding: 10,
|
||||
}}
|
||||
/>
|
||||
</ListItem.Content>
|
||||
</ListItem.Content>
|
||||
</ListItem>
|
||||
|
||||
<Column style={styles.detailes}>
|
||||
<Column style={styles.labelPart}>
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text
|
||||
size="smaller"
|
||||
color={Colors.Orange}
|
||||
style={{ textTransform: 'uppercase', fontWeight: 'bold' }}>
|
||||
{props.vc?.idType}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{props.vc?.id}
|
||||
</Text>
|
||||
</Column>
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('generatedOn')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{new Date(props.vc?.generatedOn).toLocaleDateString()}
|
||||
</Text>
|
||||
</Column>
|
||||
<Column fill padding="12 16" style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('status')}
|
||||
</Text>
|
||||
<Row>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{t('valid')}
|
||||
</Text>
|
||||
{props.vc?.isVerified && <VerifiedIcon />}
|
||||
</Row>
|
||||
</Column>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('gender')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.gender
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('dateOfBirth')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.dateOfBirth
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('phoneNumber')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.phone
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('email')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.email
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('address')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getFullAddress(
|
||||
props.vc?.verifiableCredential.credentialSubject
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
{props.vc?.reason?.length > 0 && (
|
||||
<Text margin="24 24 16 24" weight="semibold">
|
||||
{t('reasonForSharing')}
|
||||
</Text>
|
||||
)}
|
||||
{props.vc?.reason?.map((reason, index) => (
|
||||
<TextItem
|
||||
key={index}
|
||||
divider
|
||||
label={formatDistanceToNow(reason.timestamp, {
|
||||
addSuffix: true,
|
||||
locale: DateFnsLocale[i18n.language],
|
||||
})}
|
||||
text={reason.message}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</Row>
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
interface VcDetailsProps {
|
||||
vc: VC;
|
||||
}
|
||||
|
||||
interface LocalizedField {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function getFullAddress(credential: CredentialSubject) {
|
||||
if (!credential) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const fields = [
|
||||
'addressLine1',
|
||||
'addressLine2',
|
||||
'addressLine3',
|
||||
'city',
|
||||
'province',
|
||||
'region',
|
||||
];
|
||||
|
||||
return fields
|
||||
.map((field) => getLocalizedField(credential[field]))
|
||||
.concat(credential.postalCode)
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
function getLocalizedField(rawField: string | LocalizedField) {
|
||||
if (typeof rawField === 'string') {
|
||||
return rawField;
|
||||
}
|
||||
try {
|
||||
const locales: LocalizedField[] = JSON.parse(JSON.stringify(rawField));
|
||||
return locales[0].value;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
21
components/SuccesfullyReceived.tsx
Normal file
21
components/SuccesfullyReceived.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Overlay } from 'react-native-elements';
|
||||
|
||||
export const SuccesfullyReceived: React.FC<MessageOverlayProps> = (props) => {
|
||||
return (
|
||||
<Overlay
|
||||
isVisible={props.isVisible}
|
||||
onBackdropPress={props.onBackdropPress}
|
||||
onShow={props.onShow}></Overlay>
|
||||
);
|
||||
};
|
||||
|
||||
interface MessageOverlayProps {
|
||||
isVisible: boolean;
|
||||
img?: string;
|
||||
title?: string;
|
||||
message?: string;
|
||||
hasProgress?: boolean;
|
||||
onBackdropPress?: () => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
55
components/TimerBasedMessageOverlay.tsx
Normal file
55
components/TimerBasedMessageOverlay.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, StyleSheet } from 'react-native';
|
||||
import { Overlay, LinearProgress } from 'react-native-elements';
|
||||
import { Column, Text } from './ui';
|
||||
import { Colors, elevation } from './ui/styleUtils';
|
||||
import { Icon } from 'react-native-elements';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
...elevation(5),
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
});
|
||||
|
||||
export const TimerBasedMessageOverlay: React.FC<MessageOverlayProps> = (
|
||||
props
|
||||
) => {
|
||||
return (
|
||||
<Overlay
|
||||
isVisible={props.isVisible}
|
||||
overlayStyle={styles.overlay}
|
||||
onBackdropPress={props.onBackdropPress}
|
||||
onShow={props.onShow}>
|
||||
{props.img ? (
|
||||
<Icon name="check-circle" size={100} color="#4af51b" />
|
||||
) : null}
|
||||
|
||||
<Column padding="24" width={Dimensions.get('screen').width * 0.8}>
|
||||
{props.title && (
|
||||
<Text weight="bold" margin="0 0 12 0" style={{ textAlign: 'center' }}>
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
{props.message && (
|
||||
<Text margin="0 0 12 0" style={{ textAlign: 'center' }}>
|
||||
{props.message}
|
||||
</Text>
|
||||
)}
|
||||
{props.hasProgress && (
|
||||
<LinearProgress variant="indeterminate" color={Colors.Orange} />
|
||||
)}
|
||||
</Column>
|
||||
</Overlay>
|
||||
);
|
||||
};
|
||||
|
||||
interface MessageOverlayProps {
|
||||
isVisible: boolean;
|
||||
img?: string;
|
||||
title?: string;
|
||||
message?: string;
|
||||
hasProgress?: boolean;
|
||||
onBackdropPress?: () => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
289
components/UpdatedVcDetails.tsx
Normal file
289
components/UpdatedVcDetails.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import React from 'react';
|
||||
import * as DateFnsLocale from '../lib/date-fns/locale';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, StyleSheet, View } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import { VC, CredentialSubject } from '../types/vc';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
import { TextItem } from './ui/TextItem';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
successTag: {
|
||||
backgroundColor: Colors.Green,
|
||||
height: 43,
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingLeft: 6,
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 18,
|
||||
margin: 6,
|
||||
// borderRadius: 0,
|
||||
// borderWidth: 0,
|
||||
// marginBottom: 0,
|
||||
},
|
||||
logo: {
|
||||
height: 50,
|
||||
width: 40,
|
||||
marginRight: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 2,
|
||||
padding: 10,
|
||||
},
|
||||
detailes: {
|
||||
width: 290,
|
||||
marginLeft: 110,
|
||||
marginTop: 0,
|
||||
},
|
||||
labelPart: {
|
||||
margin: 0,
|
||||
borderRadius: 0,
|
||||
borderWidth: 0,
|
||||
width: 240,
|
||||
borderBottomWidth: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
const VerifiedIcon: React.FC = () => {
|
||||
return (
|
||||
<Icon
|
||||
name="check-circle"
|
||||
color={Colors.Green}
|
||||
size={14}
|
||||
containerStyle={{ marginStart: 4, bottom: 1 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const UpdatedVcDetails: React.FC<VcDetailsProps> = (props) => {
|
||||
const { t, i18n } = useTranslation('VcDetails');
|
||||
|
||||
return (
|
||||
<Column fill>
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('fullName')}
|
||||
</Text>
|
||||
<Text weight="bold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.fullName
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<Image
|
||||
source={require('../assets/mosip-logo.png')}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Row style={styles.container}>
|
||||
<ListItem bottomDivider>
|
||||
<ListItem.Content>
|
||||
<ListItem.Subtitle>{t('photo')}</ListItem.Subtitle>
|
||||
<ListItem.Content>
|
||||
<Image
|
||||
source={
|
||||
props.vc?.credential.biometrics?.face
|
||||
? { uri: props.vc?.credential.biometrics.face }
|
||||
: require('../assets/placeholder-photo.png')
|
||||
}
|
||||
style={{
|
||||
width: 128,
|
||||
height: 180,
|
||||
resizeMode: 'cover',
|
||||
borderRadius: 5,
|
||||
marginTop: -25,
|
||||
marginLeft: -14,
|
||||
padding: 10,
|
||||
}}
|
||||
/>
|
||||
</ListItem.Content>
|
||||
</ListItem.Content>
|
||||
</ListItem>
|
||||
|
||||
<Column style={styles.detailes}>
|
||||
<Column style={styles.labelPart}>
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{props.vc?.idType}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{props.vc?.id}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('generatedOn')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{new Date(props.vc?.generatedOn).toLocaleDateString()}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column fill padding="12 16" style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('status')}
|
||||
</Text>
|
||||
<Row>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{t('valid')}
|
||||
</Text>
|
||||
{props.vc?.isVerified && <VerifiedIcon />}
|
||||
</Row>
|
||||
</Column>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('gender')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.gender
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('dateOfBirth')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.dateOfBirth
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('phoneNumber')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.phone
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('email')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getLocalizedField(
|
||||
props.vc?.verifiableCredential.credentialSubject.email
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column
|
||||
fill
|
||||
padding="12 16"
|
||||
margin="0 16 0 0"
|
||||
style={styles.labelPart}>
|
||||
<Text weight="bold" size="smaller" color={Colors.Orange}>
|
||||
{t('address')}
|
||||
</Text>
|
||||
<Text weight="semibold" size="smaller">
|
||||
{getFullAddress(props.vc?.verifiableCredential.credentialSubject)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
{props.vc?.reason?.length > 0 && (
|
||||
<Text margin="24 24 16 24" weight="semibold">
|
||||
{t('reasonForSharing')}
|
||||
</Text>
|
||||
)}
|
||||
{props.vc?.reason?.map((reason, index) => (
|
||||
<TextItem
|
||||
key={index}
|
||||
divider
|
||||
label={formatDistanceToNow(reason.timestamp, {
|
||||
addSuffix: true,
|
||||
locale: DateFnsLocale[i18n.language],
|
||||
})}
|
||||
text={reason.message}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</Row>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
interface VcDetailsProps {
|
||||
vc: VC;
|
||||
}
|
||||
|
||||
interface LocalizedField {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function getFullAddress(credential: CredentialSubject) {
|
||||
if (!credential) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const fields = [
|
||||
'addressLine1',
|
||||
'addressLine2',
|
||||
'addressLine3',
|
||||
'city',
|
||||
'province',
|
||||
'region',
|
||||
];
|
||||
|
||||
return fields
|
||||
.map((field) => getLocalizedField(credential[field]))
|
||||
.concat(credential.postalCode)
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
function getLocalizedField(rawField: string | LocalizedField) {
|
||||
if (typeof rawField === 'string') {
|
||||
return rawField;
|
||||
}
|
||||
try {
|
||||
const locales: LocalizedField[] = JSON.parse(JSON.stringify(rawField));
|
||||
return locales[0].value;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
211
components/UpdatedVcItem.tsx
Normal file
211
components/UpdatedVcItem.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import React, { useContext, useRef } from 'react';
|
||||
import { useInterpret, useSelector } from '@xstate/react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
import { CheckBox, Icon } from 'react-native-elements';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import {
|
||||
createVcItemMachine,
|
||||
selectVerifiableCredential,
|
||||
selectGeneratedOn,
|
||||
selectTag,
|
||||
selectId,
|
||||
vcItemMachine,
|
||||
} from '../machines/vcItem';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
import { RotatingIcon } from './RotatingIcon';
|
||||
import { GlobalContext } from '../shared/GlobalContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
color: Colors.Black,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
loadingTitle: {
|
||||
color: 'transparent',
|
||||
backgroundColor: Colors.Grey5,
|
||||
borderRadius: 4,
|
||||
},
|
||||
subtitle: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
loadingSubtitle: {
|
||||
backgroundColor: Colors.Grey,
|
||||
borderRadius: 4,
|
||||
},
|
||||
container: {
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
loadingContainer: {
|
||||
backgroundColor: Colors.Grey6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
detailsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
padding: 10,
|
||||
backgroundColor: '#fef5eb',
|
||||
},
|
||||
bgContainer: {
|
||||
backgroundColor: '#fef5eb',
|
||||
borderRadius: 15,
|
||||
marginTop: 10,
|
||||
},
|
||||
logoContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
marginLeft: 300,
|
||||
},
|
||||
});
|
||||
|
||||
const VerifiedIcon: React.FC = () => {
|
||||
return (
|
||||
<Icon
|
||||
name="check-circle"
|
||||
color={Colors.Green}
|
||||
size={14}
|
||||
containerStyle={{ marginStart: 4, bottom: 1 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const UpdatedVcItem: React.FC<VcItemProps> = (props) => {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const { t } = useTranslation('VcDetails');
|
||||
|
||||
const machine = useRef(
|
||||
createVcItemMachine(
|
||||
appService.getSnapshot().context.serviceRefs,
|
||||
props.vcKey
|
||||
)
|
||||
);
|
||||
const service = useInterpret(machine.current);
|
||||
const uin = useSelector(service, selectId);
|
||||
const tag = useSelector(service, selectTag);
|
||||
const verifiableCredential = useSelector(service, selectVerifiableCredential);
|
||||
const generatedOn = useSelector(service, selectGeneratedOn);
|
||||
|
||||
const selectableOrCheck = props.selectable ? (
|
||||
<CheckBox
|
||||
checked={props.selected}
|
||||
checkedIcon={<Icon name="radio-button-checked" />}
|
||||
uncheckedIcon={<Icon name="radio-button-unchecked" />}
|
||||
onPress={() => props.onPress(service)}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => props.onPress(service)}
|
||||
disabled={!verifiableCredential}
|
||||
style={styles.bgContainer}>
|
||||
<Row
|
||||
crossAlign="center"
|
||||
margin={props.margin}
|
||||
backgroundColor={!verifiableCredential ? Colors.Grey6 : Colors.White}
|
||||
style={
|
||||
!verifiableCredential ? styles.loadingContainer : styles.container
|
||||
}>
|
||||
{/* <Column style={styles.logoContainer}>
|
||||
<Logo height={30} />
|
||||
</Column> */}
|
||||
|
||||
<Column fill style={styles.detailsContainer}>
|
||||
<Image
|
||||
source={require('../assets/placeholder-photo.png')}
|
||||
style={{
|
||||
width: 110,
|
||||
height: 110,
|
||||
resizeMode: 'cover',
|
||||
marginTop: 10,
|
||||
}}
|
||||
/>
|
||||
<Column margin="0 0 0 50">
|
||||
<Column>
|
||||
<Text color={Colors.Orange} size="smaller">
|
||||
Full name
|
||||
</Text>
|
||||
<Text weight="bold" size="smaller">
|
||||
{!verifiableCredential
|
||||
? ''
|
||||
: getLocalizedField(
|
||||
verifiableCredential.credentialSubject.fullName
|
||||
)}
|
||||
</Text>
|
||||
</Column>
|
||||
|
||||
<Column>
|
||||
<Text color={Colors.Orange} size="smaller">
|
||||
UIN
|
||||
</Text>
|
||||
<Text
|
||||
weight="bold"
|
||||
size="smaller"
|
||||
style={
|
||||
!verifiableCredential ? styles.loadingTitle : styles.title
|
||||
}>
|
||||
{!verifiableCredential ? '' : tag || uin}
|
||||
</Text>
|
||||
</Column>
|
||||
<Column>
|
||||
<Text color={Colors.Orange} size="smaller">
|
||||
Generated on
|
||||
</Text>
|
||||
<Text
|
||||
numLines={1}
|
||||
weight="bold"
|
||||
size="smaller"
|
||||
style={
|
||||
!verifiableCredential
|
||||
? styles.loadingSubtitle
|
||||
: styles.subtitle
|
||||
}>
|
||||
{!verifiableCredential ? '' : generatedOn}
|
||||
</Text>
|
||||
</Column>
|
||||
<Column>
|
||||
<Text size="smaller" color={Colors.Orange}>
|
||||
{t('status')}
|
||||
</Text>
|
||||
<Row>
|
||||
<Text weight="bold" size="smaller">
|
||||
{t('valid')}
|
||||
</Text>
|
||||
<VerifiedIcon />
|
||||
</Row>
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
{verifiableCredential ? (
|
||||
selectableOrCheck
|
||||
) : (
|
||||
<RotatingIcon name="sync" color={Colors.Grey5} />
|
||||
)}
|
||||
</Row>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
interface VcItemProps {
|
||||
vcKey: string;
|
||||
margin?: string;
|
||||
selectable?: boolean;
|
||||
selected?: boolean;
|
||||
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
|
||||
function getLocalizedField(rawField: string | LocalizedField) {
|
||||
if (typeof rawField === 'string') {
|
||||
return rawField;
|
||||
}
|
||||
try {
|
||||
const locales: LocalizedField[] = JSON.parse(JSON.stringify(rawField));
|
||||
return locales[0].value;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
133
components/displayVcItem.tsx
Normal file
133
components/displayVcItem.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useContext, useRef } from 'react';
|
||||
import { useInterpret, useSelector } from '@xstate/react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
import { CheckBox, Icon } from 'react-native-elements';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import {
|
||||
createVcItemMachine,
|
||||
selectVerifiableCredential,
|
||||
selectGeneratedOn,
|
||||
selectTag,
|
||||
selectId,
|
||||
vcItemMachine,
|
||||
} from '../machines/vcItem';
|
||||
import { Column, Row, Text } from './ui';
|
||||
import { Colors } from './ui/styleUtils';
|
||||
import { RotatingIcon } from './RotatingIcon';
|
||||
import { GlobalContext } from '../shared/GlobalContext';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
color: Colors.Black,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
loadingTitle: {
|
||||
color: 'transparent',
|
||||
backgroundColor: Colors.Grey5,
|
||||
borderRadius: 4,
|
||||
},
|
||||
subtitle: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
loadingSubtitle: {
|
||||
backgroundColor: Colors.Grey,
|
||||
borderRadius: 4,
|
||||
},
|
||||
container: {
|
||||
backgroundColor: Colors.White,
|
||||
},
|
||||
loadingContainer: {
|
||||
backgroundColor: Colors.Grey6,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export const VcItem: React.FC<VcItemProps> = (props) => {
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const machine = useRef(
|
||||
createVcItemMachine(
|
||||
appService.getSnapshot().context.serviceRefs,
|
||||
props.vcKey
|
||||
)
|
||||
);
|
||||
const service = useInterpret(machine.current);
|
||||
const uin = useSelector(service, selectId);
|
||||
const tag = useSelector(service, selectTag);
|
||||
const verifiableCredential = useSelector(service, selectVerifiableCredential);
|
||||
const generatedOn = useSelector(service, selectGeneratedOn);
|
||||
|
||||
const selectableOrCheck = props.selectable ? (
|
||||
<CheckBox
|
||||
checked={props.selected}
|
||||
checkedIcon={<Icon name="radio-button-checked" />}
|
||||
uncheckedIcon={<Icon name="radio-button-unchecked" />}
|
||||
onPress={() => props.onPress(service)}
|
||||
/>
|
||||
) : (
|
||||
<Icon name="chevron-right" />
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => props.onPress(service)}
|
||||
disabled={!verifiableCredential}>
|
||||
<Row
|
||||
elevation={!verifiableCredential ? 0 : 2}
|
||||
crossAlign="center"
|
||||
margin={props.margin}
|
||||
backgroundColor={!verifiableCredential ? Colors.Grey6 : Colors.White}
|
||||
padding="16 24"
|
||||
style={
|
||||
!verifiableCredential ? styles.loadingContainer : styles.container
|
||||
}>
|
||||
<Column fill margin="0 24 0 0">
|
||||
<Text
|
||||
weight="semibold"
|
||||
style={!verifiableCredential ? styles.loadingTitle : styles.title}
|
||||
margin="0 0 6 0">
|
||||
{!verifiableCredential ? '' : tag || uin}
|
||||
</Text>
|
||||
<Text
|
||||
size="smaller"
|
||||
numLines={1}
|
||||
style={
|
||||
!verifiableCredential ? styles.loadingSubtitle : styles.subtitle
|
||||
}>
|
||||
{!verifiableCredential
|
||||
? ''
|
||||
: getLocalizedField(
|
||||
verifiableCredential.credentialSubject.fullName
|
||||
) +
|
||||
' · ' +
|
||||
generatedOn}
|
||||
</Text>
|
||||
</Column>
|
||||
{verifiableCredential ? (
|
||||
selectableOrCheck
|
||||
) : (
|
||||
<RotatingIcon name="sync" color={Colors.Grey5} />
|
||||
)}
|
||||
</Row>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
interface VcItemProps {
|
||||
vcKey: string;
|
||||
margin?: string;
|
||||
selectable?: boolean;
|
||||
selected?: boolean;
|
||||
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
|
||||
}
|
||||
|
||||
function getLocalizedField(rawField: string | LocalizedField) {
|
||||
if (typeof rawField === 'string') {
|
||||
return rawField;
|
||||
}
|
||||
try {
|
||||
const locales: LocalizedField[] = JSON.parse(JSON.stringify(rawField));
|
||||
return locales[0].value;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export const Modal: React.FC<ModalProps> = (props) => {
|
||||
animationType="slide"
|
||||
style={styles.modal}
|
||||
visible={props.isVisible}
|
||||
onShow={props.onShow}
|
||||
onRequestClose={props.onDismiss}>
|
||||
<Column fill>
|
||||
<Row padding="16 32" elevation={props.headerElevation}>
|
||||
@@ -27,16 +28,10 @@ export const Modal: React.FC<ModalProps> = (props) => {
|
||||
color={Colors.Orange}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Row fill align="center">
|
||||
<Text weight="semibold">{props.headerTitle}</Text>
|
||||
</Row>
|
||||
{props.headerRight || (
|
||||
<Icon
|
||||
name="close"
|
||||
onPress={props.onDismiss}
|
||||
color={Colors.Orange}
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
{props.children}
|
||||
</Column>
|
||||
@@ -50,4 +45,5 @@ export interface ModalProps {
|
||||
headerTitle?: string;
|
||||
headerElevation?: ElevationLevel;
|
||||
headerRight?: React.ReactElement;
|
||||
onShow?: () => void;
|
||||
}
|
||||
|
||||
54
components/ui/UpdatedModal.tsx
Normal file
54
components/ui/UpdatedModal.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, Modal as RNModal, StyleSheet } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { Column, Row } from '.';
|
||||
import { Colors, ElevationLevel } from './styleUtils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
width: Dimensions.get('screen').width,
|
||||
height: Dimensions.get('screen').height,
|
||||
},
|
||||
});
|
||||
|
||||
export const Modal: React.FC<ModalProps> = (props) => {
|
||||
return (
|
||||
<RNModal
|
||||
animationType="slide"
|
||||
style={styles.modal}
|
||||
visible={props.isVisible}
|
||||
onRequestClose={props.onDismiss}>
|
||||
<Column fill>
|
||||
<Row padding="16 32" elevation={props.headerElevation}>
|
||||
{props.headerRight ? (
|
||||
<Icon
|
||||
name="chevron-left"
|
||||
onPress={props.onDismiss}
|
||||
color={Colors.Orange}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* <Row fill align="center">
|
||||
<Text weight="semibold">{props.headerTitle}</Text>
|
||||
</Row> */}
|
||||
{/* {props.headerRight || (
|
||||
<Icon //Modal closing Nav-bar part
|
||||
name="close"
|
||||
onPress={props.onDismiss}
|
||||
color={Colors.Orange}
|
||||
/>
|
||||
)} */}
|
||||
</Row>
|
||||
{props.children}
|
||||
</Column>
|
||||
</RNModal>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ModalProps {
|
||||
isVisible: boolean;
|
||||
onDismiss: () => void;
|
||||
headerTitle?: string;
|
||||
headerElevation?: ElevationLevel;
|
||||
headerRight?: React.ReactElement;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ const model = createModel(
|
||||
REJECT: () => ({}),
|
||||
CANCEL: () => ({}),
|
||||
DISMISS: () => ({}),
|
||||
GOBACK: () => ({}),
|
||||
VC_RECEIVED: (vc: VC) => ({ vc }),
|
||||
RESPONSE_SENT: () => ({}),
|
||||
CONNECTED: () => ({}),
|
||||
@@ -207,6 +208,7 @@ export const requestMachine = model.createMachine(
|
||||
},
|
||||
on: {
|
||||
DISMISS: 'navigatingToHome',
|
||||
GOBACK: '#clearingConnection',
|
||||
},
|
||||
},
|
||||
rejected: {
|
||||
@@ -221,6 +223,7 @@ export const requestMachine = model.createMachine(
|
||||
},
|
||||
},
|
||||
navigatingToHome: {},
|
||||
navigatingToTimeBasedRequest: {},
|
||||
},
|
||||
exit: ['disconnect'],
|
||||
},
|
||||
|
||||
@@ -9,12 +9,17 @@ export interface Typegen0 {
|
||||
removeLoggers:
|
||||
| 'SCREEN_BLUR'
|
||||
| 'xstate.after(CLEAR_DELAY)#clearingConnection'
|
||||
| 'DISMISS';
|
||||
| 'DISMISS'
|
||||
| 'GOBACK';
|
||||
disconnect: '';
|
||||
registerLoggers: 'xstate.after(CLEAR_DELAY)#clearingConnection' | 'DISMISS';
|
||||
registerLoggers:
|
||||
| 'xstate.after(CLEAR_DELAY)#clearingConnection'
|
||||
| 'DISMISS'
|
||||
| 'GOBACK';
|
||||
generateConnectionParams:
|
||||
| 'xstate.after(CLEAR_DELAY)#clearingConnection'
|
||||
| 'DISMISS';
|
||||
| 'DISMISS'
|
||||
| 'GOBACK';
|
||||
requestReceiverInfo: 'CONNECTED';
|
||||
requestReceivedVcs: 'xstate.init';
|
||||
requestExistingVc: 'VC_RESPONSE';
|
||||
@@ -84,6 +89,7 @@ export interface Typegen0 {
|
||||
| 'reviewing.accepted'
|
||||
| 'reviewing.rejected'
|
||||
| 'reviewing.navigatingToHome'
|
||||
| 'reviewing.navigatingToTimeBasedRequest'
|
||||
| 'disconnected'
|
||||
| {
|
||||
checkingBluetoothService?: 'checking' | 'requesting' | 'enabled';
|
||||
@@ -93,6 +99,7 @@ export interface Typegen0 {
|
||||
| 'accepted'
|
||||
| 'rejected'
|
||||
| 'navigatingToHome'
|
||||
| 'navigatingToTimeBasedRequest'
|
||||
| {
|
||||
accepting?:
|
||||
| 'requestingReceivedVcs'
|
||||
|
||||
23
package-lock.json
generated
23
package-lock.json
generated
@@ -57,6 +57,7 @@
|
||||
"react-native-securerandom": "^1.0.0",
|
||||
"react-native-simple-markdown": "^1.1.0",
|
||||
"react-native-svg": "12.1.1",
|
||||
"react-native-swipe-gestures": "^1.0.5",
|
||||
"react-native-system-setting": "^1.7.6",
|
||||
"react-native-vector-icons": "^8.1.0",
|
||||
"xstate": "^4.26.0"
|
||||
@@ -10287,9 +10288,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
|
||||
"integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
|
||||
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"original": "^1.0.0"
|
||||
@@ -20458,6 +20459,11 @@
|
||||
"react-native": ">=0.50.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-swipe-gestures": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
"integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw=="
|
||||
},
|
||||
"node_modules/react-native-system-setting": {
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-system-setting/-/react-native-system-setting-1.7.6.tgz",
|
||||
@@ -34785,9 +34791,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"eventsource": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
|
||||
"integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
|
||||
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"original": "^1.0.0"
|
||||
@@ -42870,6 +42876,11 @@
|
||||
"css-tree": "^1.0.0-alpha.39"
|
||||
}
|
||||
},
|
||||
"react-native-swipe-gestures": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
"integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw=="
|
||||
},
|
||||
"react-native-system-setting": {
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-system-setting/-/react-native-system-setting-1.7.6.tgz",
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"react-native-securerandom": "^1.0.0",
|
||||
"react-native-simple-markdown": "^1.1.0",
|
||||
"react-native-svg": "12.1.1",
|
||||
"react-native-swipe-gestures": "^1.0.5",
|
||||
"react-native-system-setting": "^1.7.6",
|
||||
"react-native-vector-icons": "^8.1.0",
|
||||
"xstate": "^4.26.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ProfileScreen } from '../screens/Profile/ProfileScreen';
|
||||
import { RequestScreen } from '../screens/Request/RequestScreen';
|
||||
import { ScanScreen } from '../screens/Scan/ScanScreen';
|
||||
import { RootStackParamList } from './index';
|
||||
import { TimerBasedRequestScreen } from '../screens/Request/TimerBasedRequestScreen';
|
||||
|
||||
export const mainRoutes: TabScreen[] = [
|
||||
{
|
||||
@@ -23,6 +24,11 @@ export const mainRoutes: TabScreen[] = [
|
||||
headerShown: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TimerBasedRequest',
|
||||
component: TimerBasedRequestScreen,
|
||||
icon: 'file-download',
|
||||
},
|
||||
{
|
||||
name: 'Request',
|
||||
component: RequestScreen,
|
||||
@@ -41,6 +47,7 @@ export type MainBottomTabParamList = {
|
||||
};
|
||||
Scan: undefined;
|
||||
Request: undefined;
|
||||
TimerBasedRequest: undefined;
|
||||
Profile: undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import { ViewVcModal } from './ViewVcModal';
|
||||
import { useHomeScreen } from './HomeScreenController';
|
||||
import { TabRef } from './HomeScreenMachine';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import { vcItemMachine } from '../../machines/vcItem';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabIndicator: {
|
||||
@@ -45,14 +47,20 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
|
||||
<MyVcsTab
|
||||
isVisible={controller.activeTab === 0}
|
||||
service={controller.tabRefs.myVcs}
|
||||
vcItemActor={controller.selectedVc}
|
||||
onSwipe={() => props.navigation.navigate('TimerBasedRequest')}
|
||||
/>
|
||||
<ReceivedVcsTab
|
||||
isVisible={controller.activeTab === 1}
|
||||
service={controller.tabRefs.receivedVcs}
|
||||
vcItemActor={controller.selectedVc}
|
||||
onSwipe={() => props.navigation.navigate('TimerBasedRequest')}
|
||||
/>
|
||||
<HistoryTab
|
||||
isVisible={controller.activeTab === 2}
|
||||
vcItemActor={controller.selectedVc}
|
||||
service={controller.tabRefs.history}
|
||||
onSwipe={() => props.navigation.navigate('TimerBasedRequest')}
|
||||
/>
|
||||
</Column>
|
||||
)}
|
||||
@@ -84,4 +92,6 @@ function TabItem(title: string) {
|
||||
export interface HomeScreenTabProps {
|
||||
isVisible: boolean;
|
||||
service: TabRef;
|
||||
onSwipe: () => void;
|
||||
vcItemActor: ActorRefFrom<typeof vcItemMachine>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button, Column, Text, Centered } from '../../components/ui';
|
||||
import { VcItem } from '../../components/VcItem';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { RefreshControl } from 'react-native';
|
||||
@@ -10,6 +9,8 @@ import { AddVcModal } from './MyVcs/AddVcModal';
|
||||
import { DownloadingVcModal } from './MyVcs/DownloadingVcModal';
|
||||
import { OnboardingOverlay } from './OnboardingOverlay';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import GestureRecognizer from 'react-native-swipe-gestures';
|
||||
import { UpdatedVcItem } from '../../components/UpdatedVcItem';
|
||||
|
||||
export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
const { t } = useTranslation('MyVcsTab');
|
||||
@@ -30,7 +31,7 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
/>
|
||||
}>
|
||||
{controller.vcKeys.map((vcKey) => (
|
||||
<VcItem
|
||||
<UpdatedVcItem
|
||||
key={vcKey}
|
||||
vcKey={vcKey}
|
||||
margin="0 2 8 2"
|
||||
@@ -38,16 +39,19 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
<Column elevation={2} margin="0 2">
|
||||
<Button
|
||||
type="clear"
|
||||
disabled={controller.isRefreshingVcs}
|
||||
title={t('addVcButton', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
})}
|
||||
onPress={controller.ADD_VC}
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<GestureRecognizer onSwipeLeft={props.onSwipe}>
|
||||
<Column elevation={2} margin="0 2">
|
||||
<Button
|
||||
type="clear"
|
||||
disabled={controller.isRefreshingVcs}
|
||||
title={t('addVcButton', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
})}
|
||||
onPress={controller.ADD_VC}
|
||||
/>
|
||||
</Column>
|
||||
</GestureRecognizer>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{controller.vcKeys.length === 0 && (
|
||||
@@ -67,13 +71,16 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
color={Colors.Orange}
|
||||
/>
|
||||
</Centered>
|
||||
<Button
|
||||
disabled={controller.isRefreshingVcs}
|
||||
title={t('addVcButton', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
})}
|
||||
onPress={controller.ADD_VC}
|
||||
/>
|
||||
|
||||
<GestureRecognizer onSwipeLeft={props.onSwipe}>
|
||||
<Button
|
||||
disabled={controller.isRefreshingVcs}
|
||||
title={t('addVcButton', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
})}
|
||||
onPress={controller.ADD_VC}
|
||||
/>
|
||||
</GestureRecognizer>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
@@ -4,9 +4,9 @@ import { RefreshControl } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { Centered, Column, Text } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { VcItem } from '../../components/VcItem';
|
||||
import { HomeScreenTabProps } from './HomeScreen';
|
||||
import { useReceivedVcsTab } from './ReceivedVcsTabController';
|
||||
import { UpdatedVcItem } from '../../components/UpdatedVcItem';
|
||||
|
||||
export const ReceivedVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
const { t } = useTranslation('ReceivedVcsTab');
|
||||
@@ -24,7 +24,7 @@ export const ReceivedVcsTab: React.FC<HomeScreenTabProps> = (props) => {
|
||||
/>
|
||||
}>
|
||||
{controller.vcKeys.map((vcKey) => (
|
||||
<VcItem
|
||||
<UpdatedVcItem
|
||||
key={vcKey}
|
||||
vcKey={vcKey}
|
||||
margin="0 2 8 2"
|
||||
|
||||
@@ -4,13 +4,13 @@ import { TextEditOverlay } from '../../components/TextEditOverlay';
|
||||
import { Column } from '../../components/ui';
|
||||
import { Modal } from '../../components/ui/Modal';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { VcDetails } from '../../components/VcDetails';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { ToastItem } from '../../components/ui/ToastItem';
|
||||
import { Passcode } from '../../components/Passcode';
|
||||
import { OtpVerificationModal } from './MyVcs/OtpVerificationModal';
|
||||
import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UpdatedVcDetails } from '../../components/UpdatedVcDetails';
|
||||
|
||||
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
|
||||
const { t } = useTranslation('ViewVcModal');
|
||||
@@ -25,9 +25,9 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
|
||||
headerRight={
|
||||
<Icon name="edit" onPress={controller.EDIT_TAG} color={Colors.Orange} />
|
||||
}>
|
||||
<Column scroll backgroundColor={Colors.LightGrey}>
|
||||
<Column>
|
||||
<VcDetails vc={controller.vc} />
|
||||
<Column scroll>
|
||||
<Column fill backgroundColor={'#fef5eb'}>
|
||||
<UpdatedVcDetails vc={controller.vc} />
|
||||
</Column>
|
||||
</Column>
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
|
||||
</ListItem>
|
||||
<Text
|
||||
weight="semibold"
|
||||
margin="32 0 0 0"
|
||||
margin="32"
|
||||
align="center"
|
||||
size="smaller"
|
||||
color={Colors.Grey}>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import { DeviceInfoList } from '../../components/DeviceInfoList';
|
||||
import { Button, Column, Text } from '../../components/ui';
|
||||
import { Button, Column } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { VcDetails } from '../../components/VcDetails';
|
||||
import { Modal, ModalProps } from '../../components/ui/Modal';
|
||||
import { useReceiveVcModal } from './ReceiveVcModalController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -12,15 +10,14 @@ export const ReceiveVcModal: React.FC<ReceveVcModalProps> = (props) => {
|
||||
const controller = useReceiveVcModal();
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<Modal
|
||||
{...props}
|
||||
onShow={() =>
|
||||
setTimeout(() => {
|
||||
props.onAccept();
|
||||
}, 5000)
|
||||
}>
|
||||
<Column scroll padding="24 0 48 0" backgroundColor={Colors.LightGrey}>
|
||||
<Column>
|
||||
<DeviceInfoList of="sender" deviceInfo={controller.senderInfo} />
|
||||
<Text weight="semibold" margin="24 24 0 24">
|
||||
{t('header', { vcLabel: controller.vcLabel.singular })}
|
||||
</Text>
|
||||
<VcDetails vc={controller.incomingVc} />
|
||||
</Column>
|
||||
<Column padding="0 24" margin="32 0 0 0">
|
||||
<Button
|
||||
title={t('acceptRequest', { vcLabel: controller.vcLabel.singular })}
|
||||
@@ -42,4 +39,5 @@ export const ReceiveVcModal: React.FC<ReceveVcModalProps> = (props) => {
|
||||
interface ReceveVcModalProps extends ModalProps {
|
||||
onAccept: () => void;
|
||||
onReject: () => void;
|
||||
onShow: () => void;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export const RequestScreen: React.FC<MainRouteProps> = (props) => {
|
||||
onDismiss={controller.REJECT}
|
||||
onAccept={controller.ACCEPT}
|
||||
onReject={controller.REJECT}
|
||||
onShow={() => console.log('rec')}
|
||||
headerTitle={t('incomingVc', { vcLabel: controller.vcLabel.singular })}
|
||||
/>
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ export function useRequestScreen({ navigation }: MainRouteProps) {
|
||||
if (state.matches('reviewing.navigatingToHome')) {
|
||||
navigation.navigate('Home', { activeTab: 1 });
|
||||
}
|
||||
if (state.matches('reviewing.navigatingToTimeBasedRequest')) {
|
||||
navigation.navigate('TimerBasedRequest');
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -102,5 +105,6 @@ export function useRequestScreen({ navigation }: MainRouteProps) {
|
||||
ACCEPT: () => requestService.send(RequestEvents.ACCEPT()),
|
||||
REJECT: () => requestService.send(RequestEvents.REJECT()),
|
||||
REQUEST: () => requestService.send(RequestEvents.SCREEN_FOCUS()),
|
||||
GOBACK: () => requestService.send(RequestEvents.GOBACK()),
|
||||
};
|
||||
}
|
||||
|
||||
32
screens/Request/TimerBasedReceiveVcModal.tsx
Normal file
32
screens/Request/TimerBasedReceiveVcModal.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Column } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { Modal, ModalProps } from '../../components/ui/Modal';
|
||||
import { useReceiveVcModal } from './ReceiveVcModalController';
|
||||
import { NewVcDetails } from '../../components/NewVcDetails';
|
||||
|
||||
export const TimerBasedReceiveVcModal: React.FC<ReceveVcModalProps> = (
|
||||
props
|
||||
) => {
|
||||
const controller = useReceiveVcModal();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
onShow={() =>
|
||||
setTimeout(() => {
|
||||
props.onAccept();
|
||||
}, 5000)
|
||||
}>
|
||||
<Column scroll padding="0 0 48 0" backgroundColor={Colors.LightGrey}>
|
||||
<NewVcDetails vc={controller.incomingVc} />
|
||||
</Column>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
interface ReceveVcModalProps extends ModalProps {
|
||||
onAccept: () => void;
|
||||
onReject: () => void;
|
||||
onShow: () => void;
|
||||
}
|
||||
77
screens/Request/TimerBasedRequestScreen.tsx
Normal file
77
screens/Request/TimerBasedRequestScreen.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import { Centered, Column, Text } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { MainRouteProps } from '../../routes/main';
|
||||
import { TimerBasedReceiveVcModal } from './TimerBasedReceiveVcModal';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { useRequestScreen } from './RequestScreenController';
|
||||
import { SuccesfullyReceived } from '../../components/SuccesfullyReceived';
|
||||
|
||||
export const TimerBasedRequestScreen: React.FC<MainRouteProps> = (props) => {
|
||||
const controller = useRequestScreen(props);
|
||||
|
||||
return (
|
||||
<Column fill padding="98 24 24 24" backgroundColor={Colors.LightGrey}>
|
||||
<Column>
|
||||
{controller.isBluetoothDenied ? (
|
||||
<Text color={Colors.Red} align="center">
|
||||
Please enable Bluetooth to be able to request{' '}
|
||||
{controller.vcLabel.singular}
|
||||
</Text>
|
||||
) : (
|
||||
<Text align="center">
|
||||
Show this QR code to request {controller.vcLabel.singular}
|
||||
</Text>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
<Centered fill>
|
||||
{controller.isWaitingForConnection &&
|
||||
controller.connectionParams !== '' ? (
|
||||
<QRCode
|
||||
size={200}
|
||||
value={controller.connectionParams}
|
||||
backgroundColor={Colors.LightGrey}
|
||||
/>
|
||||
) : null}
|
||||
</Centered>
|
||||
|
||||
{controller.statusMessage !== '' && (
|
||||
<Column elevation={1} padding="16 24">
|
||||
<Text>{controller.statusMessage}</Text>
|
||||
</Column>
|
||||
)}
|
||||
|
||||
<TimerBasedReceiveVcModal
|
||||
isVisible={controller.isReviewing}
|
||||
onDismiss={controller.REJECT}
|
||||
onAccept={controller.ACCEPT}
|
||||
onReject={controller.REJECT}
|
||||
onShow={controller.ACCEPT}
|
||||
headerTitle={``}
|
||||
/>
|
||||
|
||||
<SuccesfullyReceived
|
||||
img="true"
|
||||
isVisible={controller.isAccepted}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
onShow={controller.GOBACK}
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isRejected}
|
||||
title="Notice"
|
||||
message={`You rejected ${controller.senderInfo.deviceName}'s ${controller.vcLabel.singular}'`}
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isDisconnected}
|
||||
title="Disconnected"
|
||||
message="The connection was interrupted. Please try again."
|
||||
onBackdropPress={controller.DISMISS}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -4,9 +4,9 @@ import { Button, Column, Text } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { MainRouteProps } from '../../routes/main';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { SendVcModal } from './SendVcModal';
|
||||
import { useScanScreen } from './ScanScreenController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UpdatedSendVcModal } from './UpdatedSendVcModal';
|
||||
|
||||
export const ScanScreen: React.FC<MainRouteProps> = (props) => {
|
||||
const { t } = useTranslation('ScanScreen');
|
||||
@@ -49,7 +49,7 @@ export const ScanScreen: React.FC<MainRouteProps> = (props) => {
|
||||
onBackdropPress={controller.DISMISS_INVALID}
|
||||
/>
|
||||
|
||||
<SendVcModal
|
||||
<UpdatedSendVcModal
|
||||
isVisible={controller.isReviewing}
|
||||
onDismiss={controller.DISMISS}
|
||||
headerElevation={2}
|
||||
|
||||
116
screens/Scan/UpdatedSendVcModal.tsx
Normal file
116
screens/Scan/UpdatedSendVcModal.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React from 'react';
|
||||
import { Input } from 'react-native-elements';
|
||||
import { DeviceInfoList } from '../../components/DeviceInfoList';
|
||||
import { Button, Column } from '../../components/ui';
|
||||
import { Colors } from '../../components/ui/styleUtils';
|
||||
import { MessageOverlay } from '../../components/MessageOverlay';
|
||||
import { Modal, ModalProps } from '../../components/ui/Modal';
|
||||
import { useSendVcModal } from './SendVcModalController';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UpdatedVcItem } from '../../components/UpdatedVcItem';
|
||||
import { useSelectVcOverlay } from './SelectVcOverlayController';
|
||||
|
||||
export const UpdatedSendVcModal: React.FC<SendVcModalProps> = (props) => {
|
||||
const { t } = useTranslation('UpdatedSendVcModal');
|
||||
const controller = useSendVcModal();
|
||||
|
||||
const onShare = () => {
|
||||
controller.ACCEPT_REQUEST();
|
||||
controller2.onSelect();
|
||||
};
|
||||
|
||||
const details = {
|
||||
isVisible: controller.isSelectingVc,
|
||||
receiverName: controller.receiverInfo.deviceName,
|
||||
onSelect: controller.SELECT_VC,
|
||||
onCancel: controller.CANCEL,
|
||||
vcKeys: controller.vcKeys,
|
||||
};
|
||||
|
||||
const controller2 = useSelectVcOverlay(details);
|
||||
|
||||
const reasonLabel = t('reasonForSharing');
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<Column fill backgroundColor={Colors.LightGrey}>
|
||||
<Column padding="16 0" scroll>
|
||||
<DeviceInfoList of="receiver" deviceInfo={controller.receiverInfo} />
|
||||
<Column padding="24">
|
||||
<Input
|
||||
placeholder={!controller.reason ? reasonLabel : ''}
|
||||
label={controller.reason ? reasonLabel : ''}
|
||||
onChangeText={controller.UPDATE_REASON}
|
||||
containerStyle={{ marginBottom: 24 }}
|
||||
/>
|
||||
</Column>
|
||||
<Column>
|
||||
{controller.vcKeys.map((vcKey, index) => (
|
||||
<UpdatedVcItem
|
||||
key={vcKey}
|
||||
vcKey={vcKey}
|
||||
margin="0 2 8 2"
|
||||
onPress={controller2.selectVcItem(index)}
|
||||
selectable
|
||||
selected={index === controller2.selectedIndex}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</Column>
|
||||
<Column
|
||||
backgroundColor={Colors.White}
|
||||
padding="16 24"
|
||||
margin="2 0 0 0"
|
||||
elevation={2}>
|
||||
<Button
|
||||
title={t('AcceptRequest', { vcLabel: controller.vcLabel.singular })}
|
||||
margin="12 0 12 0"
|
||||
disabled={controller2.selectedIndex == null}
|
||||
onPress={onShare}
|
||||
/>
|
||||
<Button
|
||||
type="clear"
|
||||
title={t('Reject')}
|
||||
onPress={controller.CANCEL}
|
||||
/>
|
||||
</Column>
|
||||
</Column>
|
||||
|
||||
{/* <SelectVcOverlay
|
||||
isVisible={controller.isSelectingVc}
|
||||
receiverName={controller.receiverInfo.deviceName}
|
||||
onSelect={controller.SELECT_VC}
|
||||
onCancel={controller.CANCEL}
|
||||
vcKeys={controller.vcKeys}
|
||||
/> */}
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isSendingVc}
|
||||
title={t('statusSharing.title')}
|
||||
hasProgress
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isAccepted}
|
||||
title={t(controller.vcLabel.singular, 'Sent succesfully')}
|
||||
message={t('statusAccepted.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
receiver: controller.receiverInfo.deviceName,
|
||||
})}
|
||||
onShow={props.onDismiss}
|
||||
/>
|
||||
|
||||
<MessageOverlay
|
||||
isVisible={controller.isRejected}
|
||||
title={t('statusRejected.title')}
|
||||
message={t('statusRejected.message', {
|
||||
vcLabel: controller.vcLabel.singular,
|
||||
receiver: controller.receiverInfo.deviceName,
|
||||
})}
|
||||
onBackdropPress={props.onDismiss}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
type SendVcModalProps = ModalProps;
|
||||
Reference in New Issue
Block a user