merged with latest develop

This commit is contained in:
Monobikash Das
2022-09-16 17:56:00 +05:30
101 changed files with 3612 additions and 439 deletions

View File

@@ -5,6 +5,7 @@ on:
branches:
- main
- develop
- demobranch
tags:
- '*'
pull_request:

BIN
assets/ID-closed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
assets/ID-open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
assets/img1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
assets/img11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
assets/img12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
assets/img13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/img2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
assets/img3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

BIN
assets/img4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
assets/img5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/img6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
assets/img7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
assets/img8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
assets/img9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

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

@@ -4,7 +4,7 @@ import { Popable } from 'react-native-popable';
import { Text } from 'react-native-elements';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { Row } from './ui';
import { Colors } from './ui/styleUtils';
import { Theme } from './ui/styleUtils';
export const DropdownIcon: React.FC<DropdownProps> = (props) => {
const popover = useRef(null);
@@ -21,7 +21,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
padding: 8,
paddingTop: 4,
paddingBottom: 4,
borderBottomColor: Colors.Grey6,
borderBottomColor: Theme.Colors.borderBottomColor,
borderBottomWidth: 1,
}}>
<Pressable
@@ -30,7 +30,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
<Row>
<Icon
name={item.icon}
color={Colors.Orange}
color={Theme.Colors.Icon}
size={20}
style={{ marginRight: 8 }}
/>
@@ -46,7 +46,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
<Popable
position="bottom"
ref={popover}
backgroundColor={Colors.White}
backgroundColor={Theme.Colors.whiteBackgroundColor}
style={{ top: 10, left: -20, minWidth: 120, elevation: 1 }}
content={
<View>
@@ -57,7 +57,7 @@ export const DropdownIcon: React.FC<DropdownProps> = (props) => {
/>
</View>
}>
<Icon name={props.icon} color={Colors.Orange} size={24} />
<Icon name={props.icon} color={Theme.Colors.Icon} size={24} />
</Popable>
</View>
);

View File

@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Dimensions } from 'react-native';
import { ListItem, Overlay, Input } from 'react-native-elements';
import { Text, Column, Row, Button } from './ui';
import { Colors } from './ui/styleUtils';
import { Theme } from './ui/styleUtils';
import { useTranslation } from 'react-i18next';
export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
@@ -14,10 +14,10 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
<ListItem bottomDivider onPress={() => setIsEditing(true)}>
<ListItem.Content>
<ListItem.Title>
<Text>{props.label}</Text>
<Text color={Theme.Colors.profileLabel}>{props.label}</Text>
</ListItem.Title>
</ListItem.Content>
<Text color={Colors.Grey}>{props.value}</Text>
<Text color={Theme.Colors.profileValue}>{props.value}</Text>
<Overlay
overlayStyle={{ padding: 24, elevation: 6 }}
isVisible={isEditing}

View File

@@ -1,12 +1,13 @@
import React from 'react';
import { View, Image } from 'react-native';
import { Theme } from './ui/styleUtils';
export const Logo: React.FC<LogoProps> = (props) => {
return (
<View>
<Image
style={{ resizeMode: 'contain', ...props }}
source={require('../assets/mosip-logo.png')}
source={Theme.MosipLogo}
/>
</View>
);

View File

@@ -2,12 +2,12 @@ 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 { Theme } from './ui/styleUtils';
const styles = StyleSheet.create({
overlay: {
...elevation(5),
backgroundColor: Colors.White,
...Theme.elevation(5),
backgroundColor: Theme.Colors.whiteBackgroundColor,
},
});
@@ -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">
@@ -25,7 +26,10 @@ export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
)}
{props.message && <Text margin="0 0 12 0">{props.message}</Text>}
{props.hasProgress && (
<LinearProgress variant="indeterminate" color={Colors.Orange} />
<LinearProgress
variant="indeterminate"
color={Theme.Colors.Loading}
/>
)}
</Column>
</Overlay>
@@ -38,4 +42,5 @@ interface MessageOverlayProps {
message?: string;
hasProgress?: boolean;
onBackdropPress?: () => void;
onShow?: () => void;
}

308
components/NewVcDetails.tsx Normal file
View File

@@ -0,0 +1,308 @@
import { formatDistanceToNow } from 'date-fns';
import React from 'react';
import * as DateFnsLocale from '../lib/date-fns/locale';
import { useTranslation } from 'react-i18next';
import { Image, ImageBackground } from 'react-native';
import { Icon, ListItem } from 'react-native-elements';
import { VC, CredentialSubject } from '../types/vc';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
import { useReceiveVcModal } from '../screens/Request/ReceiveVcModalController';
const VerifiedIcon: React.FC = () => {
return (
<Icon
name="check-circle"
color={Theme.Colors.VerifiedIcon}
size={14}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
export const NewVcDetails: React.FC<VcDetailsProps> = (props) => {
const { t, i18n } = useTranslation('VcDetails');
const controller = useReceiveVcModal();
return (
<ImageBackground
style={{
width: '100%',
height: '100%',
}}
source={Theme.OpenCard}>
<Row style={Theme.Styles.successTag}>
<Icon
name="check-circle"
color={Theme.Colors.checkCircleIcon}
size={40}
/>
<Text margin="0 10" color={Theme.Colors.whiteText}>
{controller.vcLabel.singular} Received
</Text>
</Row>
<Column>
<Column style={Theme.Styles.closeDetailsHeader}>
<Column>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('fullName')}
</Text>
<Text weight="bold" size="smaller" color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.fullName
)}
</Text>
</Column>
<Image source={Theme.MosipLogo} style={Theme.Styles.logo} />
</Column>
<Row style={Theme.Styles.openDetailsContainer}>
<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 }
: Theme.ProfileIcon
}
style={Theme.Styles.openCardImage}
/>
</ListItem.Content>
</ListItem.Content>
</ListItem>
<Column style={Theme.Styles.details}>
<Column style={Theme.Styles.labelPart}>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
size="smaller"
color={Theme.Colors.DetailsLabel}
style={{ textTransform: 'uppercase', fontWeight: 'bold' }}>
{props.vc?.idType}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{props.vc?.id}
</Text>
</Column>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('generatedOn')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{new Date(props.vc?.generatedOn).toLocaleDateString()}
</Text>
</Column>
<Column fill padding="12 16" style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('status')}
</Text>
<Row>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{t('valid')}
</Text>
{props.vc?.isVerified && <VerifiedIcon />}
</Row>
</Column>
</Column>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('gender')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.gender
)}
</Text>
</Column>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('dateOfBirth')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{new Date(
getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.dateOfBirth
)
).toLocaleDateString()}
</Text>
</Column>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('phoneNumber')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.phone
)}
</Text>
</Column>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('email')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.email
)}
</Text>
</Column>
<Column
fill
padding="12 16"
margin="0 16 0 0"
style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('address')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{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>
</ImageBackground>
);
};
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

