Added Swipe Gesture & details card

This commit is contained in:
Sri Kanth Kola
2022-06-12 22:13:59 +05:30
parent 5fcf7918f6
commit 4a57e51937
27 changed files with 1394 additions and 62 deletions

View File

@@ -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>
);
};

View File

@@ -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
View 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 '';
}
}

View 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;
}

View 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;
}

View 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 '';
}
}

View 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 '';
}
}

View 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 '';
}
}

View File

@@ -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;
}

View 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;
}

View File

@@ -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'],
},

View File

@@ -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
View File

@@ -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",

View File

@@ -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"

View File

@@ -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;
};

View File

@@ -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>;
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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}>

View File

@@ -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;
}

View File

@@ -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 })}
/>

View File

@@ -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()),
};
}

View 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;
}

View 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>
);
};

View File

@@ -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}

View 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;