@@ -3,7 +3,7 @@ import { Dimensions, Modal as RNModal, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import { PasscodeVerify } from '../components/PasscodeVerify';
import { Column, Text } from '../components/ui';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
const styles = StyleSheet.create({
modal: {
@@ -19,8 +19,11 @@ export const Passcode: React.FC<PasscodeProps> = (props) => {
style={styles.modal}
visible={true}
onRequestClose={props.onDismiss}>
<Column fill padding="32" backgroundColor={Colors.White}>
<Icon name="lock" color={Colors.Orange} size={60} />
<Column
fill
padding="32"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Icon name="lock" color={Theme.Colors.Icon} size={60} />
<Column fill align="space-between" width="100%">
<Text align="center">{props.message || 'Enter your passcode'}</Text>
<PasscodeVerify
@@ -30,7 +33,7 @@ export const Passcode: React.FC<PasscodeProps> = (props) => {
/>
</Column>
<Column fill>
<Text align="center" color={Colors.Red}>
<Text align="center" color={Theme.Colors.errorMessage}>
{props.error}
</Text>
</Column>

View File

@@ -1,24 +1,8 @@
import React, { useEffect } from 'react';
import { StyleSheet, TextInput } from 'react-native';
import { TextInput } from 'react-native';
import { usePinInput } from '../machines/pinInput';
import { Row } from './ui';
import { Colors } from './ui/styleUtils';
const styles = StyleSheet.create({
input: {
borderBottomWidth: 1,
borderColor: Colors.Grey,
color: Colors.Black,
flex: 1,
fontFamily: 'Poppins_600SemiBold',
fontSize: 18,
fontWeight: '600',
height: 40,
lineHeight: 28,
margin: 8,
textAlign: 'center',
},
});
import { Theme } from './ui/styleUtils';
export const PinInput: React.FC<PinInputProps> = (props) => {
const { state, send, events } = usePinInput(props.length);
@@ -38,8 +22,8 @@ export const PinInput: React.FC<PinInputProps> = (props) => {
selectTextOnFocus
keyboardType="numeric"
maxLength={1}
selectionColor={Colors.Orange}
style={styles.input}
selectionColor={Theme.Colors.inputSelection}
style={Theme.PinInputStyle.input}
key={index}
ref={input}
value={values[index]}

View File

@@ -2,35 +2,14 @@ import React, { useContext, useEffect, useState } from 'react';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { Camera } from 'expo-camera';
import { BarCodeEvent, BarCodeScanner } from 'expo-barcode-scanner';
import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Colors } from './ui/styleUtils';
import { Linking, TouchableOpacity, View } from 'react-native';
import { Theme } from './ui/styleUtils';
import { Column, Button, Text } from './ui';
import { GlobalContext } from '../shared/GlobalContext';
import { useSelector } from '@xstate/react';
import { selectIsActive } from '../machines/app';
import { useTranslation } from 'react-i18next';
const styles = StyleSheet.create({
scannerContainer: {
borderWidth: 4,
borderColor: Colors.Black,
borderRadius: 32,
justifyContent: 'center',
height: 300,
width: 300,
overflow: 'hidden',
},
scanner: {
height: 400,
width: '100%',
margin: 'auto',
},
flipIconButton: {
alignSelf: 'center',
alignItems: 'center',
},
});
export const QrScanner: React.FC<QrScannerProps> = (props) => {
const { t } = useTranslation('QrScanner');
const { appService } = useContext(GlobalContext);
@@ -67,7 +46,7 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
if (hasPermission === false) {
return (
<Column fill align="space-between">
<Text align="center" color={Colors.Red}>
<Text align="center" color={Theme.Colors.errorMessage}>
{t('missingPermissionText')}
</Text>
<Button title={t('allowCameraButton')} onPress={openSettings} />
@@ -77,9 +56,9 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
return (
<View>
<View style={styles.scannerContainer}>
<View style={Theme.Styles.scannerContainer}>
<Camera
style={styles.scanner}
style={Theme.Styles.scanner}
barCodeScannerSettings={{
barcodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
}}
@@ -89,7 +68,7 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
</View>
<Column margin="24 0">
<TouchableOpacity
style={styles.flipIconButton}
style={Theme.Styles.flipIconButton}
onPress={() => {
setType(
type === Camera.Constants.Type.back
@@ -97,7 +76,11 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
: Camera.Constants.Type.back
);
}}>
<Icon name="flip-camera-ios" color={Colors.Black} size={64} />
<Icon
name="flip-camera-ios"
color={Theme.Colors.flipCameraIcon}
size={64}
/>
</TouchableOpacity>
</Column>
</View>

205
components/SingleVcItem.tsx Normal file
View File

@@ -0,0 +1,205 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Image, ImageBackground } from 'react-native';
import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate';
import {
createVcItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
selectTag,
selectId,
vcItemMachine,
selectContext,
} from '../machines/vcItem';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { GlobalContext } from '../shared/GlobalContext';
import { RotatingIcon } from './RotatingIcon';
import { useTranslation } from 'react-i18next';
const VerifiedIcon: React.FC = () => {
return (
<Icon
name="check-circle"
color={Theme.Colors.VerifiedIcon}
size={14}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
const getDetails = (arg1, arg2, verifiableCredential) => {
if (arg1 === 'Full Name') {
return (
<Column>
<Text color={Theme.Colors.DetailsLabel} size="smaller">
{arg1}
</Text>
<Text
color={Theme.Colors.Details}
numLines={1}
weight="bold"
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
</Column>
);
}
if (arg1 === 'Status') {
return (
<Column>
<Text size="smaller" color={Theme.Colors.DetailsLabel}>
{arg1}
</Text>
<Row>
<Text
weight="bold"
color={Theme.Colors.Details}
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
{!verifiableCredential ? null : <VerifiedIcon />}
</Row>
</Column>
);
} else {
return (
<Column>
<Text color={Theme.Colors.DetailsLabel} size="smaller">
{arg1}
</Text>
<Text
numLines={1}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
</Column>
);
}
};
export const SingleVcItem: React.FC<VcItemProps> = (props) => {
const { appService } = useContext(GlobalContext);
const { t } = useTranslation('VcDetails');
const machine = useRef(
createVcItemMachine(appService.getSnapshot().serviceRefs, props.vcKey)
);
const service = useInterpret(machine.current);
const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const uin = useSelector(service, selectId);
const tag = useSelector(service, selectTag);
const generatedOn = useSelector(service, selectGeneratedOn);
const fullName = !verifiableCredential
? ''
: getLocalizedField(verifiableCredential.credentialSubject.fullName);
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 (
<Column onShow={props.onShow(service)}>
<ImageBackground
source={!verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
style={
!verifiableCredential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
<Image
source={Theme.MosipLogo}
style={Theme.Styles.logo}
resizeMethod="auto"
/>
</Row>
<Row
crossAlign="center"
margin="5 0 0 0"
style={!verifiableCredential ? Theme.Styles.loadingContainer : null}>
<Column
style={
!verifiableCredential
? Theme.Styles.loadingContainer
: Theme.Styles.closeDetails
}>
<Image
source={
!verifiableCredential
? Theme.ProfileIcon
: { uri: context.credential.biometrics.face }
}
style={Theme.Styles.closeCardImage}
/>
<Column margin="0 0 0 10">
{getDetails(t('fullName'), fullName, verifiableCredential)}
{getDetails(t('uin'), tag || uin, verifiableCredential)}
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}
{getDetails(t('status'), t('valid'), verifiableCredential)}
</Column>
</Column>
{verifiableCredential ? (
selectableOrCheck
) : (
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)}
</Row>
</ImageBackground>
</Column>
);
};
interface VcItemProps {
vcKey: string;
margin?: string;
selectable?: boolean;
selected?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
}
interface LocalizedField {
language: string;
value: string;
}
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

@@ -2,13 +2,13 @@ import React, { useState } from 'react';
import { Dimensions, StyleSheet } from 'react-native';
import { Overlay, Input } from 'react-native-elements';
import { Button, Column, Row, Text } from './ui';
import { Colors, elevation } from './ui/styleUtils';
import { Theme } from './ui/styleUtils';
import { useTranslation } from 'react-i18next';
const styles = StyleSheet.create({
overlay: {
...elevation(5),
backgroundColor: Colors.White,
...Theme.elevation(5),
backgroundColor: Theme.Colors.overlayBackgroundColor,
padding: 0,
},
});

View File

@@ -0,0 +1,230 @@
import { formatDistanceToNow } from 'date-fns';
import React from 'react';
import * as DateFnsLocale from '../lib/date-fns/locale';
import { useTranslation } from 'react-i18next';
import { Image, ImageBackground } from 'react-native';
import { Icon } from 'react-native-elements';
import { VC, CredentialSubject } from '../types/vc';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
const VerifiedIcon: React.FC = () => {
return (
<Icon
name="check-circle"
color={Theme.Colors.VerifiedIcon}
size={14}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
export const UpdatedVcDetails: React.FC<VcDetailsProps> = (props) => {
const { t, i18n } = useTranslation('VcDetails');
return (
<ImageBackground
borderRadius={10}
style={Theme.Styles.openCardBgContainer}
source={Theme.OpenCard}>
<Row style={Theme.Styles.openDetailsHeader}>
<Column>
<Text weight="bold" size="smaller" color={Theme.Colors.DetailsLabel}>
{t('fullName')}
</Text>
<Text weight="bold" size="smaller" color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.fullName
)}
</Text>
</Column>
<Image source={Theme.MosipLogo} style={Theme.Styles.logo} />
</Row>
<Row style={Theme.Styles.openDetailsContainer}>
<Image
source={
props.vc?.credential.biometrics?.face
? { uri: props.vc?.credential.biometrics.face }
: Theme.ProfileIcon
}
style={Theme.Styles.openCardImage}
/>
<Column style={Theme.Styles.labelPartContainer}>
<Column fill>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{props.vc?.idType}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{props.vc?.id}
</Text>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('generatedOn')}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{new Date(props.vc?.generatedOn).toLocaleDateString()}
</Text>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('status')}
</Text>
<Row>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{t('valid')}
</Text>
{props.vc?.isVerified && <VerifiedIcon />}
</Row>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('gender')}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.gender
)}
</Text>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('dateOfBirth')}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{new Date(
getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.dateOfBirth
)
).toLocaleDateString()}
</Text>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('phoneNumber')}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.phone
)}
</Text>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('email')}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.email
)}
</Text>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('address')}
</Text>
<Text weight="semibold" size="smaller" color={Theme.Colors.Details}>
{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>
</ImageBackground>
);
};
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,210 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Pressable, Image, ImageBackground } from 'react-native';
import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate';
import {
createVcItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
selectTag,
selectId,
vcItemMachine,
selectContext,
} from '../machines/vcItem';
import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { RotatingIcon } from './RotatingIcon';
import { GlobalContext } from '../shared/GlobalContext';
import { useTranslation } from 'react-i18next';
const VerifiedIcon: React.FC = () => {
return (
<Icon
name="check-circle"
color={Theme.Colors.VerifiedIcon}
size={14}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
const getDetails = (arg1, arg2, verifiableCredential) => {
if (arg1 === 'Full Name') {
return (
<Column>
<Text color={Theme.Colors.DetailsLabel} size="smaller">
{arg1}
</Text>
<Text
color={Theme.Colors.Details}
numLines={1}
weight="bold"
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
</Column>
);
}
if (arg1 === 'Status') {
return (
<Column>
<Text size="smaller" color={Theme.Colors.DetailsLabel}>
{arg1}
</Text>
<Row>
<Text
weight="bold"
color={Theme.Colors.Details}
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
{!verifiableCredential ? null : <VerifiedIcon />}
</Row>
</Column>
);
} else {
return (
<Column>
<Text color={Theme.Colors.DetailsLabel} size="smaller">
{arg1}
</Text>
<Text
numLines={1}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{!verifiableCredential ? '' : arg2}
</Text>
</Column>
);
}
};
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 context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const uin = useSelector(service, selectId);
const tag = useSelector(service, selectTag);
const generatedOn = useSelector(service, selectGeneratedOn);
const fullName = !verifiableCredential
? ''
: getLocalizedField(verifiableCredential.credentialSubject.fullName);
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={Theme.Styles.closeCardBgContainer}>
<ImageBackground
source={!verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
style={
!verifiableCredential
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
<Image
source={Theme.MosipLogo}
style={Theme.Styles.logo}
resizeMethod="auto"
/>
</Row>
<Row
crossAlign="center"
margin="5 0 0 0"
style={!verifiableCredential ? Theme.Styles.loadingContainer : null}>
<Column
style={
!verifiableCredential
? Theme.Styles.loadingContainer
: Theme.Styles.closeDetails
}>
<Image
source={
!verifiableCredential
? Theme.ProfileIcon
: { uri: context.credential.biometrics.face }
}
style={Theme.Styles.closeCardImage}
/>
<Column margin="0 0 0 10">
{getDetails(t('fullName'), fullName, verifiableCredential)}
{getDetails(t('uin'), tag || uin, verifiableCredential)}
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}
{getDetails(t('status'), t('valid'), verifiableCredential)}
</Column>
</Column>
{verifiableCredential ? (
selectableOrCheck
) : (
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)}
</Row>
</ImageBackground>
</Pressable>
);
};
interface VcItemProps {
vcKey: string;
margin?: string;
selectable?: boolean;
selected?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
}
interface LocalizedField {
language: string;
value: string;
}
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

@@ -6,14 +6,14 @@ import { Image } 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 { Theme } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
const VerifiedIcon: React.FC = () => {
return (
<Icon
name="check-circle"
color={Colors.Green}
color={Theme.Colors.VerifiedIcon}
size={14}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
@@ -27,7 +27,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<Column>
<Row pY={16} pX={8} align="space-between">
<Column fill elevation={1} pY={12} pX={16} margin="0 8">
<Text size="smaller" color={Colors.Grey}>
<Text size="smaller" color={Theme.Colors.textLabel}>
{t('generatedOn')}
</Text>
<Text weight="bold" size="smaller">
@@ -37,7 +37,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
<Column fill elevation={1} pY={12} pX={16} margin="0 8">
<Text
size="smaller"
color={Colors.Grey}
color={Theme.Colors.textLabel}
style={{ textTransform: 'uppercase' }}>
{props.vc?.idType}
</Text>
@@ -46,7 +46,7 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
</Text>
</Column>
<Column fill elevation={1} pY={12} pX={16} margin="0 8">
<Text size="smaller" color={Colors.Grey}>
<Text size="smaller" color={Theme.Colors.textLabel}>
{t('status')}
</Text>
<Row>

View File

@@ -1,6 +1,6 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Pressable, StyleSheet } from 'react-native';
import { Pressable } from 'react-native';
import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate';
import {
@@ -12,36 +12,10 @@ import {
vcItemMachine,
} from '../machines/vcItem';
import { Column, Row, Text } from './ui';
import { Colors } from './ui/styleUtils';
import { Theme } 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(
@@ -75,15 +49,25 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
elevation={!verifiableCredential ? 0 : 2}
crossAlign="center"
margin={props.margin}
backgroundColor={!verifiableCredential ? Colors.Grey6 : Colors.White}
backgroundColor={
!verifiableCredential
? Theme.Colors.loadingLabel
: Theme.Colors.whiteBackgroundColor
}
padding={[16, 16]}
style={
!verifiableCredential ? styles.loadingContainer : styles.container
!verifiableCredential
? Theme.VcItemStyles.loadingContainer
: Theme.VcItemStyles.container
}>
<Column fill margin="0 24 0 0">
<Text
weight="semibold"
style={!verifiableCredential ? styles.loadingTitle : styles.title}
style={
!verifiableCredential
? Theme.VcItemStyles.loadingTitle
: Theme.VcItemStyles.title
}
margin="0 0 6 0">
{!verifiableCredential ? '' : tag || uin}
</Text>
@@ -91,21 +75,25 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
size="smaller"
numLines={1}
style={
!verifiableCredential ? styles.loadingSubtitle : styles.subtitle
!verifiableCredential
? Theme.VcItemStyles.loadingSubtitle
: Theme.VcItemStyles.subtitle
}>
{!verifiableCredential
? ''
: getLocalizedField(
verifiableCredential.credentialSubject.fullName
verifiableCredential.verifiableCredential.credentialSubject
.fullName
) +
' · ' +
generatedOn}
</Text>
</Column>
{verifiableCredential ? (
selectableOrCheck
) : (
<RotatingIcon name="sync" color={Colors.Grey5} />
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)}
</Row>
</Pressable>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -3,46 +3,21 @@ import {
Button as RNEButton,
ButtonProps as RNEButtonProps,
} from 'react-native-elements';
import {
GestureResponderEvent,
StyleProp,
StyleSheet,
ViewStyle,
} from 'react-native';
import { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native';
import { Text } from './Text';
import { Colors, Spacing, spacing } from './styleUtils';
const styles = StyleSheet.create({
fill: {
flex: 1,
},
solid: {
backgroundColor: Colors.Orange,
},
clear: {
backgroundColor: 'transparent',
},
outline: {
backgroundColor: 'transparent',
borderColor: Colors.Orange,
},
container: {
minHeight: 48,
flexDirection: 'row',
},
disabled: {
opacity: 0.5,
},
});
import { Theme } from './styleUtils';
export const Button: React.FC<ButtonProps> = (props) => {
const type = props.type || 'solid';
const buttonStyle: StyleProp<ViewStyle> = [styles.fill, styles[type]];
const buttonStyle: StyleProp<ViewStyle> = [
Theme.ButtonStyles.fill,
Theme.ButtonStyles[type],
];
const containerStyle: StyleProp<ViewStyle> = [
styles.container,
props.disabled ? styles.disabled : null,
props.margin ? spacing('margin', props.margin) : null,
Theme.ButtonStyles.container,
props.disabled ? Theme.ButtonStyles.disabled : null,
props.margin ? Theme.spacing('margin', props.margin) : null,
];
const handleOnPress = (event: GestureResponderEvent) => {
@@ -54,14 +29,21 @@ export const Button: React.FC<ButtonProps> = (props) => {
return (
<RNEButton
buttonStyle={buttonStyle}
containerStyle={[props.fill ? styles.fill : null, containerStyle]}
containerStyle={[
props.fill ? Theme.ButtonStyles.fill : null,
containerStyle,
]}
type={props.type}
raised={props.raised}
title={
<Text
weight="semibold"
align="center"
color={type === 'solid' ? Colors.White : Colors.Orange}>
color={
type === 'solid' || type === 'addId'
? Theme.Colors.whiteText
: Theme.Colors.AddIdBtnTxt
}>
{props.title}
</Text>
}

View File

@@ -9,7 +9,7 @@ import {
RefreshControlProps,
SafeAreaView,
} from 'react-native';
import { elevation, ElevationLevel, Spacing, spacing } from './styleUtils';
import { Theme, ElevationLevel, Spacing } from './styleUtils';
function createLayout(
direction: FlexStyle['flexDirection'],
@@ -31,14 +31,14 @@ function createLayout(
const styles: StyleProp<ViewStyle> = [
layoutStyles.base,
props.fill ? layoutStyles.fill : null,
props.padding ? spacing('padding', props.padding) : null,
props.margin ? spacing('margin', props.margin) : null,
props.padding ? Theme.spacing('padding', props.padding) : null,
props.margin ? Theme.spacing('margin', props.margin) : null,
props.backgroundColor ? { backgroundColor: props.backgroundColor } : null,
props.width ? { width: props.width } : null,
props.height ? { height: props.height } : null,
props.align ? { justifyContent: props.align } : null,
props.crossAlign ? { alignItems: props.crossAlign } : null,
props.elevation ? elevation(props.elevation) : null,
props.elevation ? Theme.elevation(props.elevation) : null,
props.style ? props.style : null,
props.pY ? { paddingVertical: props.pY } : null,
props.pX ? { paddingHorizontal: props.pX } : null,

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { Dimensions, Modal as RNModal, StyleSheet, View } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { Colors, ElevationLevel } from './styleUtils';
import { ElevationLevel, Theme } from './styleUtils';
const styles = StyleSheet.create({
modal: {
@@ -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 safe>
<Row elevation={props.headerElevation}>
@@ -31,7 +32,7 @@ export const Modal: React.FC<ModalProps> = (props) => {
<Icon
name="chevron-left"
onPress={props.onDismiss}
color={Colors.Orange}
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
@@ -41,7 +42,7 @@ export const Modal: React.FC<ModalProps> = (props) => {
<Icon
name="close"
onPress={props.onDismiss}
color={Colors.Orange}
color={Theme.Colors.Icon}
/>
)}
</View>
@@ -58,4 +59,5 @@ export interface ModalProps {
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
onShow?: () => void;
}

View File

@@ -0,0 +1,70 @@
import React from 'react';
import { Dimensions, Modal as RNModal, StyleSheet, View } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { Theme, 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}
onShow={props.onShow}
onRequestClose={props.onDismiss}>
<Column fill>
<Row elevation={props.headerElevation}>
<View style={{ marginHorizontal: 16, marginVertical: 16 }}>
{props.headerRight ? (
<Icon
name="chevron-left"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
<Text weight="semibold">{props.headerTitle}</Text>
</Row>
{props.headerRight || (
<Icon
name="close"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
)}
</View>
</Row>
<Row padding="16 32" elevation={props.headerElevation}>
{props.headerRight ? (
<Icon
name="chevron-left"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
<Text weight="semibold">{props.headerTitle}</Text>
</Row>
</Row>
{props.children}
</Column>
</RNModal>
);
};
export interface ModalProps {
isVisible: boolean;
onDismiss: () => void;
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
onShow?: () => void;
}

View File

@@ -0,0 +1,53 @@
import React from 'react';
import { Dimensions, Modal as RNModal, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { Theme, 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={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
<Text weight="semibold">{props.headerTitle}</Text>
</Row>
{props.headerRight || (
<Icon
name="close"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
)}
</Row>
{props.children}
</Column>
</RNModal>
);
};
export interface ModalProps {
isVisible: boolean;
onDismiss: () => void;
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { Dimensions, Modal as RNModal, StyleSheet, View } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { Theme, 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 elevation={props.headerElevation}>
<View
style={{
flex: 1,
flexDirection: 'row',
marginHorizontal: 16,
marginVertical: 16,
}}>
{props.headerRight ? (
<Icon
name="chevron-left"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
<Text weight="semibold">{props.headerTitle}</Text>
</Row>
{props.headerRight || (
<Icon
name="close"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
)}
</View>
</Row>
{props.children}
</Column>
</RNModal>
);
};
export interface ModalProps {
isVisible: boolean;
onDismiss: () => void;
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
}

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { Dimensions, Modal as RNModal, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { Theme, 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}
onShow={props.onShow}
onRequestClose={props.onDismiss}>
<Column fill>
<Row padding="16 32" elevation={props.headerElevation}>
{props.headerRight ? (
<Icon
name="chevron-left"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center">
<Text weight="semibold">{props.headerTitle}</Text>
</Row>
</Row>
{props.children}
</Column>
</RNModal>
);
};
export interface ModalProps {
isVisible: boolean;
onDismiss: () => void;
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
onShow?: () => void;
}

View File

@@ -1,42 +1,17 @@
import React from 'react';
import { StyleProp, TextStyle, StyleSheet, Text as RNText } from 'react-native';
import { Colors, Spacing, spacing } from './styleUtils';
const styles = StyleSheet.create({
base: {
color: Colors.Black,
fontSize: 18,
lineHeight: 28,
},
regular: {
fontFamily: 'Poppins_400Regular',
},
semibold: {
fontFamily: 'Poppins_600SemiBold',
},
bold: {
fontFamily: 'Poppins_700Bold',
},
small: {
fontSize: 14,
lineHeight: 21,
},
smaller: {
fontSize: 12,
lineHeight: 18,
},
});
import { StyleProp, TextStyle, Text as RNText } from 'react-native';
import { Theme, Spacing } from './styleUtils';
export const Text: React.FC<TextProps> = (props: TextProps) => {
const weight = props.weight || 'regular';
const textStyles: StyleProp<TextStyle> = [
styles.base,
styles[weight],
Theme.TextStyles.base,
Theme.TextStyles[weight],
props.color ? { color: props.color } : null,
props.align ? { textAlign: props.align } : null,
props.margin ? spacing('margin', props.margin) : null,
props.size ? styles[props.size] : null,
props.margin ? Theme.spacing('margin', props.margin) : null,
props.size ? Theme.TextStyles[props.size] : null,
props.style ? props.style : null,
];

View File

@@ -1,24 +1,26 @@
import React from 'react';
import { Column, Text } from '.';
import { Colors } from './styleUtils';
import { Theme } from './styleUtils';
export const TextItem: React.FC<TextItemProps> = (props) => {
return (
<Column
backgroundColor={Colors.White}
backgroundColor={Theme.Colors.whiteBackgroundColor}
margin={props.margin}
pX={24}
pY={props.label ? 16 : 12}
style={{
borderBottomColor: Colors.Grey6,
borderBottomColor: Theme.Colors.borderBottomColor,
borderBottomWidth: props.divider ? 1 : 0,
}}>
{props.label && (
<Text size="smaller" color={Colors.Grey} weight="semibold">
<Text size="smaller" color={Theme.Colors.textLabel} weight="semibold">
{props.label}
</Text>
)}
<Text color={Colors.Black} weight={props.label ? 'semibold' : 'regular'}>
<Text
color={Theme.Colors.textValue}
weight={props.label ? 'semibold' : 'regular'}>
{props.text}
</Text>
</Column>

View File

@@ -1,30 +1,16 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { View } from 'react-native';
import { Text } from './Text';
import { Colors } from './styleUtils';
const styles = StyleSheet.create({
toastContainer: {
backgroundColor: Colors.Orange,
position: 'absolute',
alignSelf: 'center',
top: 80,
borderRadius: 4,
},
messageContainer: {
fontSize: 12,
},
});
import { Theme } from './styleUtils';
export const ToastItem: React.FC<ToastProps> = (props) => {
return (
<View style={styles.toastContainer}>
<View style={Theme.ToastItemStyles.toastContainer}>
<Text
align="center"
margin="8 16"
color={Colors.White}
style={styles.messageContainer}>
color={Theme.Colors.ToastItemText}
style={Theme.ToastItemStyles.messageContainer}>
{props.message}
</Text>
</View>

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Dimensions, Modal as RNModal, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row } from '.';
import { Theme, 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={Theme.Colors.Icon}
/>
) : null}
</Row>
{props.children}
</Column>
</RNModal>
);
};
export interface ModalProps {
isVisible: boolean;
onDismiss: () => void;
headerTitle?: string;
headerElevation?: ElevationLevel;
headerRight?: React.ReactElement;
}

View File

@@ -1,62 +1,12 @@
import { ViewStyle } from 'react-native';
import { DefaultTheme } from './themes/DefaultTheme';
import { PurpleTheme } from './themes/PurpleTheme';
export const Colors = {
Black: '#231F20',
Grey: '#B0B0B0',
Grey5: '#E0E0E0',
Grey6: '#F2F2F2',
Orange: '#F2811D',
LightGrey: '#FAF9FF',
White: '#FFFFFF',
Red: '#EB5757',
Green: '#219653',
};
// To change the theme, CSS theme file has to import and assign it to Theme in line no 6
export const Theme = DefaultTheme;
type SpacingXY = [number, number];
type SpacingFull = [number, number, number, number];
export type Spacing = SpacingXY | SpacingFull | string;
export function spacing(type: 'margin' | 'padding', values: Spacing) {
if (Array.isArray(values) && values.length === 2) {
return {
[`${type}Horizontal`]: values[0],
[`${type}Vertical`]: values[1],
};
}
const [top, end, bottom, start] =
typeof values === 'string' ? values.split(' ').map(Number) : values;
return {
[`${type}Top`]: top,
[`${type}End`]: end != null ? end : top,
[`${type}Bottom`]: bottom != null ? bottom : top,
[`${type}Start`]: start != null ? start : end != null ? end : top,
};
}
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
export function elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/
if (level === 0) {
return null;
}
const index = level - 1;
return {
shadowColor: Colors.Black,
shadowOffset: {
width: 0,
height: [1, 1, 1, 2, 2][index],
},
shadowOpacity: [0.18, 0.2, 0.22, 0.23, 0.25][index],
shadowRadius: [1.0, 1.41, 2.22, 2.62, 3.84][index],
elevation: level,
zIndex: level,
borderRadius: 4,
backgroundColor: Colors.White,
};
}

View File

@@ -0,0 +1,413 @@
import { StyleSheet, ViewStyle } from 'react-native';
import { Spacing } from '../styleUtils';
const Colors = {
Black: '#231F20',
Grey: '#B0B0B0',
Grey5: '#E0E0E0',
Grey6: '#F2F2F2',
Orange: '#F2811D',
LightGrey: '#FAF9FF',
White: '#FFFFFF',
Red: '#EB5757',
Green: '#219653',
Transparent: 'transparent',
};
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
export const DefaultTheme = {
Colors: {
TabItemText: Colors.Orange,
Details: Colors.Black,
DetailsLabel: Colors.Orange,
AddIdBtnBg: Colors.Orange,
AddIdBtnTxt: Colors.Orange,
ClearAddIdBtnBg: Colors.Transparent,
Loading: Colors.Orange,
noUinText: Colors.Orange,
IconBg: Colors.Orange,
Icon: Colors.Orange,
borderBottomColor: Colors.Grey6,
whiteBackgroundColor: Colors.White,
lightGreyBackgroundColor: Colors.LightGrey,
profileLanguageValue: Colors.Grey,
profileVersion: Colors.Grey,
profileAuthFactorUnlock: Colors.Grey,
profileLabel: Colors.Black,
profileValue: Colors.Grey,
overlayBackgroundColor: Colors.White,
rotatingIcon: Colors.Grey5,
loadingLabel: Colors.Grey6,
textLabel: Colors.Grey,
textValue: Colors.Black,
errorMessage: Colors.Red,
QRCodeBackgroundColor: Colors.LightGrey,
ReceiveVcModalBackgroundColor: Colors.LightGrey,
ToastItemText: Colors.White,
VerifiedIcon: Colors.Green,
whiteText: Colors.White,
flipCameraIcon: Colors.Black,
IdInputModalBorder: Colors.Grey,
inputSelection: Colors.Orange,
checkCircleIcon: Colors.White,
OnboardingCircleIcon: Colors.White,
OnboardingCloseIcon: Colors.White,
},
Styles: StyleSheet.create({
title: {
color: Colors.Black,
backgroundColor: Colors.Transparent,
},
loadingTitle: {
color: Colors.Transparent,
backgroundColor: Colors.Grey,
borderRadius: 4,
},
subtitle: {
backgroundColor: Colors.Transparent,
},
loadingSubtitle: {
backgroundColor: Colors.Grey,
borderRadius: 4,
},
closeDetails: {
flex: 1,
flexDirection: 'row',
paddingRight: 90,
},
loadingContainer: {
flex: 1,
flexDirection: 'row',
backgroundColor: Colors.Grey6,
borderRadius: 4,
},
vertloadingContainer: {
flex: 1,
backgroundColor: Colors.Grey6,
borderRadius: 4,
padding: 5,
},
closeDetailsContainer: {
flex: 1,
justifyContent: 'flex-start',
},
logoContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
marginLeft: 300,
},
closeCardBgContainer: {
borderRadius: 10,
margin: 8,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOffset: { width: -1, height: 1 },
shadowOpacity: 0.4,
shadowRadius: 3,
elevation: 4,
},
labelPartContainer: {
marginLeft: 16,
},
labelPart: {
marginTop: 10,
},
openCardBgContainer: {
borderRadius: 10,
margin: 8,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOffset: { width: -1, height: 1 },
shadowOpacity: 0.4,
shadowRadius: 3,
elevation: 4,
padding: 10,
},
backgroundImageContainer: {
flex: 1,
padding: 10,
},
successTag: {
backgroundColor: Colors.Green,
height: 43,
flex: 1,
alignItems: 'center',
paddingLeft: 6,
},
closeDetailsHeader: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingLeft: 18,
margin: 6,
},
openDetailsHeader: {
flex: 1,
justifyContent: 'space-between',
},
logo: {
height: 30,
width: 30,
},
homeCloseCardDetailsHeader: {
flex: 1,
justifyContent: 'flex-end',
},
details: {
width: 290,
marginLeft: 110,
marginTop: 0,
},
openDetailsContainer: {
flex: 1,
justifyContent: 'space-between',
padding: 10,
},
closeCardImage: {
width: 100,
height: 100,
borderRadius: 5,
},
openCardImage: {
width: 100,
height: 100,
borderRadius: 5,
},
scannerContainer: {
borderWidth: 4,
borderColor: Colors.Black,
borderRadius: 32,
justifyContent: 'center',
height: 300,
width: 300,
overflow: 'hidden',
},
scanner: {
height: 400,
width: '100%',
margin: 'auto',
},
flipIconButton: {
alignSelf: 'center',
alignItems: 'center',
},
tabIndicator: {
backgroundColor: Colors.Orange,
},
tabContainer: {
backgroundColor: Colors.Transparent,
justifyContent: 'center',
},
tabView: {
flex: 1,
},
detailsText: {
fontWeight: 'bold',
fontSize: 15,
fontFamily: 'Poppins_700Bold',
},
getId: {
justifyContent: 'center',
alignItems: 'center',
marginTop: 10,
},
placeholder: {
fontFamily: 'Poppins_400Regular',
},
}),
PinInputStyle: StyleSheet.create({
input: {
borderBottomWidth: 1,
borderColor: Colors.Grey,
color: Colors.Black,
flex: 1,
fontFamily: 'Poppins_600SemiBold',
fontSize: 18,
fontWeight: '600',
height: 40,
lineHeight: 28,
margin: 8,
textAlign: 'center',
},
}),
TextStyles: StyleSheet.create({
base: {
color: Colors.Black,
fontSize: 18,
lineHeight: 28,
},
regular: {
fontFamily: 'Poppins_400Regular',
},
semibold: {
fontFamily: 'Poppins_600SemiBold',
},
bold: {
fontFamily: 'Poppins_700Bold',
},
small: {
fontSize: 14,
lineHeight: 21,
},
smaller: {
fontSize: 12,
lineHeight: 18,
},
}),
VcItemStyles: 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,
},
}),
ToastItemStyles: StyleSheet.create({
toastContainer: {
backgroundColor: Colors.Orange,
position: 'absolute',
alignSelf: 'center',
top: 80,
borderRadius: 4,
},
messageContainer: {
fontSize: 12,
},
}),
ButtonStyles: StyleSheet.create({
fill: {
flex: 1,
},
solid: {
backgroundColor: Colors.Orange,
},
clear: {
backgroundColor: Colors.Transparent,
},
outline: {
backgroundColor: Colors.Transparent,
borderColor: Colors.Orange,
},
container: {
minHeight: 48,
flexDirection: 'row',
},
disabled: {
opacity: 0.5,
},
addId: {
backgroundColor: Colors.Orange,
},
clearAddIdBtnBg: {
backgroundColor: Colors.Transparent,
},
}),
OnboardingOverlayStyles: StyleSheet.create({
overlay: {
padding: 24,
bottom: 86,
backgroundColor: 'transparent',
shadowColor: 'transparent',
},
slide: {
width: '100%',
padding: 20,
},
slider: {
backgroundColor: Colors.Orange,
minHeight: 300,
width: '100%',
margin: 0,
borderRadius: 4,
},
appSlider: {},
sliderTitle: {
color: Colors.White,
marginBottom: 20,
fontFamily: 'Poppins_700Bold',
},
text: {
color: Colors.White,
},
paginationContainer: {
margin: 10,
},
paginationDots: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
},
closeIcon: {
alignItems: 'flex-end',
end: 16,
top: 40,
zIndex: 1,
},
}),
OpenCard: require('../../../assets/ID-open.png'),
CloseCard: require('../../../assets/ID-closed.png'),
ProfileIcon: require('../../../assets/placeholder-photo.png'),
MosipLogo: require('../../../assets/mosip-logo.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/
if (level === 0) {
return null;
}
const index = level - 1;
return {
shadowColor: Colors.Black,
shadowOffset: {
width: 0,
height: [1, 1, 1, 2, 2][index],
},
shadowOpacity: [0.18, 0.2, 0.22, 0.23, 0.25][index],
shadowRadius: [1.0, 1.41, 2.22, 2.62, 3.84][index],
elevation: level,
zIndex: level,
borderRadius: 4,
backgroundColor: Colors.White,
};
},
spacing(type: 'margin' | 'padding', values: Spacing) {
if (Array.isArray(values) && values.length === 2) {
return {
[`${type}Horizontal`]: values[0],
[`${type}Vertical`]: values[1],
};
}
const [top, end, bottom, start] =
typeof values === 'string' ? values.split(' ').map(Number) : values;
return {
[`${type}Top`]: top,
[`${type}End`]: end != null ? end : top,
[`${type}Bottom`]: bottom != null ? bottom : top,
[`${type}Start`]: start != null ? start : end != null ? end : top,
};
},
};

View File

@@ -0,0 +1,453 @@
import { StyleSheet, ViewStyle } from 'react-native';
import { Spacing } from '../styleUtils';
const Colors = {
Black: '#231F20',
Grey: '#B0B0B0',
Grey5: '#E0E0E0',
Grey6: '#F2F2F2',
Orange: '#F2811D',
LightGrey: '#FAF9FF',
White: '#FFFFFF',
Red: '#EB5757',
Green: '#219653',
Purple: '#70308C',
Transparent: 'transparent',
};
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
export const PurpleTheme = {
Colors: {
TabItemText: Colors.Purple,
Details: Colors.White,
DetailsLabel: Colors.White,
AddIdBtnBg: Colors.Purple,
AddIdBtnTxt: Colors.Purple,
ClearAddIdBtnBg: 'transparent',
noUinText: Colors.Purple,
IconBg: Colors.Purple,
Icon: Colors.Purple,
Loading: Colors.Purple,
borderBottomColor: Colors.Grey6,
whiteBackgroundColor: Colors.White,
lightGreyBackgroundColor: Colors.LightGrey,
profileLanguageValue: Colors.Grey,
profileVersion: Colors.Grey,
profileAuthFactorUnlock: Colors.Grey,
profileLabel: Colors.Black,
profileValue: Colors.Grey,
overlayBackgroundColor: Colors.White,
rotatingIcon: Colors.Grey5,
loadingLabel: Colors.Grey6,
textLabel: Colors.Grey,
textValue: Colors.Black,
errorMessage: Colors.Red,
QRCodeBackgroundColor: Colors.LightGrey,
ReceiveVcModalBackgroundColor: Colors.LightGrey,
ToastItemText: Colors.White,
VerifiedIcon: Colors.Green,
whiteText: Colors.White,
flipCameraIcon: Colors.Black,
IdInputModalBorder: Colors.Grey,
inputSelection: Colors.Orange,
checkCircleIcon: Colors.White,
OnboardingCircleIcon: Colors.White,
OnboardingCloseIcon: Colors.White,
},
Styles: StyleSheet.create({
title: {
color: '#231F20',
backgroundColor: 'transparent',
},
loadingTitle: {
color: 'transparent',
backgroundColor: '#B0B0B0',
borderRadius: 4,
},
subtitle: {
backgroundColor: 'transparent',
},
loadingSubtitle: {
backgroundColor: '#B0B0B0',
borderRadius: 4,
},
closeDetails: {
flex: 1,
flexDirection: 'row',
paddingRight: 90,
},
loadingContainer: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#F2F2F2',
borderRadius: 4,
margin: 5,
},
vertloadingContainer: {
flex: 1,
backgroundColor: '#F2F2F2',
borderRadius: 4,
margin: 5,
},
closeDetailsContainer: {
flex: 1,
justifyContent: 'flex-start',
},
logoContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
marginLeft: 300,
},
closeCardBgContainer: {
marginBottom: 10,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOffset: { width: -1, height: 1 },
shadowOpacity: 0.4,
shadowRadius: 3,
elevation: 4,
},
labelPartContainer: {
marginLeft: 16,
},
labelPart: {
marginTop: 10,
},
openCardBgContainer: {
margin: 8,
shadowColor: '#000',
shadowOffset: { width: -1, height: 1 },
shadowOpacity: 1,
shadowRadius: 3,
elevation: 5,
padding: 10,
},
backgroundImageContainer: {
flex: 1,
padding: 10,
},
successTag: {
backgroundColor: '#219653',
height: 43,
flex: 1,
alignItems: 'center',
paddingLeft: 6,
},
closeDetailsHeader: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingLeft: 18,
margin: 6,
},
openDetailsHeader: {
flex: 1,
justifyContent: 'space-between',
},
homeCloseCardDetailsHeader: {
flex: 1,
justifyContent: 'space-between',
},
logo: {
height: 30,
width: 30,
},
openDetailsContainer: {
flex: 1,
justifyContent: 'space-between',
padding: 10,
},
closeCardImage: {
width: 100,
height: 100,
borderRadius: 5,
},
details: {
width: 290,
marginLeft: 110,
marginTop: 0,
},
openCardImage: {
width: 100,
height: 100,
borderRadius: 5,
},
scannerContainer: {
borderWidth: 4,
borderColor: '#231F20',
borderRadius: 32,
justifyContent: 'center',
height: 300,
width: 300,
overflow: 'hidden',
},
scanner: {
height: 400,
width: '100%',
margin: 'auto',
},
flipIconButton: {
alignSelf: 'center',
alignItems: 'center',
},
tabIndicator: {
backgroundColor: '#70308C',
},
tabContainer: {
backgroundColor: 'transparent',
justifyContent: 'center',
},
tabView: {
flex: 1,
},
detailsText: {
fontWeight: 'bold',
fontSize: 15,
fontFamily: 'Poppins_700Bold',
},
getId: {
justifyContent: 'center',
alignItems: 'center',
marginTop: 10,
},
placeholder: {
fontFamily: 'Poppins_400Regular',
},
overlay: {
padding: 24,
bottom: 86,
backgroundColor: 'transparent',
shadowColor: 'transparent',
},
slide: {
width: '100%',
padding: 20,
},
slider: {
backgroundColor: '#70308C',
minHeight: 280,
width: '100%',
margin: 0,
borderRadius: 4,
},
appSlider: {},
sliderTitle: {
color: '#FFFFFF',
marginBottom: 20,
fontFamily: 'Poppins_700Bold',
},
text: {
color: '#FFFFFF',
},
paginationContainer: {
margin: 10,
},
paginationDots: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
},
closeIcon: {
alignItems: 'flex-end',
end: 16,
top: 40,
zIndex: 1,
},
}),
PinInputStyle: StyleSheet.create({
input: {
borderBottomWidth: 1,
borderColor: Colors.Grey,
color: Colors.Black,
flex: 1,
fontFamily: 'Poppins_600SemiBold',
fontSize: 18,
fontWeight: '600',
height: 40,
lineHeight: 28,
margin: 8,
textAlign: 'center',
},
}),
TextStyles: StyleSheet.create({
base: {
color: Colors.Black,
fontSize: 18,
lineHeight: 28,
},
regular: {
fontFamily: 'Poppins_400Regular',
},
semibold: {
fontFamily: 'Poppins_600SemiBold',
},
bold: {
fontFamily: 'Poppins_700Bold',
},
small: {
fontSize: 14,
lineHeight: 21,
},
smaller: {
fontSize: 12,
lineHeight: 18,
},
}),
VcItemStyles: 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,
},
}),
ToastItemStyles: StyleSheet.create({
toastContainer: {
backgroundColor: Colors.Orange,
position: 'absolute',
alignSelf: 'center',
top: 80,
borderRadius: 4,
},
messageContainer: {
fontSize: 12,
},
}),
ButtonStyles: StyleSheet.create({
fill: {
flex: 1,
},
solid: {
backgroundColor: Colors.Purple,
},
clear: {
backgroundColor: Colors.Transparent,
},
outline: {
backgroundColor: Colors.Transparent,
borderColor: Colors.Purple,
},
container: {
minHeight: 48,
flexDirection: 'row',
},
disabled: {
opacity: 0.5,
},
addId: {
backgroundColor: Colors.Purple,
},
clearAddIdBtnBg: {
backgroundColor: Colors.Transparent,
},
}),
OnboardingOverlayStyles: StyleSheet.create({
overlay: {
padding: 24,
bottom: 86,
backgroundColor: 'transparent',
shadowColor: 'transparent',
},
slide: {
width: '100%',
padding: 20,
},
slider: {
backgroundColor: Colors.Purple,
minHeight: 300,
width: '100%',
margin: 0,
borderRadius: 4,
},
appSlider: {},
sliderTitle: {
color: Colors.White,
marginBottom: 20,
fontFamily: 'Poppins_700Bold',
},
text: {
color: Colors.White,
},
paginationContainer: {
margin: 10,
},
paginationDots: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
},
closeIcon: {
alignItems: 'flex-end',
end: 16,
top: 40,
zIndex: 1,
},
}),
OpenCard: require('../../../purpleAassets/bg_cart_one.png'),
CloseCard: require('../../../purpleAassets/cart_unsel.png'),
ProfileIcon: require('../../../purpleAassets/profile_icon_unsel.png'),
MosipLogo: require('../../../purpleAassets/logo.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/
if (level === 0) {
return null;
}
const index = level - 1;
return {
shadowColor: Colors.Black,
shadowOffset: {
width: 0,
height: [1, 1, 1, 2, 2][index],
},
shadowOpacity: [0.18, 0.2, 0.22, 0.23, 0.25][index],
shadowRadius: [1.0, 1.41, 2.22, 2.62, 3.84][index],
elevation: level,
zIndex: level,
borderRadius: 4,
backgroundColor: Colors.White,
};
},
spacing(type: 'margin' | 'padding', values: Spacing) {
if (Array.isArray(values) && values.length === 2) {
return {
[`${type}Horizontal`]: values[0],
[`${type}Vertical`]: values[1],
};
}
const [top, end, bottom, start] =
typeof values === 'string' ? values.split(' ').map(Number) : values;
return {
[`${type}Top`]: top,
[`${type}End`]: end != null ? end : top,
[`${type}Bottom`]: bottom != null ? bottom : top,
[`${type}Start`]: start != null ? start : end != null ? end : top,
};
},
};

View File

@@ -0,0 +1,209 @@
# CSS Themes
We can customize the application Theme by adding a new file under components/ui/themes and import that file in components/ui/styleUtils.ts and assign that file to Theme variable
```
components/ui/styleUtils.ts
eg:-
import { PurpleTheme } from './PurpleTheme';
export const Theme = PurpleTheme;
```
## **Logo and Background Images :**
To change the mosip logo
```
MosipLogo = require(path of logo you want to use, in string format)
*logo can be png or svg
eg:-
MosipLogo = require('../../assets/mosip-logo.png')
```
To change the profile logo which is available as a demo while loading the vc details
```
ProfileIcon = require(path of logo you want to use, in string format)
*logo can be png or svg
eg:-
ProfileIcon: require('../../assets/placeholder-photo.png')
```
To change the close card details background
```
CloseCard = require(path of image you want to use, in string format)
*Image can be png or svg
-width: 363 pixels
-height: 236 pixels
eg:-
CloseCard: require('../../assets/ID-closed.png')
```
To change the OpenCard card details background
```
OpenCard = require(path of image you want to use, in string format)
*Image can be png or svg
-width: 363 pixels
-height: 623 pixels
eg:-
OpenCard: require('../../assets/ID-open.png')
```
## **Colors :**
To change the color of TabItemText
![Image](../../../assets/img3.png 'Image')
```
export const DefaultTheme = {
Colors: {
TabItemText: colors.Orange,
...
}
}
```
To change the color of Details Label Text
![Image](../../../assets/img1.png 'Image')
```
export const DefaultTheme = {
Colors: {
DetailsLabel: colors.Orange,
...
}
}
```
To change the color of Details Text
![Image](../../../assets/img1.png 'Image')
```
export const DefaultTheme = {
Colors: {
Details: colors.Orange,
...
}
}
```
To change the color of AddId Button Text and Background
![Image](../../../assets/img4.png 'Image')
```
export const DefaultTheme = {
Colors: {
AddIdBtnBg: colors.Orange,
AddIdBtnTxt: colors.Orange,
...
}
}
```
To change the color of Icons
![Image](../../../assets/img6.png 'Image')
```
export const DefaultTheme = {
Colors: {
Icon: colors.Orange,
...
}
}
```
To change the Background Color of Icons
![Image](../../../assets/img7.png 'Image')
```
export const DefaultTheme = {
Colors: {
IconBg: colors.Orange,
...
}
}
```
To change the Color of Loading Transition
![Image](../../../assets/img8.png 'Image')
```
export const DefaultTheme = {
Colors: {
Loading: colors.Orange,
...
}
}
```
To change the Color of noUinText
![Image](../../../assets/img9.png 'Image')
```
export const DefaultTheme = {
Colors: {
noUinText: colors.Orange,
...
}
}
```
To change the Color of profileLabel
![Image](../../../assets/img10.png 'Image')
```
export const DefaultTheme = {
Colors: {
profileLabel: colors.Black,
...
}
}
```
To change the Color of profileValue and profileAuthFactorUnlock
![Image](../../../assets/img11.png 'Image')
```
export const DefaultTheme = {
Colors: {
profileAuthFactorUnlock: colors.Grey,
profileValue: colors.Grey,
...
}
}
```
To change the Color of profileVersion
![Image](../../../assets/img12.png 'Image')
```
export const DefaultTheme = {
Colors: {
profileVersion: colors.Grey,
...
}
}
```

View File

@@ -14,6 +14,7 @@
},
"VcDetails": {
"generatedOn": "Generated on",
"uin":"UIN",
"status": "Status",
"valid": "Valid",
"photo": "Photo",
@@ -67,6 +68,22 @@
}
}
},
"GetVcModal": {
"retrievingId": "Retrieving UID/VID...",
"applicationProcessing": "Your application is still being processed. Please try again after a few days",
"errors": {
"input": {
"empty": "The input cannot be empty",
"invalidFormat": "The input format is incorrect"
},
"backend": {
"invalidOtp": "OTP is invalid",
"applicationProcessing": "Your application is still being processed. Please try again after a few days",
"invalidAid": "AID is not ready",
"timeOut": "Requesting Time Out"
}
}
},
"DownloadingVcModal": {
"header": "Downloading your {{vcLabel}}",
"bodyText": "This may take some time, we will notify you when your {{vcLabel}} has been downloaded and is available",
@@ -75,7 +92,13 @@
"IdInputModal": {
"header": "Enter the MOSIP-provided UIN or VID of the {{vcLabel}} you wish to retrieve",
"generateVc": "Generate {{vcLabel}}",
"enterId": "Enter your {{idType}}"
"enterId": "Enter your {{idType}}",
"noUIN/VID": "Don't have your UIN/VID? Get it here"
},
"GetIdInputModal": {
"header": "To retrieve your UIN or VID, enter your Application {{vcLabel}} number",
"getUIN": "Get UIN/VID",
"applicationId": "Application {{vcLabel}} number"
},
"OtpVerificationModal": {
"enterOtp": "Enter the 6-digit verification code we sent you"

View File

@@ -49,6 +49,7 @@ const model = createModel(
REJECT: () => ({}),
CANCEL: () => ({}),
DISMISS: () => ({}),
GOBACK: () => ({}),
VC_RECEIVED: (vc: VC) => ({ vc }),
CONNECTED: () => ({}),
DISCONNECT: () => ({}),
@@ -241,6 +242,7 @@ export const requestMachine = model.createMachine(
},
on: {
DISMISS: 'navigatingToHome',
GOBACK: '#clearingConnection',
},
},
rejected: {
@@ -255,6 +257,7 @@ export const requestMachine = model.createMachine(
},
},
navigatingToHome: {},
navigatingToTimeBasedRequest: {},
},
exit: ['disconnect'],
},

View File

@@ -559,6 +559,10 @@ export function selectVerifiableCredential(state: State) {
return state.context.verifiableCredential;
}
export function selectContext(state: State) {
return state.context;
}
export function selectIsEditingTag(state: State) {
return state.matches('editingTag');
}

11
package-lock.json generated
View File

@@ -59,6 +59,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-uuid": "^2.0.1",
"react-native-vector-icons": "^8.1.0",
@@ -21223,6 +21224,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",
@@ -44044,6 +44050,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

@@ -63,6 +63,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-uuid": "^2.0.1",
"react-native-vector-icons": "^8.1.0",
@@ -90,7 +91,6 @@
"name": "mosip-resident-app",
"version": "1.0.0",
"lint-staged": {
"*.{ts,tsx}": "eslint --cache --fix",
"*.{ts,tsx,js,css,md}": "prettier --write",
"*.strings.json": "node scripts/compile-strings.js"
},

BIN
purpleAassets/_cart_sel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
purpleAassets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
purpleAassets/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -7,6 +7,7 @@ import { HomeScreen } from '../screens/Home/HomeScreen';
import { ProfileScreen } from '../screens/Profile/ProfileScreen';
import { ScanScreen } from '../screens/Scan/ScanScreen';
import { RootStackParamList } from './index';
import { TimerBasedRequestScreen } from '../screens/Request/TimerBasedRequestScreen';
import { RequestLayout } from '../screens/Request/RequestLayout';
export const mainRoutes: TabScreen[] = [
@@ -31,6 +32,11 @@ export const mainRoutes: TabScreen[] = [
headerShown: false,
},
},
{
name: 'TimerBasedRequest',
component: TimerBasedRequestScreen,
icon: 'timer',
},
{
name: 'Profile',
component: ProfileScreen,
@@ -44,6 +50,7 @@ export type MainBottomTabParamList = {
};
Scan: undefined;
Request: undefined;
TimerBasedRequest: undefined;
Profile: undefined;
};

View File

@@ -8,7 +8,7 @@ import { LanguageSelector } from '../components/LanguageSelector';
import { authRoutes, baseRoutes } from '../routes';
import { useAppLayout } from './AppLayoutController';
import { Icon } from 'react-native-elements';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
import { StatusBar } from 'react-native';
const { Navigator, Screen } = createNativeStackNavigator();
@@ -22,7 +22,7 @@ export const AppLayout: React.FC = () => {
headerShadowVisible: false,
headerRight: () => (
<LanguageSelector
triggerComponent={<Icon name="language" color={Colors.Orange} />}
triggerComponent={<Icon name="language" color={Theme.Colors.Icon} />}
/>
),
headerBackVisible: false,

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Icon } from 'react-native-elements';
import { MessageOverlay } from '../components/MessageOverlay';
import { Button, Centered, Column, Text } from '../components/ui';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
import { RootRouteProps } from '../routes';
import { useAuthScreen } from './AuthScreenController';
@@ -12,7 +12,10 @@ export const AuthScreen: React.FC<RootRouteProps> = (props) => {
const controller = useAuthScreen(props);
return (
<Column fill padding={[32, 32, 32, 32]} backgroundColor={Colors.White}>
<Column
fill
padding={[32, 32, 32, 32]}
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
@@ -22,7 +25,7 @@ export const AuthScreen: React.FC<RootRouteProps> = (props) => {
<Text align="center">{t('header')}</Text>
</Column>
<Centered fill>
<Icon name="fingerprint" size={180} color={Colors.Orange} />
<Icon name="fingerprint" size={180} color={Theme.Colors.Icon} />
</Centered>
<Column>
<Button

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
import { Button, Centered, Column } from '../components/ui';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
import { RootRouteProps } from '../routes';
import { useBiometricScreen } from './BiometricScreenController';
import { Passcode } from '../components/Passcode';
@@ -13,10 +13,14 @@ export const BiometricScreen: React.FC<RootRouteProps> = (props) => {
const controller = useBiometricScreen(props);
return (
<Column fill pY={32} pX={32} backgroundColor={Colors.White}>
<Column
fill
pY={32}
pX={32}
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Centered fill>
<TouchableOpacity onPress={controller.useBiometrics}>
<Icon name="fingerprint" size={180} color={Colors.Orange} />
<Icon name="fingerprint" size={180} color={Theme.Colors.Icon} />
</TouchableOpacity>
</Centered>

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { Tab } from 'react-native-elements';
import { Column, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { Theme } from '../../components/ui/styleUtils';
import { HomeRouteProps } from '../../routes/main';
import { HistoryTab } from './HistoryTab';
import { MyVcsTab } from './MyVcsTab';
@@ -11,19 +10,8 @@ import { ViewVcModal } from './ViewVcModal';
import { useHomeScreen } from './HomeScreenController';
import { TabRef } from './HomeScreenMachine';
import { useTranslation } from 'react-i18next';
const styles = StyleSheet.create({
tabIndicator: {
backgroundColor: Colors.Orange,
},
tabContainer: {
backgroundColor: 'transparent',
justifyContent: 'center',
},
tabView: {
flex: 1,
},
});
import { ActorRefFrom } from 'xstate';
import { vcItemMachine } from '../../machines/vcItem';
export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
const { t } = useTranslation('HomeScreen');
@@ -31,11 +19,11 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
return (
<React.Fragment>
<Column fill backgroundColor={Colors.LightGrey}>
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Tab
value={controller.activeTab}
onChange={controller.SELECT_TAB}
indicatorStyle={styles.tabIndicator}>
indicatorStyle={Theme.Styles.tabIndicator}>
{TabItem(t('myVcsTab', { vcLabel: controller.vcLabel.plural }))}
{TabItem(t('receivedVcsTab', { vcLabel: controller.vcLabel.plural }))}
{TabItem(t('historyTab'))}
@@ -45,13 +33,17 @@ 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}
/>
<HistoryTab
isVisible={controller.activeTab === 2}
vcItemActor={controller.selectedVc}
service={controller.tabRefs.history}
/>
</Column>
@@ -71,9 +63,9 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
function TabItem(title: string) {
return (
<Tab.Item
containerStyle={styles.tabContainer}
containerStyle={Theme.Styles.tabContainer}
title={
<Text align="center" color={Colors.Orange}>
<Text align="center" color={Theme.Colors.TabItemText}>
{title}
</Text>
}
@@ -84,4 +76,6 @@ function TabItem(title: string) {
export interface HomeScreenTabProps {
isVisible: boolean;
service: TabRef;
onSwipe?: () => void;
vcItemActor: ActorRefFrom<typeof vcItemMachine>;
}

View File

@@ -17,6 +17,7 @@ export const AddVcModal: React.FC<AddVcModalProps> = (props) => {
!controller.isAcceptingOtpInput && !controller.isRequestingCredential
}
onDismiss={controller.DISMISS}
onPress={props.onPress}
/>
<OtpVerificationModal

View File

@@ -26,4 +26,5 @@ export function useAddVcModal({ service }: AddVcModalProps) {
export interface AddVcModalProps {
service: ActorRefFrom<typeof AddVcModalMachine>;
onPress?: () => void;
}

View File

@@ -49,6 +49,11 @@ export const AddVcModalMachine =
},
id: 'AddVcModal',
initial: 'acceptingIdInput',
on: {
INPUT_ID: {
actions: 'setId',
},
},
states: {
acceptingIdInput: {
entry: ['setTransactionId', 'clearOtp'],
@@ -281,10 +286,13 @@ export const AddVcModalMachine =
services: {
requestOtp: async (context) => {
return request('POST', '/req/otp', {
id: 'mosip.identity.otp.internal',
individualId: context.id,
individualIdType: context.idType,
otpChannel: ['EMAIL', 'PHONE'],
metadata: {},
otpChannel: ['PHONE', 'EMAIL'],
requestTime: String(new Date().toISOString()),
transactionID: context.transactionId,
version: '1.0',
});
},

View File

@@ -9,7 +9,10 @@ export const DownloadingVcModal: React.FC<ModalProps> = (props) => {
const controller = useDownloadingVcModal();
return (
<Modal isVisible={props.isVisible} onDismiss={props.onDismiss}>
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
onShow={props.onShow}>
<Column fill pY={32} pX={24} align="space-between">
<Column fill>
<Text weight="semibold" align="center">

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { Icon, Input } from 'react-native-elements';
import { Button, Column, Row, Text } from '../../../components/ui';
import { Modal } from '../../../components/ui/Modal';
import { Theme } from '../../../components/ui/styleUtils';
import {
GetIdInputModalProps,
useGetIdInputModal,
} from './GetIdInputModalController';
import { useTranslation } from 'react-i18next';
export const GetIdInputModal: React.FC<GetIdInputModalProps> = (props) => {
const { t } = useTranslation('GetIdInputModal');
const controller = useGetIdInputModal(props);
const inputLabel = t('applicationId', {
vcLabel: controller.vcLabel.singular,
});
return (
<Modal onDismiss={props.onDismiss} isVisible={props.isVisible}>
<Column fill align="space-between" padding="32 24">
<Text align="center">
{t('header', { vcLabel: controller.vcLabel.singular })}
</Text>
<Column>
<Row crossAlign="flex-end">
<Column fill>
<Input
placeholder={!controller.id ? inputLabel : ''}
label={controller.id ? inputLabel : ''}
labelStyle={{
color: controller.isInvalid
? Theme.Colors.errorMessage
: Theme.Colors.textValue,
}}
style={Theme.Styles.placeholder}
value={controller.id}
keyboardType="number-pad"
rightIcon={
controller.isInvalid ? (
<Icon name="error" size={18} color="red" />
) : (
<Icon name="help" size={18} />
)
}
errorStyle={{ color: Theme.Colors.errorMessage }}
errorMessage={controller.idError}
onChangeText={controller.INPUT_ID}
ref={(node) => !controller.idInputRef && controller.READY(node)}
/>
</Column>
</Row>
<Button
title={t('getUIN', { vcLabel: controller.vcLabel.singular })}
margin="24 0 0 0"
onPress={controller.VALIDATE_INPUT}
loading={controller.isRequestingOtp}
/>
</Column>
</Column>
</Modal>
);
};

View File

@@ -0,0 +1,46 @@
import { useContext } from 'react';
import { useSelector } from '@xstate/react';
import { selectVcLabel } from '../../../machines/settings';
import { GlobalContext } from '../../../shared/GlobalContext';
import { ActorRefFrom } from 'xstate';
import { TextInput } from 'react-native';
import { ModalProps } from '../../../components/ui/Modal';
import {
GetVcModalEvents,
GetVcModalMachine,
selectIsAcceptingOtpInput,
selectIsInvalid,
selectIsRequestingOtp,
selectOtpError,
selectId,
selectIdError,
selectIdInputRef,
} from './GetVcModalMachine';
export function useGetIdInputModal({ service }: GetIdInputModalProps) {
const { appService } = useContext(GlobalContext);
const settingsService = appService.children.get('settings');
return {
id: useSelector(service, selectId),
idInputRef: useSelector(service, selectIdInputRef),
vcLabel: useSelector(settingsService, selectVcLabel),
idError: useSelector(service, selectIdError),
otpError: useSelector(service, selectOtpError),
isInvalid: useSelector(service, selectIsInvalid),
isAcceptingOtpInput: useSelector(service, selectIsAcceptingOtpInput),
isRequestingOtp: useSelector(service, selectIsRequestingOtp),
INPUT_ID: (id: string) => service.send(GetVcModalEvents.INPUT_ID(id)),
VALIDATE_INPUT: () => service.send(GetVcModalEvents.VALIDATE_INPUT()),
INPUT_OTP: (otp: string) => service.send(GetVcModalEvents.INPUT_OTP(otp)),
READY: (input: TextInput) => service.send(GetVcModalEvents.READY(input)),
DISMISS: () => service.send(GetVcModalEvents.DISMISS()),
};
}
export interface GetIdInputModalProps extends ModalProps {
service: ActorRefFrom<typeof GetVcModalMachine>;
onPress?: () => void;
}

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { MessageOverlay } from '../../../components/MessageOverlay';
import { useGetVcModal, GetVcModalProps } from './GetVcModalController';
import { OtpVerificationModal } from './OtpVerificationModal';
import { GetIdInputModal } from './GetIdInputModal';
import { useTranslation } from 'react-i18next';
export const GetVcModal: React.FC<GetVcModalProps> = (props) => {
const { t } = useTranslation('GetVcModal');
const controller = useGetVcModal(props);
return (
<React.Fragment>
<GetIdInputModal
service={props.service}
isVisible={true}
onDismiss={controller.DISMISS}
/>
<OtpVerificationModal
isVisible={controller.isAcceptingOtpInput}
onDismiss={controller.DISMISS}
onInputDone={controller.INPUT_OTP}
error={controller.otpError}
/>
<MessageOverlay
isVisible={controller.isRequestingCredential}
title={t('retrievingId')}
hasProgress
/>
</React.Fragment>
);
};

View File

@@ -0,0 +1,30 @@
import { useSelector } from '@xstate/react';
import { ActorRefFrom } from 'xstate';
import {
GetVcModalEvents,
GetVcModalMachine,
selectIsAcceptingOtpInput,
selectIsRequestingCredential,
selectOtpError,
selectIsAcceptingIdInput,
} from './GetVcModalMachine';
export function useGetVcModal({ service }: GetVcModalProps) {
return {
isRequestingCredential: useSelector(service, selectIsRequestingCredential),
otpError: useSelector(service, selectOtpError),
isAcceptingUinInput: useSelector(service, selectIsAcceptingIdInput),
isAcceptingOtpInput: useSelector(service, selectIsAcceptingOtpInput),
INPUT_OTP: (otp: string) => service.send(GetVcModalEvents.INPUT_OTP(otp)),
DISMISS: () => service.send(GetVcModalEvents.DISMISS()),
};
}
export interface GetVcModalProps {
service: ActorRefFrom<typeof GetVcModalMachine>;
onPress?: () => void;
}

View File

@@ -0,0 +1,334 @@
import { TextInput } from 'react-native';
import {
assign,
ErrorPlatformEvent,
EventFrom,
DoneInvokeEvent,
sendParent,
StateFrom,
} from 'xstate';
import { createModel } from 'xstate/lib/model';
import { BackendResponseError, request } from '../../../shared/request';
import i18n from '../../../i18n';
import { AddVcModalMachine } from './AddVcModalMachine';
import { GET_INDIVIDUAL_ID } from '../../../shared/constants';
const model = createModel(
{
idInputRef: null as TextInput,
id: '',
idError: '',
otp: '',
otpError: '',
transactionId: '',
child: null,
},
{
events: {
INPUT_ID: (id: string) => ({ id }),
INPUT_OTP: (otp: string) => ({ otp }),
VALIDATE_INPUT: () => ({}),
READY: (idInputRef: TextInput) => ({ idInputRef }),
DISMISS: () => ({}),
GOT_ID: (id: string) => ({ id }),
},
}
);
export const GetVcModalEvents = model.events;
export const GetVcModalMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QEEIQGoGMCyB7CAhgDYB0BmmYADgC4CWAdlAJITMNUCuNJATmAwhhejKAGIASgFFkAEQCaiUFVyw69XAyUgAHogC0ARgAsATgDMJAEynjhgBzmArE-v2AbDYA0IAJ4GrAHZ3EkCnKxMrc0NDc1NA+ycAXySfVAwcfGIyCmp6JlZ2Lh4AM1xMTjUmMR1YGgIaMDISxt4ACkMABk6ASjF0rDxCUnJKWlFCjm4SMoqqqG0VNQ0tJF0DWM6QwOjXQydjYyDbH38EIycQ+M6PI6t7TsNTdxS0tEGskdzxgrYpnjoECIYDEzAAcgAFACqABUAPrMWSLVTqOiabR6c6GdxOQIkRydcJRQnudweU6IezGEjGezxKxRQJPTamV4gAaZYY5Mb5Fh-YokQHAsToZAAGURyBhUgRkNhyOWaNWoExRisLmsuPMgSCgUC8XM9gpCCCThI5huNkNpme2qc5jZHKG2VGeQm-OmQpBAGUpGKpABheGIuEw+QQqQK1Hotaq-b2QwkTqmVzBTqBQnmdXGoyGKyheI6xnmYzmMmGR3vTku768yYCxgAN2IgNBcuDSLWS2jyvWWM6ZdCA6c6eMYQtuJz8WshuxHhcVkzDtS7Krzq+PPdRU9DGbRFboolsilMvB0JhUZWGIMpcs+1METHycSnWMxsfSdcZfM2pLFsNlYZOu3Jur824AruLYQGIvr+kGCKyKG4aRl2KJXrGGyEp0JCmPeOKJP+ZbGu4pg4XSOp0q+jgJKyK5Op8IE-Hy4F8GAACOnBwLyADyNBUGIECaE0Ta4AA1k09Fcq6TH1tM-AcVxoi8VQCAiZgDRKgA2p0AC6l5Kte-ZuNYpJ6par6XIExr2DYNI2bigRmDqJKAR8Um1lu-ysQpdRKXxYjCLwuC8CQVBEA0ZS8AAtiQkk1puYFefJnG+Uwymqbu5QaZo2l6ahioxiqBjxPYJAxFseZpnY0TGmYIROA43QPAOpkvHRa4MdJdYejQYiyMw3rYAN3r6YVfb6MY7jUtiWYllY7jYpE7jWWEZVPPcTILQ1FbtUBnUeWlfHgW255wtxMIQqNvZxnE1Iph4i4Wo8LXGqYhJJs1RwPj+32udWG6gVAynHf1g3DVdhkXDsSYJJ0VhmAyDi0rVGopqYVLww8jkmMubx7VyyWKUwAb8EIDD0MQAlCYKmXibFHUE+xKW8iTkACBTRAZY2WUrLlEMYVi2LYfc+xmDaJhklYxpdBaoSixE7gDjcxjJLtbnZITqVQKzZMcwFvBBSFYURcFMVxaQmss6T7N0MQXM81pun80VWKHImMS0sccPuHqUt+IgMTBGVDKLvquLUcYKQrgw+BwNo5uMd1LH8IIwiiM743YvqJBTQ+GZPDstJvv75zzZYYQKz9pYPG1ePqwDMk9TM5SVOn+U9pDsR5kOA4pjirjzU4OZl6E4QzbdhpbH9wFdZ5DZAmAGdxqW1JRM4Nrat0JimMaTJkfSwTGDcWY7XX-2J3PO57oCJBgFFtBnMoaEGQL+g7Hi9g6p49oq735jSzZT8jgEw+1worSOatz6z0Sg2SC+4IDN2ig0JeBhgj5hcIaT++wnhUiNCXEseILQ2TcGEDMP5T6rnxvFQGskILXwQQAI3IOJQQKDzgkUTJmDwOJFY4gsMaEcpEGoPBMGObU7hojT32glZiXkmxQTYW-DwJAMEkOwejZGJdJpAO1HSQ4U1QFSPcjI2h3lmZ+SoIo8qH9nC0gahYBIpJiLahwg+H25FXxxFrpQ+uF8YHcCsWQnOCN84GiLjmUs2wx5mGEdjIx1CmLA3+FYzwpEggPEmgaMueCzj6jxJ7A4r4VbagfPEi2TMiba2tuTW2RArGTXzA4cWJIdieGlo8UqEQDgYPTAQspJBBIMEXu3dCLt9B0ksIrRwtgDi4RtO00scsDiHATCWDJZTAlBGCXnR4YSqQ5kEaPLphgdjpntFSKOSQgA */
model.createMachine(
{
tsTypes: {} as import('./GetVcModalMachine.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
id: 'GetVcModal',
initial: 'acceptingIdInput',
states: {
acceptingIdInput: {
entry: ['setTransactionId', 'clearOtp'],
initial: 'rendering',
states: {
rendering: {
on: {
READY: {
actions: 'setIdInputRef',
target: 'focusing',
},
},
},
focusing: {
after: {
'100': {
description:
'Small delay to properly show the keyboard when focusing input.',
target: 'idle',
},
},
},
idle: {
entry: 'focusInput',
invoke: {
id: 'AddVcModal',
src: AddVcModalMachine,
},
on: {
INPUT_ID: {
actions: 'setId',
},
VALIDATE_INPUT: [
{
cond: 'isEmptyId',
target: '#GetVcModal.acceptingIdInput.invalid.empty',
},
{
cond: 'isWrongIdFormat',
target: '#GetVcModal.acceptingIdInput.invalid.format',
},
{
target: 'requestingOtp',
},
],
},
},
invalid: {
entry: 'focusInput',
states: {
empty: {
entry: 'setIdErrorEmpty',
},
format: {
entry: 'setIdErrorWrongFormat',
},
backend: {},
},
on: {
INPUT_ID: {
actions: ['setId', 'clearIdError'],
target: 'idle',
},
VALIDATE_INPUT: [
{
cond: 'isEmptyId',
target: '.empty',
},
{
cond: 'isWrongIdFormat',
target: '.format',
},
{
target: 'requestingOtp',
},
],
},
},
requestingOtp: {
invoke: {
src: 'requestOtp',
onDone: [
{
target: '#GetVcModal.acceptingOtpInput',
},
],
onError: [
{
actions: 'setIdBackendError',
target: '#GetVcModal.acceptingIdInput.invalid.backend',
},
],
},
},
},
on: {
DISMISS: {
actions: 'forwardToParent',
},
},
},
acceptingOtpInput: {
entry: 'clearOtp',
on: {
INPUT_OTP: {
actions: 'setOtp',
target: 'requestingUinVid',
},
DISMISS: {
target: '#GetVcModal.acceptingIdInput.idle',
},
},
},
requestingUinVid: {
invoke: {
src: 'requestingUinVid',
onDone: [
{
actions: ['setIndividualId'],
target: 'done',
},
],
onError: [
{
actions: 'setIdBackendError',
cond: 'isIdInvalid',
target: '#GetVcModal.acceptingIdInput.invalid.backend',
},
{
actions: 'setOtpError',
target: 'acceptingOtpInput',
},
],
},
},
done: {
type: 'final',
},
},
},
{
actions: {
forwardToParent: sendParent('DISMISS'),
setId: model.assign({
id: (_context, event) => event.id,
}),
setOtp: model.assign({
otp: (_context, event) => event.otp,
}),
setTransactionId: assign({
transactionId: () => String(new Date().valueOf()).substring(3, 13),
}),
setIndividualId: (_context, event) =>
GET_INDIVIDUAL_ID((event as DoneInvokeEvent<string>).data),
setIdBackendError: assign({
idError: (context, event) => {
if ((event as ErrorPlatformEvent).data == 'IDA-MLC-001') {
return i18n.t('errors.backend.timeOut', { ns: 'GetVcModal' });
}
const message = (event as ErrorPlatformEvent).data.message;
const ID_ERRORS_MAP = {
'AID is not ready': 'applicationProcessing',
};
return ID_ERRORS_MAP[message]
? i18n.t(`errors.backend.${ID_ERRORS_MAP[message]}`, {
ns: 'GetVcModal',
})
: message;
},
}),
clearIdError: model.assign({ idError: '' }),
setIdErrorEmpty: model.assign({
idError: () => i18n.t('errors.input.empty', { ns: 'GetVcModal' }),
}),
setIdErrorWrongFormat: model.assign({
idError: () =>
i18n.t('errors.input.invalidFormat', { ns: 'GetVcModal' }),
}),
setOtpError: assign({
otpError: (_context, event) => {
const message = (event as ErrorPlatformEvent).data.message;
const OTP_ERRORS_MAP = {
'OTP is invalid': 'invalidOtp',
};
return OTP_ERRORS_MAP[message]
? i18n.t(`errors.backend.${OTP_ERRORS_MAP[message]}`, {
ns: 'GetVcModal',
})
: message;
},
}),
setIdInputRef: model.assign({
idInputRef: (_context, event) => event.idInputRef,
}),
clearOtp: assign({ otp: '' }),
focusInput: (context) => context.idInputRef.focus(),
},
services: {
requestOtp: async (context) => {
return await request('POST', '/req/individualId/otp', {
id: 'mosip.identity.otp.internal',
aid: context.id,
metadata: {},
otpChannel: ['EMAIL', 'PHONE'],
requestTime: String(new Date().toISOString()),
transactionID: context.transactionId,
version: '1.0',
});
},
requestingUinVid: async (context) => {
const response = await request('POST', '/aid/get-individual-id', {
aid: context.id,
otp: context.otp,
transactionID: context.transactionId,
});
return response.response.individualId;
},
},
guards: {
isEmptyId: ({ id }) => !id || !id.length,
isWrongIdFormat: ({ id }) => !/^\d{14,29}$/.test(id),
isIdInvalid: (_context, event: unknown) =>
['RES-SER-449', 'IDA-MLC-001'].includes(
(event as BackendResponseError).name
),
},
}
);
type State = StateFrom<typeof GetVcModalMachine>;
export function selectId(state: State) {
return state.context.id;
}
export function selectIdInputRef(state: State) {
return state.context.idInputRef;
}
export function selectIdError(state: State) {
return state.context.idError;
}
export function selectOtpError(state: State) {
return state.context.otpError;
}
export function selectIsAcceptingIdInput(state: State) {
return state.matches('acceptingIdInput');
}
export function selectIsInvalid(state: State) {
return state.matches('acceptingIdInput.invalid');
}
export function selectIsAcceptingOtpInput(state: State) {
return state.matches('acceptingOtpInput');
}
export function selectIsRequestingOtp(state: State) {
return state.matches('acceptingIdInput.requestingOtp');
}
export function selectIsRequestingCredential(state: State) {
return state.matches('requestingUinVid');
}

View File

@@ -0,0 +1,102 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'done.invoke.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]': {
type: 'done.invoke.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.GetVcModal.requestingUinVid:invocation[0]': {
type: 'done.invoke.GetVcModal.requestingUinVid:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]': {
type: 'error.platform.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]';
data: unknown;
};
'error.platform.GetVcModal.requestingUinVid:invocation[0]': {
type: 'error.platform.GetVcModal.requestingUinVid:invocation[0]';
data: unknown;
};
'xstate.after(100)#GetVcModal.acceptingIdInput.focusing': {
type: 'xstate.after(100)#GetVcModal.acceptingIdInput.focusing';
};
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
requestOtp: 'done.invoke.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]';
requestingUinVid: 'done.invoke.GetVcModal.requestingUinVid:invocation[0]';
};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
};
'eventsCausingActions': {
clearIdError: 'INPUT_ID';
clearOtp:
| 'DISMISS'
| 'done.invoke.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]'
| 'error.platform.GetVcModal.requestingUinVid:invocation[0]'
| 'xstate.init';
focusInput:
| 'DISMISS'
| 'INPUT_ID'
| 'VALIDATE_INPUT'
| 'error.platform.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]'
| 'error.platform.GetVcModal.requestingUinVid:invocation[0]'
| 'xstate.after(100)#GetVcModal.acceptingIdInput.focusing';
forwardToParent: 'DISMISS';
setId: 'INPUT_ID';
setIdBackendError:
| 'error.platform.GetVcModal.acceptingIdInput.requestingOtp:invocation[0]'
| 'error.platform.GetVcModal.requestingUinVid:invocation[0]';
setIdErrorEmpty: 'VALIDATE_INPUT';
setIdErrorWrongFormat: 'VALIDATE_INPUT';
setIdInputRef: 'READY';
setIndividualId: 'done.invoke.GetVcModal.requestingUinVid:invocation[0]';
setOtp: 'INPUT_OTP';
setOtpError: 'error.platform.GetVcModal.requestingUinVid:invocation[0]';
setTransactionId:
| 'DISMISS'
| 'error.platform.GetVcModal.requestingUinVid:invocation[0]'
| 'xstate.init';
};
'eventsCausingServices': {
requestOtp: 'VALIDATE_INPUT';
requestingUinVid: 'INPUT_OTP';
};
'eventsCausingGuards': {
isEmptyId: 'VALIDATE_INPUT';
isIdInvalid: 'error.platform.GetVcModal.requestingUinVid:invocation[0]';
isWrongIdFormat: 'VALIDATE_INPUT';
};
'eventsCausingDelays': {};
'matchesStates':
| 'acceptingIdInput'
| 'acceptingIdInput.focusing'
| 'acceptingIdInput.idle'
| 'acceptingIdInput.invalid'
| 'acceptingIdInput.invalid.backend'
| 'acceptingIdInput.invalid.empty'
| 'acceptingIdInput.invalid.format'
| 'acceptingIdInput.rendering'
| 'acceptingIdInput.requestingOtp'
| 'acceptingOtpInput'
| 'done'
| 'requestingUinVid'
| {
acceptingIdInput?:
| 'focusing'
| 'idle'
| 'invalid'
| 'rendering'
| 'requestingOtp'
| { invalid?: 'backend' | 'empty' | 'format' };
};
'tags': never;
}

View File

@@ -3,19 +3,34 @@ import { Icon, Input } from 'react-native-elements';
import { Picker } from '@react-native-picker/picker';
import { Button, Column, Row, Text } from '../../../components/ui';
import { Modal } from '../../../components/ui/Modal';
import { Colors } from '../../../components/ui/styleUtils';
import { Theme } from '../../../components/ui/styleUtils';
import { IdInputModalProps, useIdInputModal } from './IdInputModalController';
import { useTranslation } from 'react-i18next';
import { KeyboardAvoidingView, Platform } from 'react-native';
import { TouchableOpacity } from 'react-native';
import { individualId } from '../../../shared/constants';
import { GET_INDIVIDUAL_ID } from '../../../shared/constants';
export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
const { t } = useTranslation('IdInputModal');
const controller = useIdInputModal(props);
const setIndividualID = () => {
controller.INPUT_ID(individualId);
};
const dismissInput = () => {
props.onDismiss();
GET_INDIVIDUAL_ID('');
};
const inputLabel = t('enterId', { idType: controller.idType });
return (
<Modal onDismiss={props.onDismiss} isVisible={props.isVisible}>
<Modal
onDismiss={dismissInput}
isVisible={props.isVisible}
onShow={setIndividualID}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
@@ -30,7 +45,9 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
style={{
borderBottomWidth: 1,
borderColor:
Platform.OS === 'ios' ? 'transparent' : Colors.Grey,
Platform.OS === 'ios'
? 'transparent'
: Theme.Colors.IdInputModalBorder,
bottom: Platform.OS === 'ios' ? 50 : 24,
height: Platform.OS === 'ios' ? 100 : 'auto',
}}>
@@ -46,7 +63,9 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
placeholder={!controller.id ? inputLabel : ''}
label={controller.id ? inputLabel : ''}
labelStyle={{
color: controller.isInvalid ? Colors.Red : Colors.Black,
color: controller.isInvalid
? Theme.Colors.errorMessage
: Theme.Colors.textValue,
}}
value={controller.id}
keyboardType="number-pad"
@@ -55,7 +74,7 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
<Icon name="error" size={18} color="red" />
) : null
}
errorStyle={{ color: Colors.Red }}
errorStyle={{ color: Theme.Colors.errorMessage }}
errorMessage={controller.idError}
onChangeText={controller.INPUT_ID}
ref={(node) =>
@@ -70,6 +89,14 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
onPress={controller.VALIDATE_INPUT}
loading={controller.isRequestingOtp}
/>
{!controller.id && (
<TouchableOpacity
activeOpacity={1}
onPress={props.onPress}
style={Theme.Styles.getId}>
<Text color={Theme.Colors.AddIdBtnBg}>{t('noUIN/VID')}</Text>
</TouchableOpacity>
)}
</Column>
</Column>
</KeyboardAvoidingView>

View File

@@ -47,4 +47,5 @@ export function useIdInputModal({ service }: IdInputModalProps) {
export interface IdInputModalProps extends ModalProps {
service: ActorRefFrom<typeof AddVcModalMachine>;
onPress?: () => void;
}

View File

@@ -4,7 +4,7 @@ import { Icon } from 'react-native-elements';
import { PinInput } from '../../../components/PinInput';
import { Column, Text } from '../../../components/ui';
import { Modal, ModalProps } from '../../../components/ui/Modal';
import { Colors } from '../../../components/ui/styleUtils';
import { Theme } from '../../../components/ui/styleUtils';
export const OtpVerificationModal: React.FC<OtpVerificationModalProps> = (
props
@@ -14,10 +14,13 @@ export const OtpVerificationModal: React.FC<OtpVerificationModalProps> = (
return (
<Modal isVisible={props.isVisible} onDismiss={props.onDismiss}>
<Column fill padding="32">
<Icon name="lock" color={Colors.Orange} size={60} />
<Icon name="lock" color={Theme.Colors.Icon} size={60} />
<Column fill align="space-between">
<Text align="center">{t('enterOtp')}</Text>
<Text align="center" color={Colors.Red} margin="16 0 0 0">
<Text
align="center"
color={Theme.Colors.errorMessage}
margin="16 0 0 0">
{props.error}
</Text>
<PinInput length={6} onDone={props.onInputDone} />

View File

@@ -1,20 +1,32 @@
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 { Theme } from '../../components/ui/styleUtils';
import { RefreshControl } from 'react-native';
import { useMyVcsTab } from './MyVcsTabController';
import { HomeScreenTabProps } from './HomeScreen';
import { AddVcModal } from './MyVcs/AddVcModal';
import { GetVcModal } from './MyVcs/GetVcModal';
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';
import { GET_INDIVIDUAL_ID } from '../../shared/constants';
export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
const { t } = useTranslation('MyVcsTab');
const controller = useMyVcsTab(props);
const getId = () => {
controller.DISMISS();
controller.GET_VC();
};
const clearIndividualId = () => {
GET_INDIVIDUAL_ID('');
};
return (
<React.Fragment>
<Column fill style={{ display: props.isVisible ? 'flex' : 'none' }}>
@@ -23,6 +35,7 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
<React.Fragment>
<Column
scroll
margin="0 0 20 0"
refreshControl={
<RefreshControl
refreshing={controller.isRefreshingVcs}
@@ -30,23 +43,25 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
/>
}>
{controller.vcKeys.map((vcKey) => (
<VcItem
<UpdatedVcItem
key={vcKey}
vcKey={vcKey}
margin="0 2 8 2"
onPress={controller.VIEW_VC}
/>
))}
</Column>
<Column elevation={2} margin="0 2">
<Button
type="clear"
disabled={controller.isRefreshingVcs}
title={t('addVcButton', {
vcLabel: controller.vcLabel.singular,
})}
onPress={controller.ADD_VC}
/>
<GestureRecognizer onSwipeLeft={props.onSwipe}>
<Column elevation={2} margin="10 2 0 2">
<Button
type="clear"
disabled={controller.isRefreshingVcs}
title={t('addVcButton', {
vcLabel: controller.vcLabel.singular,
})}
onPress={controller.ADD_VC}
/>
</Column>
</GestureRecognizer>
</Column>
</React.Fragment>
)}
@@ -56,7 +71,7 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
<Text weight="semibold" margin="0 0 8 0">
{t('generateVc', { vcLabel: controller.vcLabel.plural })}
</Text>
<Text color={Colors.Grey} align="center">
<Text color={Theme.Colors.textLabel} align="center">
{t('generateVcDescription', {
vcLabel: controller.vcLabel.singular,
})}
@@ -64,28 +79,38 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
<Icon
name="arrow-downward"
containerStyle={{ marginTop: 20 }}
color={Colors.Orange}
color={Theme.Colors.Icon}
/>
</Centered>
<Button
disabled={controller.isRefreshingVcs}
title={t('addVcButton', {
vcLabel: controller.vcLabel.singular,
})}
onPress={controller.ADD_VC}
/>
<GestureRecognizer onSwipeLeft={props.onSwipe}>
<Button
type="addId"
disabled={controller.isRefreshingVcs}
title={t('addVcButton', {
vcLabel: controller.vcLabel.singular,
})}
onPress={controller.ADD_VC}
/>
</GestureRecognizer>
</React.Fragment>
)}
</Column>
</Column>
{controller.AddVcModalService && (
<AddVcModal service={controller.AddVcModalService} />
<AddVcModal service={controller.AddVcModalService} onPress={getId} />
)}
{controller.GetVcModalService && (
<GetVcModal service={controller.GetVcModalService} />
)}
{controller.isRequestSuccessful && (
<DownloadingVcModal
isVisible={controller.isRequestSuccessful}
onDismiss={controller.DISMISS}
onShow={clearIndividualId}
/>
)}

View File

@@ -7,7 +7,7 @@ import {
selectMyVcs,
VcEvents,
} from '../../machines/vc';
import { vcItemMachine } from '../../machines/vcItem';
import { vcItemMachine } from '../../machines/vcItem';
import { GlobalContext } from '../../shared/GlobalContext';
import { HomeScreenTabProps } from './HomeScreen';
import {
@@ -16,6 +16,7 @@ import {
selectAddVcModal,
selectIsOnboarding,
selectIsRequestSuccessful,
selectGetVcModal,
} from './MyVcsTabMachine';
export function useMyVcsTab(props: HomeScreenTabProps) {
@@ -27,6 +28,7 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
return {
service,
AddVcModalService: useSelector(service, selectAddVcModal),
GetVcModalService: useSelector(service, selectGetVcModal),
vcKeys: useSelector(vcService, selectMyVcs),
vcLabel: useSelector(settingsService, selectVcLabel),
@@ -39,9 +41,11 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
ADD_VC: () => service.send(MyVcsTabEvents.ADD_VC()),
GET_VC: () => service.send(MyVcsTabEvents.GET_VC()),
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
VIEW_VC: (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
VIEW_VC: (vcRef: ActorRefFrom<typeof vcItemMachine>) => {
return service.send(MyVcsTabEvents.VIEW_VC(vcRef));
},

View File

@@ -16,6 +16,7 @@ import {
ONBOARDING_STATUS_STORE_KEY,
} from '../../shared/constants';
import { AddVcModalMachine } from './MyVcs/AddVcModalMachine';
import { GetVcModalMachine } from './MyVcs/GetVcModalMachine';
const model = createModel(
{
@@ -30,6 +31,7 @@ const model = createModel(
DISMISS: () => ({}),
STORE_RESPONSE: (response?: unknown) => ({ response }),
ADD_VC: () => ({}),
GET_VC: () => ({}),
ONBOARDING_DONE: () => ({}),
},
}
@@ -75,6 +77,7 @@ export const MyVcsTabMachine = model.createMachine(
on: {
ADD_VC: 'addingVc',
VIEW_VC: 'viewingVc',
GET_VC: 'gettingVc',
},
},
viewingVc: {
@@ -111,6 +114,20 @@ export const MyVcsTabMachine = model.createMachine(
},
},
},
gettingVc: {
invoke: {
id: 'GetVcModal',
src: GetVcModalMachine,
onDone: 'addingVc',
},
on: {
DISMISS: 'idle',
},
initial: 'waitingForvcKey',
states: {
waitingForvcKey: {},
},
},
},
},
{
@@ -168,6 +185,10 @@ export function selectAddVcModal(state: State) {
return state.children.AddVcModal as ActorRefFrom<typeof AddVcModalMachine>;
}
export function selectGetVcModal(state: State) {
return state.children.GetVcModal as ActorRefFrom<typeof GetVcModalMachine>;
}
export function selectIsOnboarding(state: State) {
return state.matches('onboarding');
}

View File

@@ -1,58 +1,14 @@
import React, { useRef, useContext } from 'react';
import AppIntroSlider from 'react-native-app-intro-slider';
import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native';
import { SafeAreaView, ScrollView, View } from 'react-native';
import { Icon, Overlay } from 'react-native-elements';
import { Button, Column, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { Theme } from '../../components/ui/styleUtils';
import { useSelector } from '@xstate/react';
import { GlobalContext } from '../../shared/GlobalContext';
import { selectVcLabel } from '../../machines/settings';
import { useTranslation } from 'react-i18next';
const styles = StyleSheet.create({
overlay: {
padding: 24,
bottom: 86,
backgroundColor: 'transparent',
shadowColor: 'transparent',
},
slide: {
width: '100%',
padding: 20,
},
slider: {
backgroundColor: Colors.Orange,
minHeight: 300,
width: '100%',
margin: 0,
borderRadius: 4,
},
appSlider: {},
title: {
color: Colors.White,
marginBottom: 20,
fontFamily: 'Poppins_700Bold',
},
text: {
color: Colors.White,
},
paginationContainer: {
margin: 10,
},
paginationDots: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
},
closeIcon: {
alignItems: 'flex-end',
end: 16,
top: 40,
zIndex: 1,
},
});
export const OnboardingOverlay: React.FC<OnboardingProps> = (props) => {
const slider = useRef<AppIntroSlider>();
@@ -90,10 +46,12 @@ export const OnboardingOverlay: React.FC<OnboardingProps> = (props) => {
const renderItem = ({ item }) => {
return (
<View style={styles.slide}>
<View style={Theme.OnboardingOverlayStyles.slide}>
<ScrollView showsVerticalScrollIndicator={true}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.text}>{item.text}</Text>
<Text style={Theme.OnboardingOverlayStyles.sliderTitle}>
{item.title}
</Text>
<Text style={Theme.OnboardingOverlayStyles.text}>{item.text}</Text>
{item.footer}
</ScrollView>
</View>
@@ -102,14 +60,14 @@ export const OnboardingOverlay: React.FC<OnboardingProps> = (props) => {
const renderPagination = (activeIndex: number) => {
return (
<View style={styles.paginationContainer}>
<View style={Theme.OnboardingOverlayStyles.paginationContainer}>
<SafeAreaView>
<View style={styles.paginationDots}>
<View style={Theme.OnboardingOverlayStyles.paginationDots}>
{slides.length > 1 &&
slides.map((_, i) => (
<Icon
key={i}
color={Colors.White}
color={Theme.Colors.OnboardingCircleIcon}
size={10}
name="circle"
style={{ opacity: i === activeIndex ? 1 : 0.6, margin: 2 }}
@@ -124,21 +82,21 @@ export const OnboardingOverlay: React.FC<OnboardingProps> = (props) => {
return (
<Overlay
isVisible={props.isVisible}
overlayStyle={styles.overlay}
overlayStyle={Theme.OnboardingOverlayStyles.overlay}
transparent
onBackdropPress={props.onDone}>
<Column fill align="flex-end">
<Icon
name="close"
color={Colors.White}
color={Theme.Colors.OnboardingCloseIcon}
onPress={props.onDone}
containerStyle={styles.closeIcon}
containerStyle={Theme.OnboardingOverlayStyles.closeIcon}
/>
<View style={styles.slider}>
<View style={Theme.OnboardingOverlayStyles.slider}>
<AppIntroSlider
renderItem={renderItem}
data={slides}
style={styles.appSlider}
style={Theme.OnboardingOverlayStyles.appSlider}
ref={slider}
renderPagination={renderPagination}
/>

View File

@@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next';
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 { Theme } from '../../components/ui/styleUtils';
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"
@@ -44,7 +44,7 @@ export const ReceivedVcsTab: React.FC<HomeScreenTabProps> = (props) => {
vcLabel: controller.vcLabel.plural,
})}
</Text>
<Text align="center" color={Colors.Grey}>
<Text align="center" color={Theme.Colors.textLabel}>
{t('noReceivedVcsText', {
vcLabel: controller.vcLabel.singular,
})}

View File

@@ -3,14 +3,14 @@ import { Icon } from 'react-native-elements';
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 { Theme } from '../../components/ui/styleUtils';
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');
@@ -23,11 +23,15 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
headerTitle={controller.vc.tag || controller.vc.id}
headerElevation={2}
headerRight={
<Icon name="edit" onPress={controller.EDIT_TAG} color={Colors.Orange} />
<Icon
name="edit"
onPress={controller.EDIT_TAG}
color={Theme.Colors.Icon}
/>
}>
<Column scroll backgroundColor={Colors.LightGrey}>
<Column>
<VcDetails vc={controller.vc} />
<Column scroll>
<Column fill>
<UpdatedVcDetails vc={controller.vc} />
</Column>
</Column>

View File

@@ -7,7 +7,7 @@ import { Icon } from 'react-native-elements';
import { mainRoutes } from '../routes/main';
import { RootRouteProps } from '../routes';
import { LanguageSelector } from '../components/LanguageSelector';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
import { useTranslation } from 'react-i18next';
const { Navigator, Screen } = createBottomTabNavigator();
@@ -16,10 +16,11 @@ export const MainLayout: React.FC<RootRouteProps> = () => {
const { t } = useTranslation('MainLayout');
const options: BottomTabNavigationOptions = {
headerLeft: () => <Icon name="notifications" color={Theme.Colors.Icon} />,
headerLeftContainerStyle: { paddingStart: 16 },
headerRight: () => (
<LanguageSelector
triggerComponent={<Icon name="language" color={Colors.Orange} />}
triggerComponent={<Icon name="language" color={Theme.Colors.Icon} />}
/>
),
headerRightContainerStyle: { paddingEnd: 16 },
@@ -47,7 +48,7 @@ export const MainLayout: React.FC<RootRouteProps> = () => {
tabBarIcon: ({ focused }) => (
<Icon
name={route.icon}
color={focused ? Colors.Orange : Colors.Grey}
color={focused ? Theme.Colors.IconBg : Theme.Colors.Icon}
reverse={focused}
/>
),

View File

@@ -4,7 +4,7 @@ import { Icon } from 'react-native-elements';
import { MAX_PIN, PasscodeVerify } from '../components/PasscodeVerify';
import { PinInput } from '../components/PinInput';
import { Column, Text } from '../components/ui';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
import { PasscodeRouteProps } from '../routes';
import { usePasscodeScreen } from './PasscodeScreenController';
@@ -30,8 +30,11 @@ export const PasscodeScreen: React.FC<PasscodeRouteProps> = (props) => {
);
return (
<Column fill padding="32" backgroundColor={Colors.White}>
<Icon name="lock" color={Colors.Orange} size={60} />
<Column
fill
padding="32"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Icon name="lock" color={Theme.Colors.Icon} size={60} />
{props.route.params?.setup ? (
<Column fill align="space-between" width="100%">
{passcodeSetup}
@@ -48,7 +51,7 @@ export const PasscodeScreen: React.FC<PasscodeRouteProps> = (props) => {
)}
<Column fill>
<Text align="center" color={Colors.Red}>
<Text align="center" color={Theme.Colors.errorMessage}>
{controller.error}
</Text>
</Column>

View File

@@ -4,7 +4,7 @@ import { Dimensions, Image, StyleSheet, View } from 'react-native';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import Markdown from 'react-native-simple-markdown';
import { Button, Text, Row } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { Theme } from '../../components/ui/styleUtils';
import creditsContent from '../../Credits.md';
export const Credits: React.FC<CreditsProps> = (props) => {
@@ -57,7 +57,7 @@ export const Credits: React.FC<CreditsProps> = (props) => {
<ListItem bottomDivider onPress={() => setIsViewing(true)}>
<ListItem.Content>
<ListItem.Title>
<Text>{props.label}</Text>
<Text color={props.color}>{props.label}</Text>
</ListItem.Title>
</ListItem.Content>
<Overlay
@@ -69,7 +69,7 @@ export const Credits: React.FC<CreditsProps> = (props) => {
<View style={styles.buttonContainer}>
<Button
type="clear"
icon={<Icon name="chevron-left" color={Colors.Orange} />}
icon={<Icon name="chevron-left" color={Theme.Colors.Icon} />}
title=""
onPress={() => setIsViewing(false)}
/>
@@ -90,4 +90,5 @@ export const Credits: React.FC<CreditsProps> = (props) => {
interface CreditsProps {
label: string;
color?: string;
}

View File

@@ -3,7 +3,7 @@ import { View } from 'react-native';
import { getVersion } from 'react-native-device-info';
import { ListItem, Switch } from 'react-native-elements';
import { Column, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { Theme } from '../../components/ui/styleUtils';
import { MainRouteProps } from '../../routes/main';
import { EditableListItem } from '../../components/EditableListItem';
import { MessageOverlay } from '../../components/MessageOverlay';
@@ -25,7 +25,7 @@ const LanguageSetting: React.FC = () => {
<Text>{t('language')}</Text>
</ListItem.Title>
</ListItem.Content>
<Text margin="0 12 0 0" color={Colors.Grey}>
<Text margin="0 12 0 0" color={Theme.Colors.profileLanguageValue}>
{SUPPORTED_LANGUAGES[i18next.language]}
</Text>
</ListItem>
@@ -38,7 +38,10 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
const { t } = useTranslation('ProfileScreen');
const controller = useProfileScreen(props);
return (
<Column fill padding="24 0" backgroundColor={Colors.LightGrey}>
<Column
fill
padding="24 0"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
@@ -58,27 +61,29 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
<ListItem bottomDivider disabled={!controller.canUseBiometrics}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('bioUnlock')}</Text>
<Text color={Theme.Colors.profileLabel}>{t('bioUnlock')}</Text>
</ListItem.Title>
</ListItem.Content>
<Switch
value={controller.isBiometricUnlockEnabled}
onValueChange={controller.useBiometrics}
color={Colors.Orange}
color={Theme.Colors.profileValue}
/>
</ListItem>
<ListItem bottomDivider disabled>
<ListItem.Content>
<ListItem.Title>
<Text color={Colors.Grey}>{t('authFactorUnlock')}</Text>
<Text color={Theme.Colors.profileAuthFactorUnlock}>
{t('authFactorUnlock')}
</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<Credits label={t('credits')} />
<Credits label={t('credits')} color={Theme.Colors.profileLabel} />
<ListItem bottomDivider onPress={controller.LOGOUT}>
<ListItem.Content>
<ListItem.Title>
<Text>{t('logout')}</Text>
<Text color={Theme.Colors.profileLabel}>{t('logout')}</Text>
</ListItem.Title>
</ListItem.Content>
</ListItem>
@@ -87,7 +92,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
margin="32 0 0 0"
align="center"
size="smaller"
color={Colors.Grey}>
color={Theme.Colors.profileVersion}>
Version: {getVersion()}
</Text>
{controller.backendInfo.application.name !== '' ? (
@@ -96,7 +101,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
weight="semibold"
align="center"
size="smaller"
color={Colors.Grey}>
color={Theme.Colors.profileValue}>
{controller.backendInfo.application.name}:{' '}
{controller.backendInfo.application.version}
</Text>
@@ -104,7 +109,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
weight="semibold"
align="center"
size="smaller"
color={Colors.Grey}>
color={Theme.Colors.profileValue}>
MOSIP: {controller.backendInfo.config['mosip.host']}
</Text>
</View>

View File

View File

@@ -1,8 +1,10 @@
import React from 'react';
import QRCode from 'react-native-qrcode-svg';
import { Button, Centered, Column, Row, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { Centered, Button, Row, Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { MainRouteProps } from '../../routes/main';
import { ReceiveVcModal } from './ReceiveVcModal';
import { MessageOverlay } from '../../components/MessageOverlay';
import { useRequestScreen } from './RequestScreenController';
import { useTranslation } from 'react-i18next';
import { Switch } from 'react-native-elements';
@@ -13,11 +15,14 @@ export const RequestScreen: React.FC = () => {
const controller = useRequestScreen();
return (
<Column fill padding="24" backgroundColor={Colors.LightGrey}>
<Column
fill
padding="24"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column>
{controller.isBluetoothDenied ? (
<React.Fragment>
<Text color={Colors.Red} align="center">
<Text color={Theme.Colors.errorMessage} align="center">
{t('bluetoothDenied', { vcLabel: controller.vcLabel.singular })}
</Text>
<Button
@@ -38,7 +43,7 @@ export const RequestScreen: React.FC = () => {
<QRCode
size={200}
value={controller.connectionParams}
backgroundColor={Colors.LightGrey}
backgroundColor={Theme.Colors.QRCodeBackgroundColor}
/>
) : null}
</Centered>

View File

@@ -78,5 +78,6 @@ export function useRequestScreen() {
SWITCH_PROTOCOL: (value: boolean) =>
requestService.send(RequestEvents.SWITCH_PROTOCOL(value)),
GOTO_SETTINGS: () => requestService.send(RequestEvents.GOTO_SETTINGS()),
GOBACK: () => requestService.send(RequestEvents.GOBACK()),
};
}

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { Column } from '../../components/ui';
import { Theme } 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={Theme.Colors.lightGreyBackgroundColor}>
<NewVcDetails vc={controller.incomingVc} />
</Column>
</Modal>
);
};
interface ReceveVcModalProps extends ModalProps {
onAccept: () => void;
onReject: () => void;
onShow: () => void;
}

View File

@@ -0,0 +1,93 @@
import React from 'react';
import QRCode from 'react-native-qrcode-svg';
import { Centered, Column, Text } from '../../components/ui';
import { Theme } 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';
import { useTranslation } from 'react-i18next';
import { useIsFocused } from '@react-navigation/native';
export const TimerBasedRequestScreen: React.FC<MainRouteProps> = (props) => {
const controller = useRequestScreen(props);
const { t } = useTranslation('RequestScreen');
const isFocused = useIsFocused();
return (
<Column
fill
padding="98 24 24 24"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column>
{controller.isBluetoothDenied ? (
<Text color={Theme.Colors.errorMessage} align="center">
{t('bluetoothDenied', { vcLabel: controller.vcLabel.singular })}
</Text>
) : (
controller.isWaitingForConnection && (
<Text align="center">
{t('showQrCode', { vcLabel: controller.vcLabel.singular })}
</Text>
)
)}
</Column>
<Centered fill>
{controller.isWaitingForConnection &&
controller.connectionParams !== '' ? (
<QRCode
size={200}
value={controller.connectionParams}
backgroundColor={Theme.Colors.QRCodeBackgroundColor}
/>
) : null}
</Centered>
{controller.statusMessage !== '' && (
<Column elevation={1} padding="16 24">
<Text>{controller.statusMessage}</Text>
</Column>
)}
{isFocused && (
<TimerBasedReceiveVcModal
isVisible={controller.isReviewing}
onDismiss={controller.REJECT}
onAccept={controller.ACCEPT}
onReject={controller.REJECT}
onShow={controller.ACCEPT}
headerTitle={``}
/>
)}
{isFocused && (
<SuccesfullyReceived
img="true"
isVisible={controller.isAccepted}
onBackdropPress={controller.DISMISS}
onShow={controller.GOBACK}
/>
)}
{isFocused && (
<MessageOverlay
isVisible={controller.isRejected}
title="Notice"
message={`You rejected ${controller.senderInfo.deviceName}'s ${controller.vcLabel.singular}'`}
onBackdropPress={controller.DISMISS}
/>
)}
{isFocused && (
<MessageOverlay
isVisible={controller.isDisconnected}
title={t('Rejected')}
message={t('The request to share ID was rejected')}
onBackdropPress={controller.DISMISS}
/>
)}
</Column>
);
};

View File

@@ -1,26 +1,29 @@
import React from 'react';
import { QrScanner } from '../../components/QrScanner';
import { Button, Column, Text } from '../../components/ui';
import { Colors } from '../../components/ui/styleUtils';
import { Theme } 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');
const controller = useScanScreen(props);
return (
<Column fill padding="98 24 24 24" backgroundColor={Colors.LightGrey}>
<Column
fill
padding="98 24 24 24"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Text align="center">{t('header')}</Text>
{controller.isLocationDisabled ||
controller.isLocationDenied ||
controller.isFlightMode ? (
<Column fill align="space-between">
<Text align="center" margin="16 0" color={Colors.Red}>
<Text align="center" margin="16 0" color={Theme.Colors.errorMessage}>
{controller.locationError.message}
</Text>
<Button
@@ -37,7 +40,7 @@ export const ScanScreen: React.FC<MainRouteProps> = (props) => {
</Column>
)
) : (
<Text align="center" margin="16 0" color={Colors.Red}>
<Text align="center" margin="16 0" color={Theme.Colors.errorMessage}>
{t('noShareableVcs', { vcLabel: controller.vcLabel.plural })}
</Text>
)}
@@ -49,7 +52,7 @@ export const ScanScreen: React.FC<MainRouteProps> = (props) => {
onBackdropPress={controller.DISMISS_INVALID}
/>
<SendVcModal
<UpdatedSendVcModal
isVisible={controller.isReviewing}
onDismiss={controller.DISMISS}
headerElevation={2}

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Dimensions, StyleSheet } from 'react-native';
import { Overlay } from 'react-native-elements/dist/overlay/Overlay';
import { Button, Column, Row, Text } from '../../components/ui';
import { Colors, elevation } from '../../components/ui/styleUtils';
import { Theme } from '../../components/ui/styleUtils';
import { VcItem } from '../../components/VcItem';
import {
SelectVcOverlayProps,
@@ -12,8 +12,8 @@ import {
const styles = StyleSheet.create({
overlay: {
...elevation(5),
backgroundColor: Colors.White,
...Theme.elevation(5),
backgroundColor: Theme.Colors.overlayBackgroundColor,
padding: 0,
},
});

View File

@@ -2,7 +2,7 @@ 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 { Theme } from '../../components/ui/styleUtils';
import { SelectVcOverlay } from './SelectVcOverlay';
import { MessageOverlay } from '../../components/MessageOverlay';
import { Modal, ModalProps } from '../../components/ui/Modal';
@@ -17,7 +17,7 @@ export const SendVcModal: React.FC<SendVcModalProps> = (props) => {
return (
<Modal {...props}>
<Column fill backgroundColor={Colors.LightGrey}>
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column padding="16 0" scroll>
<DeviceInfoList of="receiver" deviceInfo={controller.receiverInfo} />
<Column padding="24">
@@ -30,7 +30,7 @@ export const SendVcModal: React.FC<SendVcModalProps> = (props) => {
</Column>
</Column>
<Column
backgroundColor={Colors.White}
backgroundColor={Theme.Colors.whiteBackgroundColor}
padding="16 24"
margin="2 0 0 0"
elevation={2}>

View File

@@ -0,0 +1,121 @@
import React from 'react';
import { Input } from 'react-native-elements';
import { DeviceInfoList } from '../../components/DeviceInfoList';
import { Button, Column } from '../../components/ui';
import { Theme } 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';
import { SingleVcItem } from '../../components/SingleVcItem';
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('Reason For Sharing');
return (
<Modal {...props}>
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<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.length === 1 && (
<SingleVcItem
key={controller.vcKeys[0]}
vcKey={controller.vcKeys[0]}
margin="0 2 8 2"
onShow={controller2.selectVcItem(0)}
selectable
selected={0 === controller2.selectedIndex}
/>
)}
{controller.vcKeys.length > 1 &&
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={Theme.Colors.whiteBackgroundColor}
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>
<MessageOverlay
isVisible={controller.isSendingVc}
title={t('Sharing..')}
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;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Logo } from '../components/Logo';
import { Button, Centered, Column, Text } from '../components/ui';
import { Colors } from '../components/ui/styleUtils';
import { Theme } from '../components/ui/styleUtils';
import { RootRouteProps } from '../routes';
import { useWelcomeScreen } from './WelcomeScreenController';
@@ -11,7 +11,10 @@ export const WelcomeScreen: React.FC<RootRouteProps> = (props) => {
const controller = useWelcomeScreen(props);
return (
<Column fill padding="32 32 0" backgroundColor={Colors.White}>
<Column
fill
padding="32 32 0"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Centered fill>
<Logo height={182} />
<Text margin="16 0 0 0">{t('title')}</Text>

Some files were not shown because too many files have changed in this diff Show More