ui revamp on ble

This commit is contained in:
Sri Kanth Kola
2023-04-21 09:48:07 +05:30
112 changed files with 4717 additions and 1016 deletions

View File

@@ -47,28 +47,9 @@ jobs:
- name: Install npm dependencies
run: |
npm install
- name: Create .env.local file
run: |
echo "${{ secrets.ENV_FILE }}" > .env.local > android/local.properties
- name: Setup branch and env
run: |
# Strip git ref prefix from version
echo "BRANCH_NAME=$(echo ${{ github.ref }} | sed -e 's,.*/\(.*\),\1,')" >> $GITHUB_ENV
echo "GPG_TTY=$(tty)" >> $GITHUB_ENV
- name: Setup branch and GPG public key
run: |
# Strip git ref prefix from version
echo ${{ env.BRANCH_NAME }}
echo ${{ env.GPG_TTY }}
sudo apt-get --yes install gnupg2
gpg2 --import ./.github/keys/mosipgpgkey_pub.gpg
gpg2 --quiet --batch --passphrase=${{secrets.gpg_secret}} --allow-secret-key-import --import ./.github/keys/mosipgpgkey_sec.gpg
- name: Build App Newlogic Release
run: |
cd android
@@ -76,11 +57,10 @@ jobs:
./gradlew :app:assembleNewlogicRelease
env:
MIMOTO_HOST: ${{ github.event.inputs.backendServiceUrl }}
FIREBASE_SECRET: ${{ secrets.GPG_SECRET }}
APPLICATION_THEME: ${{ github.event.inputs.theme }}
FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET }}
- name: Upload Artifact
uses: actions/upload-artifact@v3.1.1
with:
name: apk-output
path: android/app/build/outputs/apk/newlogic/release/
retention-days: 10
retention-days: 5

View File

@@ -1,4 +1,7 @@
name: 'Delete old artifacts'
on:
workflow_dispatch:
name: 'Delete old artifacts'
on:
workflow_dispatch:
@@ -8,5 +11,5 @@ jobs:
steps:
- uses: kolpav/purge-artifacts-action@v1
with:
token: ${{ secrets. access_token }}
token: ${{ secrets.ACTION_PAT }}
expire-in: 2days # Setting this to 0 will delete all artifacts

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<color name="splashscreen_background">#ffffff</color>
<color name="splashscreen_background">#F59B4B</color>
<color name="iconBackground">#FFFFFF</color>
<color name="colorPrimary">#023c69</color>
<color name="colorPrimaryDark">#ffffff</color>

View File

@@ -4,6 +4,6 @@
<string name="app_name_mosip">MOSIP Resident App - Mosip/Inji</string>
<string name="app_name_newlogic">MOSIP Resident App - Newlogic</string>
<string name="app_name_ph">MOSIP Resident App - PH</string>
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
<string name="expo_splash_screen_resize_mode" translatable="false">cover</string>
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
</resources>

View File

@@ -6,7 +6,7 @@ export default {
icon: './assets/icon.png',
splash: {
image: './assets/splash.png',
resizeMode: 'contain',
resizeMode: 'cover',
backgroundColor: '#ffffff',
},
updates: {

BIN
assets/Secure-Sharing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
assets/Secure-Sharing2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

BIN
assets/help-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/inji-home-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/inji-logo-white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
assets/inji_small_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
assets/intro-scanner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

BIN
assets/lock-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 607 B

BIN
assets/progressing-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -14,6 +14,7 @@ export const ActivityLogText: React.FC<{ activity: ActivityLog }> = (props) => {
<TextItem
label={getActionLabel(activity, i18n.language)}
text={`${activity.vcLabel} ${t(activity.type)}`}
topDivider
/>
);
};

View File

@@ -1,23 +1,18 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { TextItem } from './ui/TextItem';
import { Text } from './ui';
export const DeviceInfoList: React.FC<DeviceInfoProps> = (props) => {
const { t } = useTranslation('DeviceInfoList');
return (
<React.Fragment>
<TextItem
divider
label={props.of === 'receiver' ? t('requestedBy') : t('sentBy')}
text={props.deviceInfo.deviceName}
/>
<Text>{props.deviceInfo.deviceName}</Text>
</React.Fragment>
);
};
interface DeviceInfoProps {
of: 'sender' | 'receiver';
deviceInfo: DeviceInfo;
}

View File

@@ -11,23 +11,26 @@ export const EditableListItem: React.FC<EditableListItemProps> = (props) => {
const [newValue, setNewValue] = useState(props.value);
return (
<ListItem
bottomDivider
onPress={() => setIsEditing(true)}
style={{ display: props.display }}>
<ListItem bottomDivider topDivider onPress={() => setIsEditing(true)}>
<Icon
name={props.Icon}
type="antdesign"
size={20}
style={Theme.Styles.profileIconBg}
containerStyle={Theme.Styles.settingsIconBg}
type={props.IconType}
size={22}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
<ListItem.Title>
<Text color={Theme.Colors.profileLabel}>{props.label}</Text>
<Text weight="semibold" color={Theme.Colors.profileLabel}>
{props.label}
</Text>
</ListItem.Title>
</ListItem.Content>
<Text color={Theme.Colors.profileValue}>{props.value}</Text>
<Icon
name="chevron-right"
size={21}
color={Theme.Colors.profileLanguageValue}
/>
<Overlay
overlayStyle={{ padding: 24, elevation: 6 }}
isVisible={isEditing}
@@ -67,6 +70,7 @@ interface EditableListItemProps {
label: string;
value: string;
Icon: string;
IconType?: string;
onEdit: (newValue: string) => void;
display?: 'none' | 'flex';
}

74
components/KebabPopUp.tsx Normal file
View File

@@ -0,0 +1,74 @@
import React from 'react';
import { BottomSheet, Icon, ListItem } from 'react-native-elements';
import { Theme } from '../components/ui/styleUtils';
import { Centered, Column, Row, Text } from '../components/ui';
import { WalletBinding } from '../screens/Home/MyVcs/WalletBinding';
import { Pressable } from 'react-native';
import { useKebabPopUp } from './KebabPopUpController';
import { ActorRefFrom } from 'xstate';
import { vcItemMachine } from '../machines/vcItem';
import { useTranslation } from 'react-i18next';
import { HistoryTab } from '../screens/Home/MyVcs/HistoryTab';
export const KebabPopUp: React.FC<KebabPopUpProps> = (props) => {
const controller = useKebabPopUp(props);
const { t } = useTranslation('HomeScreenKebabPopUp');
return (
<Column>
<Icon
name={props.iconName}
type={props.iconType}
color={Theme.Colors.GrayIcon}
/>
<BottomSheet
isVisible={props.isVisible}
containerStyle={Theme.KebabPopUpStyles.kebabPopUp}>
<Row style={Theme.KebabPopUpStyles.kebabHeaderStyle}>
<Text weight="bold">{t('title')}</Text>
<Icon
name="close"
onPress={props.onDismiss}
color={Theme.Colors.Details}
size={25}
/>
</Row>
<Column>
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Title>
<Pressable onPress={controller.PIN_CARD}>
<Text size="small" weight="bold">
{props.vcKey.split(':')[4] == 'true'
? t('unPinCard')
: t('pinCard')}
</Text>
</Pressable>
</ListItem.Title>
</ListItem.Content>
</ListItem>
<WalletBinding
label={t('offlineAuthenticationDisabled!')}
content={t('offlineAuthDisabledMessage')}
service={props.service}
/>
<HistoryTab
service={props.service}
label={t('ActivityLog')}
vcKey={props.vcKey}
/>
</Column>
</BottomSheet>
</Column>
);
};
export interface KebabPopUpProps {
iconName: string;
iconType?: string;
vcKey: string;
isVisible: boolean;
onDismiss: () => void;
service: ActorRefFrom<typeof vcItemMachine>;
}

View File

@@ -0,0 +1,75 @@
import { useSelector } from '@xstate/react';
import { ActorRefFrom } from 'xstate';
import {
selectKebabPopUpWalletBindingInProgress,
selectKebabPopUp,
selectKebabPopUpAcceptingBindingOtp,
selectKebabPopUpBindingWarning,
selectEmptyWalletBindingId,
selectIsPinned,
selectOtpError,
selectShowWalletBindingError,
selectWalletBindingError,
VcItemEvents,
vcItemMachine,
selectShowActivities,
} from '../machines/vcItem';
import { selectActivities } from '../machines/activityLog';
import { GlobalContext } from '../shared/GlobalContext';
import { useContext } from 'react';
export function useKebabPopUp(props) {
const service = props.service as ActorRefFrom<typeof vcItemMachine>;
const PIN_CARD = () => service.send(VcItemEvents.PIN_CARD());
const KEBAB_POPUP = () => service.send(VcItemEvents.KEBAB_POPUP());
const ADD_WALLET_BINDING_ID = () =>
service.send(VcItemEvents.ADD_WALLET_BINDING_ID());
const CONFIRM = () => service.send(VcItemEvents.CONFIRM());
const DISMISS = () => service.send(VcItemEvents.DISMISS());
const CANCEL = () => service.send(VcItemEvents.CANCEL());
const SHOW_ACTIVITY = () => service.send(VcItemEvents.SHOW_ACTIVITY());
const INPUT_OTP = (otp: string) => service.send(VcItemEvents.INPUT_OTP(otp));
const isPinned = useSelector(service, selectIsPinned);
const isBindingWarning = useSelector(service, selectKebabPopUpBindingWarning);
const isAcceptingOtpInput = useSelector(
service,
selectKebabPopUpAcceptingBindingOtp
);
const isWalletBindingError = useSelector(
service,
selectShowWalletBindingError
);
const otpError = useSelector(service, selectOtpError);
const walletBindingError = useSelector(service, selectWalletBindingError);
const WalletBindingInProgress = useSelector(
service,
selectKebabPopUpWalletBindingInProgress
);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const isShowActivities = useSelector(service, selectShowActivities);
const { appService } = useContext(GlobalContext);
const activityLogService = appService.children.get('activityLog');
return {
isPinned,
PIN_CARD,
KEBAB_POPUP,
ADD_WALLET_BINDING_ID,
CONFIRM,
DISMISS,
CANCEL,
INPUT_OTP,
SHOW_ACTIVITY,
isBindingWarning,
isAcceptingOtpInput,
isWalletBindingError,
walletBindingError,
otpError,
WalletBindingInProgress,
emptyWalletBindingId,
isKebabPopUp,
isShowActivities,
activities: useSelector(activityLogService, selectActivities),
};
}

View File

@@ -14,14 +14,33 @@ export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
overlayStyle={Theme.MessageOverlayStyles.overlay}
onShow={props.onShow}
onBackdropPress={props.onBackdropPress}>
<Column width={Dimensions.get('screen').width * 0.8}>
<Column padding="24">
<Column
width={Dimensions.get('screen').width * 0.8}
style={
!props.progress
? Theme.MessageOverlayStyles.popupOverLay
: { height: 100 }
}>
<Column padding="21" crossAlign="center">
{props.title && (
<Text weight="semibold" margin="0 0 12 0">
<Text
align="center"
weight="bold"
margin="0 0 10 0"
color={Theme.Colors.Details}>
{props.title}
</Text>
)}
{props.message && <Text margin="0 0 12 0">{props.message}</Text>}
{props.message && (
<Text
align="center"
weight="semibold"
size="small"
margin="10 0 12 0"
color={Theme.Colors.Details}>
{props.message}
</Text>
)}
{props.progress && <Progress progress={props.progress} />}
{props.hint && (
<Text
@@ -35,6 +54,7 @@ export const MessageOverlay: React.FC<MessageOverlayProps> = (props) => {
</Column>
{!props.children && props.onCancel ? (
<Button
type="gradient"
title={t('cancel')}
onPress={props.onCancel}
styles={Theme.MessageOverlayStyles.button}
@@ -60,6 +80,7 @@ export interface MessageOverlayProps {
title?: string;
message?: string;
progress?: boolean | number;
requester?: boolean;
hint?: string;
onCancel?: () => void;
onBackdropPress?: () => void;

View File

@@ -22,6 +22,7 @@ export const PinInput: React.FC<PinInputProps> = (props) => {
selectTextOnFocus
keyboardType="numeric"
maxLength={1}
secureTextEntry
selectionColor={Theme.Colors.inputSelection}
style={Theme.PinInputStyle.input}
key={index}

View File

@@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Centered, Column, Text } from './ui';
import { Modal } from './ui/Modal';
import { Image, View } from 'react-native';
import { Theme } from './ui/styleUtils';
import PaginationDot from 'react-native-animated-pagination-dot';
export const ProgressingModal: React.FC<ProgressingModalProps> = (props) => {
const { t } = useTranslation('ScanScreen');
let n = 0;
const [curPage, setCurPage] = useState(n);
const highLightDot = () => setCurPage(n + 1);
return (
<React.Fragment>
<Modal
isVisible={props.isVisible}
headerLeft={t(props.title)}
onDismiss={props.onCancel}
headerLabel={props.label}
headerElevation={3}
requester={props.requester}>
<Centered crossAlign="center" fill>
<Column margin="24 0" align="space-around">
<Image
source={Theme.InjiProgressingLogo}
height={2}
width={2}
style={{ marginBottom: 15, marginLeft: -6 }}
/>
<PaginationDot
activeDotColor={'black'}
curPage={curPage}
maxPage={3}
/>
</Column>
<Column style={{ display: props.timeoutHint ? 'flex' : 'none' }}>
<Column style={Theme.SelectVcOverlayStyles.timeoutHintContainer}>
<Text
align="center"
color={Theme.Colors.TimoutText}
style={Theme.TextStyles.bold}>
{t('ScanScreen:status.sharing.timeoutHint')}
</Text>
<Button
type="clear"
title={t('common:cancel')}
onPress={props.onCancel}
/>
</Column>
</Column>
</Centered>
</Modal>
</React.Fragment>
);
};
export interface ProgressingModalProps {
isVisible: boolean;
title?: string;
label?: string;
timeoutHint?: string;
onCancel?: () => void;
requester?: boolean;
}

View File

@@ -58,11 +58,6 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
return (
<View>
{props.title && (
<Text align="center" margin="16 0" color={Theme.Colors.Details}>
{props.title}
</Text>
)}
<View style={Theme.Styles.scannerContainer}>
<Camera
style={Theme.Styles.scanner}
@@ -73,23 +68,15 @@ export const QrScanner: React.FC<QrScannerProps> = (props) => {
type={type}
/>
</View>
<Column margin="24 0">
<TouchableOpacity
style={Theme.Styles.flipIconButton}
onPress={() => {
setType(
type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
);
}}>
<Icon
name="flip-camera-ios"
color={Theme.Colors.flipCameraIcon}
size={64}
/>
</TouchableOpacity>
</Column>
{props.title && (
<Text
align="center"
weight="bold"
style={Theme.TextStyles.base}
margin="20 57 0 57">
{props.title}
</Text>
)}
</View>
);

212
components/SingleVcItem.tsx Normal file
View File

@@ -0,0 +1,212 @@
import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import { Image, ImageBackground, Pressable } from 'react-native';
import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate';
import {
createVcItemMachine,
selectVerifiableCredential,
selectGeneratedOn,
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
numLines={4}
color={Theme.Colors.Details}
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>
);
}
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().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 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)}
style={
props.selected
? Theme.Styles.selectedVc
: Theme.Styles.closeCardBgContainer
}>
<ImageBackground
source={!verifiableCredential ? null : Theme.CloseCard}
resizeMode="stretch"
borderRadius={4}
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" style={{ alignItems: 'flex-start' }}>
{getDetails(t('fullName'), fullName, verifiableCredential)}
{getDetails(t('uin'), 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

@@ -9,7 +9,18 @@ import { Button, Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils';
import { TextItem } from './ui/TextItem';
import { VcItemTags } from './VcItemTags';
import VerifiedIcon from './VerifiedIcon';
import QRCode from 'react-native-qrcode-svg';
const VerifiedIcon: React.FC = () => {
return (
<Icon
name="check-circle"
color={Theme.Colors.VerifiedIcon}
size={14}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
export const VcDetails: React.FC<VcDetailsProps> = (props) => {
const { t, i18n } = useTranslation('VcDetails');
@@ -28,33 +39,29 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
borderRadius={10}
style={Theme.Styles.openCardBgContainer}
source={Theme.OpenCard}>
<Row style={Theme.Styles.openDetailsHeader}>
<Column margin={'0 0 0 10'}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('idType')}
</Text>
<Text weight="bold" size="smaller" color={Theme.Colors.Details}>
{t('nationalCard')}
</Text>
<Row align="space-between">
<Column align="space-evenly" crossAlign="center">
<Image
source={
props.vc?.credential.biometrics?.face
? { uri: props.vc?.credential.biometrics.face }
: Theme.ProfileIcon
}
style={Theme.Styles.openCardImage}
/>
<Column margin="20 0 0 0">
<Image source={Theme.MosipLogo} style={Theme.Styles.logo} />
</Column>
<Column margin="20 0 0 0">
<QRCode
size={90}
value={String(props.vc.credential)}
backgroundColor={Theme.Colors.QRCodeBackgroundColor}
/>
</Column>
</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>
<Column align="space-evenly">
<Column>
<Text
weight="bold"
size="smaller"
@@ -70,173 +77,190 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
)}
</Text>
</Column>
<Row>
<Column>
<Column>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('idType')}
</Text>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.Details}>
{t('nationalCard')}
</Text>
</Column>
{uin ? (
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('uin')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{uin}
</Text>
</Column>
) : null}
{uin ? (
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('uin')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{uin}
</Text>
{vid ? (
<Column margin="20 0 0 0">
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('vid')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{vid}
</Text>
</Column>
) : null}
<Column margin="20 0 0 0">
<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>
) : null}
{vid ? (
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('vid')}
</Text>
<Text
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{vid}
</Text>
<Column margin="0 0 0 40">
<Column>
<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 margin="20 0 0 0">
<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 margin="20 0 0 0">
<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 margin="20 0 0 0">
<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>
) : null}
<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>
<Row>
<Text
style={
props.vc?.verifiableCredential.credentialSubject.email
.length > 25
? { flex: 1 }
: { flex: 0 }
}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.email
)}
</Text>
</Row>
</Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('address')}
</Text>
<Row>
<Text
style={{ flex: 1 }}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getFullAddress(
props.vc?.verifiableCredential.credentialSubject
)}
</Text>
</Row>
</Column>
</Row>
</Column>
</Row>
<View style={Theme.Styles.hrLine}></View>
<Column>
<Column fill style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('email')}
</Text>
<Row>
<Text
style={
props.vc?.verifiableCredential.credentialSubject.email
.length > 25
? { flex: 1 }
: { flex: 0 }
}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getLocalizedField(
props.vc?.verifiableCredential.credentialSubject.email
)}
</Text>
</Row>
</Column>
<Column style={Theme.Styles.labelPart}>
<Text
weight="bold"
size="smaller"
color={Theme.Colors.DetailsLabel}>
{t('address')}
</Text>
<Row>
<Text
style={{ flex: 1 }}
weight="semibold"
size="smaller"
color={Theme.Colors.Details}>
{getFullAddress(
props.vc?.verifiableCredential.credentialSubject
)}
</Text>
</Row>
</Column>
</Column>
<VcItemTags tag={props.vc?.tag} />
</ImageBackground>

View File

@@ -1,12 +1,6 @@
import React, { useContext, useRef } from 'react';
import React, { useContext, useRef, useState } from 'react';
import { useInterpret, useSelector } from '@xstate/react';
import {
Pressable,
Image,
ImageBackground,
Dimensions,
View,
} from 'react-native';
import { Pressable, Image, ImageBackground, Dimensions } from 'react-native';
import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate';
import {
@@ -17,14 +11,16 @@ import {
selectContext,
selectTag,
selectEmptyWalletBindingId,
selectKebabPopUp,
VcItemEvents,
} 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';
import { LocalizedField } from '../types/vc';
import { VcItemTags } from './VcItemTags';
import { KebabPopUp } from './KebabPopUp';
import VerifiedIcon from './VerifiedIcon';
const getDetails = (arg1, arg2, verifiableCredential) => {
@@ -126,7 +122,9 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
const isKebabPopUp = useSelector(service, selectKebabPopUp);
const KEBAB_POPUP = () => service.send(VcItemEvents.KEBAB_POPUP());
const DISMISS = () => service.send(VcItemEvents.DISMISS());
//Assigning the UIN and VID from the VC details to display the idtype label
const uin = verifiableCredential?.credentialSubject.UIN;
const vid = verifiableCredential?.credentialSubject.VID;
@@ -135,16 +133,24 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
const fullName = !verifiableCredential
? ''
: getLocalizedField(verifiableCredential.credentialSubject.fullName);
const isvalid = !verifiableCredential ? '' : t('valid');
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;
const tag = useSelector(service, selectTag);
return (
<Pressable
onPress={() => props.onPress(service)}
disabled={!verifiableCredential}
style={
props.selected
? Theme.Styles.selectedBindedVc
? Theme.Styles.selectedVc
: Theme.Styles.closeCardBgContainer
}>
<ImageBackground
@@ -156,140 +162,124 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
? Theme.Styles.vertloadingContainer
: Theme.Styles.backgroundImageContainer
}>
<Row style={Theme.Styles.homeCloseCardDetailsHeader}>
<Column>
<Text
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="bold"
size="smaller">
{t('idType')}
</Text>
<Text
weight="bold"
color={Theme.Colors.Details}
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}>
{t('nationalCard')}
</Text>
</Column>
<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 25" style={{ alignItems: 'flex-start' }}>
{getDetails(t('fullName'), fullName, verifiableCredential)}
{!verifiableCredential
? getDetails(t('id'), uin || vid, verifiableCredential)
: null}
{uin ? getDetails(t('uin'), uin, verifiableCredential) : null}
{vid ? getDetails(t('vid'), vid, verifiableCredential) : null}
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}
{getDetails(t('status'), t('valid'), verifiableCredential)}
</Column>
</Column>
{!verifiableCredential && (
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)}
</Row>
<VcItemTags tag={tag} />
</ImageBackground>
{props.activeTab !== 'receivedVcsTab' &&
props.activeTab != 'sharingVcScreen' && (
<Row>
{emptyWalletBindingId ? (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
{verifiableCredential && <WalletUnverified />}
<Text
color={Theme.Colors.Details}
weight="semibold"
size="small"
margin="10 33 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.statusLabel
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
<Pressable
onPress={() =>
verifiableCredential ? props.onPress(service) : null
}>
<Column>
<Row align="space-between">
<Row>
<ImageBackground
source={
!verifiableCredential
? Theme.ProfileIcon
: { uri: context.credential.biometrics.face }
}
style={Theme.Styles.closeCardImage}>
{props.iconName && (
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
name={props.iconName}
type={props.iconType}
color={Theme.Colors.Icon}
style={{ marginLeft: -80 }}
/>
</Pressable>
</Row>
) : (
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
<WalletVerified />
)}
</ImageBackground>
<Column margin="0 0 0 10">
{getDetails(t('fullName'), fullName, verifiableCredential)}
<Column margin="10 0 0 0">
<Text
color={Theme.Colors.statusLabel}
color={
!verifiableCredential
? Theme.Colors.LoadingDetailsLabel
: Theme.Colors.DetailsLabel
}
weight="semibold"
size="smaller"
margin="10 10 10 10"
align="left">
{t('idType')}
</Text>
<Text
weight="regular"
color={Theme.Colors.Details}
size="smaller"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('profileAuthenticated')}></Text>
</Row>
}>
{t('nationalCard')}
</Text>
</Column>
</Column>
</Row>
{props.showOnlyBindedVc ? null : (
<Pressable onPress={() => props.onPress(service)}>
<Icon
name="dots-three-horizontal"
type="entypo"
color={Theme.Colors.GrayIcon}
/>
</Pressable>
)}
</Row>
)}
<Column>{verifiableCredential ? selectableOrCheck : null}</Column>
</Row>
<Row
align="space-between"
margin="5 0 0 0"
style={
!verifiableCredential ? Theme.Styles.loadingContainer : null
}>
<Column>
{uin ? getDetails(t('uin'), uin, verifiableCredential) : null}
{vid ? getDetails(t('vid'), vid, verifiableCredential) : null}
{!verifiableCredential
? getDetails(t('id'), uin || vid, verifiableCredential)
: null}
{getDetails(t('generatedOn'), generatedOn, verifiableCredential)}
</Column>
<Column>
{verifiableCredential
? getDetails(t('status'), isvalid, verifiableCredential)
: null}
</Column>
<Column style={{ display: verifiableCredential ? 'flex' : 'none' }}>
<Image
source={Theme.MosipLogo}
style={Theme.Styles.logo}
resizeMethod="auto"
/>
</Column>
</Row>
</Column>
<VcItemTags tag={tag} />
</ImageBackground>
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
{verifiableCredential &&
(emptyWalletBindingId ? <WalletUnverified /> : <WalletVerified />)}
<Text
color={Theme.Colors.Details}
weight="semibold"
size="small"
margin="10 33 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={
emptyWalletBindingId
? t('offlineAuthDisabledHeader')
: t('profileAuthenticated')
}></Text>
</Row>
{verifiableCredential && (
<Pressable onPress={KEBAB_POPUP}>
<KebabPopUp
vcKey={props.vcKey}
iconName="dots-three-horizontal"
iconType="entypo"
isVisible={isKebabPopUp}
onDismiss={DISMISS}
service={service}
/>
</Pressable>
)}
</Row>
</Pressable>
);
};
@@ -302,7 +292,8 @@ interface VcItemProps {
showOnlyBindedVc?: boolean;
onPress?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
onShow?: (vcRef?: ActorRefFrom<typeof vcItemMachine>) => void;
activeTab?: string;
iconName?: string;
iconType?: string;
}
function getLocalizedField(rawField: string | LocalizedField) {

View File

@@ -8,7 +8,7 @@ import { Text } from './Text';
import { Theme, Spacing } from './styleUtils';
export const Button: React.FC<ButtonProps> = (props) => {
const type = props.type || 'solid';
const type = props.type || 'solid' || 'radius' || 'gradient';
const buttonStyle: StyleProp<ViewStyle> = [
props.fill ? Theme.ButtonStyles.fill : null,
Theme.ButtonStyles[type],
@@ -16,9 +16,14 @@ export const Button: React.FC<ButtonProps> = (props) => {
];
const containerStyle: StyleProp<ViewStyle> = [
Theme.ButtonStyles.container,
!(type === 'gradient') ? Theme.ButtonStyles.container : null,
props.disabled ? Theme.ButtonStyles.disabled : null,
props.margin ? Theme.spacing('margin', props.margin) : null,
type === 'gradient'
? props.isVcThere
? Theme.ButtonStyles.float
: Theme.ButtonStyles.gradient
: null,
props.styles,
];
@@ -28,7 +33,7 @@ export const Button: React.FC<ButtonProps> = (props) => {
}
};
return (
return !(type === 'gradient') ? (
<RNEButton
buttonStyle={buttonStyle}
containerStyle={[
@@ -44,6 +49,8 @@ export const Button: React.FC<ButtonProps> = (props) => {
color={
type === 'solid' || type === 'addId' || type === 'radius'
? Theme.Colors.whiteText
: type === 'plain'
? Theme.Colors.plainText
: Theme.Colors.AddIdBtnTxt
}>
{props.title}
@@ -54,6 +61,36 @@ export const Button: React.FC<ButtonProps> = (props) => {
onPress={handleOnPress}
loading={props.loading}
/>
) : (
<RNEButton
ViewComponent={require('react-native-linear-gradient').default}
linearGradientProps={{
colors: !props.disabled
? Theme.Colors.GradientColors
: Theme.Colors.DisabledColors,
}}
containerStyle={
props.isVcThere ? containerStyle : Theme.ButtonStyles.gradient
}
type={props.type}
raised={props.raised}
title={
<Text
weight="semibold"
style={Theme.TextStyles.bold}
color={
type === 'solid' || type === 'gradient' || type === 'radius'
? Theme.Colors.whiteText
: Theme.Colors.DownloadIdBtnTxt
}>
{props.title}
</Text>
}
buttonStyle={!props.isVcThere ? { height: 45 } : { height: 39 }}
icon={props.icon}
onPress={handleOnPress}
loading={props.loading}
/>
);
};
@@ -62,10 +99,12 @@ interface ButtonProps {
disabled?: boolean;
margin?: Spacing;
type?: RNEButtonProps['type'];
isVcThere?: boolean;
onPress?: RNEButtonProps['onPress'];
fill?: boolean;
raised?: boolean;
loading?: boolean;
icon?: RNEButtonProps['icon'];
styles?: StyleProp<ViewStyle>;
colors?: (string | number)[];
}

View File

@@ -2,9 +2,13 @@ import React from 'react';
import { I18nManager, Modal as RNModal, View } from 'react-native';
import { Icon } from 'react-native-elements';
import { Column, Row, Text } from '.';
import { useSendVcScreen } from '../../screens/Scan/SendVcScreenController';
import { DeviceInfoList } from '../DeviceInfoList';
import { ElevationLevel, Theme } from './styleUtils';
export const Modal: React.FC<ModalProps> = (props) => {
const controller = useSendVcScreen();
return (
<RNModal
animationType="slide"
@@ -19,8 +23,8 @@ export const Modal: React.FC<ModalProps> = (props) => {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginHorizontal: 21,
marginVertical: 16,
marginHorizontal: 18,
marginVertical: 15,
}}>
{props.headerRight ? (
<Icon
@@ -34,17 +38,45 @@ export const Modal: React.FC<ModalProps> = (props) => {
name="arrow-left"
type="material-community"
onPress={props.onDismiss}
color={Theme.Colors.Details}
containerStyle={Theme.Styles.backArrowContainer}
color={Theme.Colors.Icon}
/>
) : null}
<Row fill align="center" margin={'5 30 0 0'}>
<Text weight="semibold">{props.headerTitle}</Text>
<Row
fill
align={props.headerLeft ? 'flex-start' : 'center'}
margin={'16 0 0 0'}>
<Column>
<Text style={Theme.TextStyles.header}>
{props.headerTitle || props.headerLeft}
</Text>
{!props.requester ? (
<Text
weight="semibold"
style={Theme.TextStyles.small}
color={
props.headerLabelColor
? props.headerLabelColor
: Theme.Colors.profileLanguageValue
}>
{props.headerLabel}
</Text>
) : (
<Text
weight="semibold"
style={Theme.TextStyles.small}
color={Theme.Colors.IconBg}>
<DeviceInfoList deviceInfo={controller.receiverInfo} />
</Text>
)}
</Column>
</Row>
{props.headerRight || props.arrowLeft || (
<Icon
name="close"
onPress={props.onDismiss}
color={Theme.Colors.Icon}
color={Theme.Colors.Details}
size={27}
/>
)}
</View>
@@ -57,10 +89,14 @@ export const Modal: React.FC<ModalProps> = (props) => {
export interface ModalProps {
isVisible: boolean;
onDismiss: () => void;
requester?: boolean;
onDismiss?: () => void;
headerTitle?: string;
headerElevation?: ElevationLevel;
headerLabel?: string;
headerLabelColor?: string;
headerRight?: React.ReactElement;
headerLeft?: React.ReactElement;
arrowLeft?: React.ReactElement;
onShow?: () => void;
}

View File

@@ -0,0 +1,69 @@
import React, { useEffect, useState } from 'react';
import { Dimensions } from 'react-native';
import { Icon, ListItem } from 'react-native-elements';
import { Column } from './Layout';
import { Text } from './Text';
import { Theme } from './styleUtils';
interface Picker extends React.VFC<PickerProps<unknown>> {
<T>(props: PickerProps<T>): ReturnType<React.FC>;
}
export const SetupPicker: Picker = (props: PickerProps<unknown>) => {
const [isContentVisible, setIsContentVisible] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
useEffect(() => {
setSelectedIndex(
props.items.findIndex(({ value }) => value === props.selectedValue)
);
}, [props.selectedValue]);
const toggleContent = () => setIsContentVisible(!isContentVisible);
const selectItem = (index: number) => {
setSelectedIndex(index);
props.onValueChange(props.items[index].value, index);
toggleContent();
};
return (
<Column
width={Dimensions.get('window').width * 0.8}
backgroundColor={Theme.Colors.whiteBackgroundColor}>
{props.items.map((item, index) => (
<ListItem
bottomDivider
topDivider={index !== 0}
onPress={() => selectItem(index)}
key={index}>
<ListItem.Content>
<ListItem.Title>
<Text
color={selectedIndex === index ? Theme.Colors.Icon : null}
weight={selectedIndex === index ? 'semibold' : 'regular'}>
{item.label}
</Text>
</ListItem.Title>
</ListItem.Content>
{selectedIndex === index ? (
<Icon name="radio-button-checked" color={Theme.Colors.Icon} />
) : (
<Icon name="radio-button-unchecked" color={Theme.Colors.GrayIcon} />
)}
</ListItem>
))}
</Column>
);
};
interface PickerProps<T> {
items: PickerItem<T>[];
selectedValue: T;
onValueChange: (value: T, index: number) => void;
}
interface PickerItem<T> {
label: string;
value: T;
}

View File

@@ -10,10 +10,17 @@ export const TextItem: React.FC<TextItemProps> = (props) => {
pX={24}
pY={props.label ? 16 : 12}
style={{
borderBottomColor: Theme.Colors.borderBottomColor,
borderBottomWidth: props.divider ? 1 : 0,
borderColor: Theme.Colors.borderBottomColor,
borderBottomWidth: props.divider ? 2 : 0,
borderTopWidth: props.topDivider ? 2 : 0,
alignItems: 'flex-start',
}}>
<Text
color={Theme.Colors.textValue}
weight={props.label ? 'semibold' : 'regular'}
style={{ textAlign: 'left' }}>
{props.text}
</Text>
{props.label && (
<Text
size="smaller"
@@ -23,12 +30,6 @@ export const TextItem: React.FC<TextItemProps> = (props) => {
{props.label}
</Text>
)}
<Text
color={Theme.Colors.textValue}
weight={props.label ? 'semibold' : 'regular'}
style={{ textAlign: 'left' }}>
{props.text}
</Text>
</Column>
);
};
@@ -37,5 +38,6 @@ interface TextItemProps {
text: string;
label?: string;
divider?: boolean;
topDivider?: boolean;
margin?: string;
}

View File

@@ -3,23 +3,35 @@ import { Dimensions, StyleSheet, ViewStyle } from 'react-native';
import { Spacing } from '../styleUtils';
const Colors = {
Black: '#231F20',
Grey: '#B0B0B0',
Black: '#000000',
Zambezi: '#5F5F5F',
Grey: '#C7C7C7',
Grey5: '#E0E0E0',
Grey6: '#F2F2F2',
Gray40: '#666666',
Gray9: '#171717',
DimGray: '#737373',
Orange: '#F2811D',
LightGrey: '#f7f5f0',
LightGrey: '#F5F5F5',
ShadeOfGrey: '#6F6F6F',
White: '#FFFFFF',
Red: '#EB5757',
Green: '#219653',
Red: '#D52929',
Green: '#4B9D20',
Transparent: 'transparent',
Warning: '#f0ad4e',
LightOrange: '#fce7e3',
GrayText: '#6F6F6F',
dorColor: '#CBCBCB',
plainText: '#FFD6A7',
walletbindingLabel: '#000000',
walletbindingContent: '#666666',
LightOrange: '#FDF1E6',
GradientColors: ['#F59B4B', '#E86E04'],
DisabledColors: ['#C7C7C7', '#C7C7C7'],
TimeoutHintBoxColor: '#FFF7E5',
TimoutText: '#8B6105',
};
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6;
export const DefaultTheme = {
Colors: {
@@ -29,10 +41,11 @@ export const DefaultTheme = {
LoadingDetailsLabel: Colors.Orange,
AddIdBtnBg: Colors.Orange,
AddIdBtnTxt: Colors.Orange,
ClearAddIdBtnBg: Colors.Transparent,
DownloadIdBtnTxt: Colors.White,
Loading: Colors.Orange,
noUinText: Colors.Orange,
IconBg: Colors.Orange,
popUp: Colors.Green,
Icon: Colors.Orange,
GrayIcon: Colors.Grey,
borderBottomColor: Colors.Grey6,
@@ -51,6 +64,7 @@ export const DefaultTheme = {
loadingLabel: Colors.Grey6,
textLabel: Colors.Grey,
textValue: Colors.Black,
requesterName: Colors.Red,
errorMessage: Colors.Red,
QRCodeBackgroundColor: Colors.LightGrey,
ReceiveVcModalBackgroundColor: Colors.LightGrey,
@@ -59,15 +73,26 @@ export const DefaultTheme = {
whiteText: Colors.White,
flipCameraIcon: Colors.Black,
IdInputModalBorder: Colors.Grey,
RetrieveIdLabel: Colors.ShadeOfGrey,
inputSelection: Colors.Orange,
checkCircleIcon: Colors.White,
OnboardingCircleIcon: Colors.White,
OnboardingCloseIcon: Colors.White,
WarningIcon: Colors.Warning,
DefaultToggle: Colors.LightOrange,
ProfileIconBg: Colors.LightOrange,
Cursor: Colors.Orange,
version: Colors.DimGray,
poweredByBLE: Colors.Gray9,
GrayText: Colors.GrayText,
gradientBtn: ['#F59B4B', '#E86E04'],
dotColor: Colors.dorColor,
plainText: Colors.plainText,
IconBackground: Colors.LightOrange,
GradientColors: Colors.GradientColors,
DisabledColors: Colors.DisabledColors,
getVidColor: Colors.Zambezi,
TimeoutHintBoxColor: Colors.TimeoutHintBoxColor,
TimoutText: Colors.TimoutText,
walletbindingLabel: Colors.Black,
walletbindingContent: Colors.Gray40,
},
Styles: StyleSheet.create({
title: {
@@ -110,6 +135,31 @@ export const DefaultTheme = {
backgroundColor: Colors.Grey6,
borderRadius: 4,
},
bottomTabIconStyle: {
padding: 4,
width: 36,
height: 36,
borderRadius: 6,
backgroundColor: Colors.LightOrange,
},
popUp: {
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: Colors.Green,
height: 39,
position: 'relative',
paddingHorizontal: 12,
},
homeScreenContainer: {
alignItems: 'center',
justifyContent: 'center',
borderRadius: 10,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOpacity: 0.4,
elevation: 5,
padding: 10,
},
vertloadingContainer: {
flex: 1,
backgroundColor: Colors.Grey6,
@@ -120,10 +170,11 @@ export const DefaultTheme = {
flex: 1,
justifyContent: 'flex-start',
},
logoContainer: {
closecardMosipLogo: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignSelf: 'flex-end',
marginLeft: 300,
},
closeCardBgContainer: {
@@ -136,7 +187,7 @@ export const DefaultTheme = {
shadowRadius: 3,
elevation: 4,
},
selectedBindedVc: {
selectedVc: {
borderRadius: 10,
margin: 5,
borderWidth: 2,
@@ -159,7 +210,7 @@ export const DefaultTheme = {
width: 100,
},
bottomButtonsContainer: {
height: 120,
height: 'auto',
borderTopLeftRadius: 27,
borderTopRightRadius: 27,
padding: 6,
@@ -217,12 +268,11 @@ export const DefaultTheme = {
justifyContent: 'space-between',
},
logo: {
height: 36,
width: 30,
height: 46,
width: 40,
},
homeCloseCardDetailsHeader: {
flex: 1,
justifyContent: 'space-between',
},
details: {
width: 290,
@@ -233,11 +283,27 @@ export const DefaultTheme = {
flex: 1,
padding: 10,
},
profileIconBg: {
padding: 8,
width: 40,
height: 40,
borderRadius: 6,
IconContainer: {
padding: 6,
width: 36,
marginRight: 4,
marginLeft: 10,
height: 36,
borderRadius: 10,
backgroundColor: Colors.LightOrange,
},
settingsIconBg: {
padding: 6,
width: 36,
marginRight: 4,
height: 36,
backgroundColor: Colors.Transparent,
},
backArrowContainer: {
padding: 6,
width: 36,
height: 36,
borderRadius: 10,
backgroundColor: Colors.LightOrange,
},
domainVerifiyIcon: {
@@ -261,23 +327,22 @@ export const DefaultTheme = {
borderRadius: 5,
},
scannerContainer: {
borderWidth: 4,
borderColor: Colors.Black,
borderRadius: 32,
justifyContent: 'center',
height: 300,
width: 300,
alignSelf: 'center',
height: 330,
width: 320,
overflow: 'hidden',
marginLeft: 18,
marginTop: -65,
},
scanner: {
height: 400,
width: '100%',
margin: 'auto',
},
flipIconButton: {
alignSelf: 'center',
alignItems: 'center',
photoConsentLabel: {
backgroundColor: Colors.White,
padding: 0,
borderWidth: 0,
},
tabIndicator: {
backgroundColor: Colors.Orange,
@@ -292,53 +357,85 @@ export const DefaultTheme = {
detailsText: {
fontWeight: 'bold',
fontSize: 15,
fontFamily: 'Poppins_700Bold',
fontFamily: 'Inter_700Bold',
},
getId: {
justifyContent: 'center',
alignItems: 'center',
marginTop: 10,
marginVertical: 6,
},
placeholder: {
fontFamily: 'Poppins_400Regular',
fontFamily: 'Inter_600SemiBold',
},
hrLine: {
borderBottomColor: 'black',
borderBottomWidth: 1,
marginTop: 10,
},
}),
PinInputStyle: StyleSheet.create({
input: {
borderBottomWidth: 1,
borderBottomWidth: 3,
borderColor: Colors.Grey,
color: Colors.Black,
flex: 1,
fontFamily: 'Poppins_600SemiBold',
fontSize: 18,
fontWeight: '600',
fontSize: 33,
fontFamily: 'Inter_600SemiBold',
height: 40,
lineHeight: 28,
margin: 8,
textAlign: 'center',
},
onEnteringPin: {
borderBottomWidth: 3,
borderColor: Colors.Orange,
color: Colors.Black,
flex: 1,
fontFamily: 'Inter_700Bold',
fontSize: 29,
height: 40,
margin: 8,
textAlign: 'center',
},
}),
TextStyles: StyleSheet.create({
header: {
color: Colors.Black,
fontFamily: 'Inter_700Bold',
fontSize: 18,
lineHeight: 22,
paddingTop: 4,
},
retrieveIdLabel: {
color: Colors.ShadeOfGrey,
fontFamily: 'Inter_600SemiBold',
lineHeight: 18,
},
error: {
color: Colors.Red,
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
},
base: {
color: Colors.Black,
fontSize: 18,
lineHeight: 28,
fontSize: 16,
lineHeight: 18,
},
regular: {
fontFamily: 'Poppins_400Regular',
fontFamily: 'Inter_400Regular',
},
semibold: {
fontFamily: 'Poppins_600SemiBold',
fontFamily: 'Inter_600SemiBold',
},
bold: {
fontFamily: 'Poppins_700Bold',
fontFamily: 'Inter_700Bold',
},
small: {
fontSize: 14,
fontSize: 13,
lineHeight: 21,
},
smaller: {
fontSize: 12,
fontSize: 11,
lineHeight: 18,
},
}),
@@ -395,15 +492,30 @@ export const DefaultTheme = {
borderColor: Colors.Orange,
},
container: {
minHeight: 48,
height: 45,
flexDirection: 'row',
},
disabled: {
opacity: 0.5,
backgroundColor: Colors.Grey,
},
addId: {
backgroundColor: Colors.Orange,
},
gradient: {
borderRadius: 9,
width: '88%',
alignSelf: 'center',
margin: 4,
},
float: {
borderRadius: 9,
width: '34%',
alignSelf: 'center',
fontSize: 10,
elevation: 5,
position: 'absolute',
bottom: 24,
},
clearAddIdBtnBg: {
backgroundColor: Colors.Transparent,
},
@@ -429,6 +541,24 @@ export const DefaultTheme = {
backgroundColor: Colors.White,
padding: 0,
},
consentCheckContainer: {
backgroundColor: Colors.White,
borderWidth: 0,
marginTop: -15,
fontFamily: 'Inter_600SemiBold',
padding: 0,
},
timeoutHintContainer: {
backgroundColor: Colors.TimeoutHintBoxColor,
margin: 21,
paddingHorizontal: 14,
paddingVertical: 12,
borderRadius: 12,
},
sharedSuccessfully: {
flex: 1,
backgroundColor: Colors.White,
},
}),
AppMetaDataStyles: StyleSheet.create({
buttonContainer: {
@@ -497,12 +627,33 @@ export const DefaultTheme = {
height: Dimensions.get('screen').height,
},
}),
KebabPopUpStyles: StyleSheet.create({
kebabPopUp: {
marginHorizontal: 4,
},
kebabHeaderStyle: {
backgroundColor: 'white',
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
justifyContent: 'space-between',
fontFamily: 'Inter_700Bold',
paddingRight: 15,
paddingLeft: 130,
paddingTop: 18,
},
}),
MessageOverlayStyles: StyleSheet.create({
overlay: {
elevation: 5,
backgroundColor: Colors.White,
padding: 0,
},
popupOverLay: {
height: 260,
backgroundColor: Colors.White,
borderRadius: 15,
margin: -13.5,
},
button: {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
@@ -664,7 +815,7 @@ export const DefaultTheme = {
sliderTitle: {
color: Colors.White,
marginBottom: 20,
fontFamily: 'Poppins_700Bold',
fontFamily: 'Inter_700Bold',
},
text: {
color: Colors.White,
@@ -684,14 +835,21 @@ export const DefaultTheme = {
top: 40,
zIndex: 1,
},
bottomContainer: {
padding: 20,
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
marginTop: -185,
paddingBottom: 100,
},
}),
claimsContainer: StyleSheet.create({
container: {
backgroundColor: Colors.Transparent,
},
}),
OpenCard: require('../../../assets/ID-open.png'),
CloseCard: require('../../../assets/ID-closed.png'),
OpenCard: '',
CloseCard: '',
ProfileIcon: require('../../../assets/placeholder-photo.png'),
MosipSplashLogo: require('../../../assets/icon.png'),
MosipLogo: require('../../../assets/mosip-logo.png'),
@@ -699,6 +857,16 @@ export const DefaultTheme = {
WarningLogo: require('../../../assets/warningLogo.png'),
OtpLogo: require('../../../assets/otp-mobile-logo.png'),
SuccessLogo: require('../../../assets/success-logo.png'),
DigitalIdentityLogo: require('../../../assets/digital-identity-icon.png'),
InjiLogoWhite: require('../../../assets/inji-logo-white.png'),
InjiProgressingLogo: require('../../../assets/progressing-logo.png'),
LockIcon: require('../../../assets/lock-icon.png'),
InjiHomeLogo: require('../../../assets/inji-home-logo.png'),
HelpIcon: require('../../../assets/help-icon.png'),
sharingIntro: require('../../../assets/Secure-Sharing.png'),
walletIntro: require('../../../assets/intro-wallet-binding.png'),
IntroScanner: require('../../../assets/intro-scanner.png'),
injiSmallLogo: require('../../../assets/inji_small_logo.png'),
elevation(level: ElevationLevel): ViewStyle {
// https://ethercreative.github.io/react-native-shadow-generator/

View File

@@ -18,6 +18,7 @@ const Colors = {
Purple2: '#AEA7FF',
Transparent: 'transparent',
Warning: '#f0ad4e',
GrayText: '#6F6F6F',
};
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
@@ -66,9 +67,8 @@ export const PurpleTheme = {
OnboardingCircleIcon: Colors.White,
OnboardingCloseIcon: Colors.White,
WarningIcon: Colors.Warning,
Cursor: Colors.Purple,
version: Colors.DimGray,
poweredByBLE: Colors.Gray9,
GrayText: Colors.GrayText,
gradientBtn: ['#F59B4B', '#E86E04'],
},
Styles: StyleSheet.create({
title: {
@@ -186,13 +186,16 @@ export const PurpleTheme = {
justifyContent: 'space-between',
},
logo: {
height: 36,
width: 36,
height: 46,
width: 40,
},
homeCloseCardDetailsHeader: {
flex: 1,
justifyContent: 'space-between',
},
closecardMosipLogo: {
alignSelf: 'flex-end',
},
details: {
width: 290,
marginLeft: 110,
@@ -243,7 +246,7 @@ export const PurpleTheme = {
detailsText: {
fontWeight: 'bold',
fontSize: 15,
fontFamily: 'Poppins_700Bold',
fontFamily: 'Inter_700Bold',
},
getId: {
justifyContent: 'center',
@@ -251,7 +254,17 @@ export const PurpleTheme = {
marginTop: 10,
},
placeholder: {
fontFamily: 'Poppins_400Regular',
fontFamily: 'Inter_400Regular',
},
hrLine: {
borderBottomColor: 'black',
borderBottomWidth: 1,
marginTop: 10,
},
hrLine: {
borderBottomColor: 'black',
borderBottomWidth: 1,
marginTop: 10,
},
}),
PinInputStyle: StyleSheet.create({
@@ -260,7 +273,7 @@ export const PurpleTheme = {
borderColor: Colors.Grey,
color: Colors.Black,
flex: 1,
fontFamily: 'Poppins_600SemiBold',
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
fontWeight: '600',
height: 40,
@@ -276,13 +289,13 @@ export const PurpleTheme = {
lineHeight: 28,
},
regular: {
fontFamily: 'Poppins_400Regular',
fontFamily: 'Inter_400Regular',
},
semibold: {
fontFamily: 'Poppins_600SemiBold',
fontFamily: 'Inter_600SemiBold',
},
bold: {
fontFamily: 'Poppins_700Bold',
fontFamily: 'Inter_700Bold',
},
small: {
fontSize: 14,
@@ -362,6 +375,12 @@ export const PurpleTheme = {
borderRadius: 10,
backgroundColor: Colors.Purple,
},
gradient: {
borderRadius: 10,
},
gradient: {
borderRadius: 10,
},
}),
OIDCAuthStyles: StyleSheet.create({
viewContainer: {
@@ -603,7 +622,7 @@ export const PurpleTheme = {
sliderTitle: {
color: Colors.White,
marginBottom: 20,
fontFamily: 'Poppins_700Bold',
fontFamily: 'Inter_700Bold',
},
text: {
color: Colors.White,

12
i18n.ts
View File

@@ -18,7 +18,7 @@ export const SUPPORTED_LANGUAGES = {
ar: 'عربى',
hi: 'हिंदी',
kn: 'ಕನ್ನಡ',
ta: 'தமிழ்',
ta: 'ತಮಿಳು',
};
i18next
@@ -32,14 +32,18 @@ i18next
})
.then(async () => {
const language = await AsyncStorage.getItem('language');
if (language !== i18next.language) {
i18next.changeLanguage(language);
if (Object.keys(SUPPORTED_LANGUAGES).includes(i18next.language)) {
if (language !== i18next.language) {
i18next.changeLanguage(language);
}
} else {
i18next.changeLanguage('en');
}
});
export default i18next;
function getLanguageCode(code: string) {
export function getLanguageCode(code: string) {
const [language] = code.split('-');
return language;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -43,30 +43,55 @@
"gender": "Gender",
"dateOfBirth": "Date of birth",
"phoneNumber": "Phone number",
"email": "Email",
"email": "Email Address",
"address": "Address",
"reasonForSharing": "Reason for sharing",
"idType": "ID type",
"id": "Id",
"nationalCard": "National Card",
"uin": "UIN",
"enableVerification": "Activate",
"profileAuthenticated": "Activated for online login",
"offlineAuthDisabledHeader": "Activation pending for online login",
"offlineAuthDisabledMessage": "Please click the button below to activate this credential to be used for online login.",
"vid": "VID",
"enabledAuthPopUp":"Credentials are enabled for online authentication.",
"verificationEnabledSuccess": "Activated for online login",
"goback": "GO BACK",
"BindingWarning": "If you have enabled verification for this credential on another wallet, it will get overridden. Do you want to proceed?",
"yes_confirm": "Yes, I Confirm",
"no": "No",
"Alert": "Alert",
"ok": "Okay"
"ok": "Okay",
"downloading": "Downloading Your Card",
"enableVerification": "Enable Verification",
"profileAuthenticated": "Profile is authenticated!",
"offlineAuthDisabledHeader": "Offline Authentication disabled!",
"offlineAuthDisabledMessage": "Click here to enable this credentials to be used for offline authentication."
},
"HomeScreenKebabPopUp": {
"title": "More Options",
"unPinCard": "Unpin Card",
"pinCard" : "Pin Card",
"offlineAuthenticationDisabled!": "Offline authentication disabled!",
"offlineAuthDisabledMessage": "Click here to enable this credentials to be used for offline authentication.",
"viewActivityLog": "View activity log",
"removeFromWallet": "Remove from wallet",
"revokeId": "Revoke ID",
"revokeMessage": "Revoke the virtual ID for this profile",
"ActivityLog":"View Activity Log"
},
"WalletBinding": {
"inProgress" : "In Progress",
"profileAuthenticated": "Profile is authenticated!"
},
"BindingVcWarningOverlay": {
"alert": "Please Confirm",
"BindingWarning": "If you have enabled verification for this credential on another wallet, it will get overridden. Do you want to proceed?",
"yesConfirm": "Yes, I confirm",
"no" : "No"
},
"AuthScreen": {
"header": "Would you like to use biometrics to unlock the application?",
"useBiometrics": "Use biometrics",
"usePasscode": "I'd rather use a passcode",
"header": "Select App Unlock Method",
"Description": "Would you like to use biometrics to unlock the application?",
"useBiometrics": "Use Biometrics",
"usePasscode": "Use Passcode",
"errors": {
"unavailable": "Device does not support Biometrics",
"unenrolled": "To use Biometrics, please enroll your biometrics in your device settings",
@@ -76,8 +101,8 @@
},
"BiometricScreen": {
"unlock": "Unlock with biometrics"
},
"HistoryTab": {
},
"HistoryScreen": {
"noHistory": "No history available yet",
"downloaded": "downloaded",
"shared": "shared",
@@ -89,7 +114,16 @@
"receivedVcsTab": "Received\n{{vcLabel}}",
"historyTab": "History"
},
"SettingScreen": {
"header": "Settings",
"bioUnlock": "Bio Unlock",
"language": "Language",
"aboutInji": "About Inji",
"injiTourGuide": "Inji Tour Guide",
"logout": "Logout"
},
"AddVcModal": {
"inputIdHeader":"Retrieve your ID",
"requestingCredential": "Requesting credential...",
"errors": {
"input": {
@@ -138,35 +172,45 @@
"qstnMarkToolTip": "Application ID is available in the acknowledgement received after enrolment."
},
"IdInputModal": {
"header": "Enter your UIN/VID to download your {{vcLabel}}",
"generateVc": "Generate My {{vcLabel}}",
"enterId": "Enter your {{idType}}",
"noUIN/VID": "Don't have your UIN/VID? Get it here",
"header": "Enter the MOSIP-provided UIN or VID of the {{vcLabel}} you wish to retrieve",
"title": "Retrieve your ID",
"guideLabel": "Select ID type and enter the MOSIP provided UIN or VID of the ID you wish to retrieve",
"generateVc": "Generate {{vcLabel}}",
"downloadID": "Download ID",
"enterId": "Enter {{idType}}",
"noUIN/VID": "Don't have UIN/VID?",
"getItHere": "Get it now",
"requestingOTP": "Requesting OTP..."
},
"OtpVerificationModal": {
"enterOtp": "Enter the 6-digit verification code we sent you",
"header": "OTP Verification"
"title": "OTP Verification",
"otpSentMessage": "We've sent the 6 digit code to your registered mobile number!",
"resendTheCode": "You can resend the code in ",
"resendCode": "Resend Code"
},
"MyVcsTab": {
"addVcButton": "Add {{vcLabel}}",
"generateVc": "Generate your {{vcLabel}}",
"generateVcDescription": "Tap on \"Add {{vcLabel}}\" below to download your {{vcLabel}}"
"downloadID": "Download ID",
"bringYourDigitalID": "Bring Your Digital ID",
"generateVcDescription": "To download your {{vcLabel}} tap Download {{vcLabel}} below",
"downloadingYourId": "Downloading your ID, this can take upto 5 minutes"
},
"OnboardingOverlay": {
"stepOneTitle": "Welcome!",
"stepOneText": "Keep your digital credential with you at all times. To get started, add {{vcLabel}} to your profile.",
"stepTwoTitle": "{{vcLabel}} management",
"stepTwoText": "Once generated, {{vcLabel}} are safely stored on your mobile and can be renamed or shared at any time.",
"stepThreeTitle": "Easy sharing",
"stepThreeText": "Share and receive {{vcLabel}} switfly using your phone camera to scan QR codes.",
"stepThreeButton": "Get started and add {{vcLabel}}"
"stepOneTitle": "Secure Sharing!",
"stepOneText": "Share and receive {{vcLabel}} switfly using your phone camera to scan QR codes",
"stepTwoTitle": "Trusted Digital Wallet",
"stepTwoText": "Keep your digital credential with you at all times",
"stepThreeTitle": "Quick Access",
"stepThreeText": "Once generated, {{vcLabel}} are safely stored on your mobile.",
"stepThreeButton": "Get Started",
"skip": "Skip",
"next": "Next"
},
"ReceivedVcsTab": {
"noReceivedVcsTitle": "No {{vcLabel}} available yet",
"noReceivedVcsText": "Tap on Request below to receive {{vcLabel}}"
},
"ViewVcModal": {
"title": "ID Details",
"cancel": "Cancel",
"lock": "Lock",
"unlock": "Unlock",
@@ -177,7 +221,6 @@
"requestingOtp": "Requesting OTP...",
"editTag": "Rename",
"redirecting": "Redirecting...",
"inProgress": "Loading...",
"success": {
"unlocked": "{{vcLabel}} successfully unlocked",
"locked": "{{vcLabel}} successfully locked",
@@ -188,11 +231,13 @@
"home": "Home",
"scan": "Scan",
"request": "Request",
"history": "History",
"settings": "Settings"
},
"PasscodeScreen": {
"header": "Set a passcode to secure your application",
"confirmPasscode": "Confirm your passcode",
"header": "Set Passcode",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
"confirmPasscode": "Confirm passcode",
"enterPasscode": "Enter your passcode"
},
"AppMetaData": {
@@ -201,14 +246,16 @@
"useBle": "Powered by BLE",
"useGoogleNearby": "Powered by GoogleNearby"
},
"ProfileScreen": {
"name": "Name",
"SettingScreen": {
"header": "Settings",
"profile": "Profile",
"vcLabel": "VC Label",
"language": "Language",
"bioUnlock": "Unlock with biometrics",
"authFactorUnlock": "Unlock auth factor",
"AppMetaData": "About Inji",
"logout": "Logout",
"credits": "Credits and legal notices",
"featuresWalkAround":"Feature Walkaround",
"logout": "Log-out",
"revokeLabel": "Revoke VID",
"revokeHeader": "REVOKE VID",
"revokingVids": "You are about to revoke ({{count}}) VIDs.",
@@ -299,7 +346,8 @@
"gotoSettings": "Go to settings"
},
"ScanScreen": {
"header": "Scan QR Code",
"scanningGuide": "Hold the phone study and scan the QR code",
"requester": "Requester",
"noShareableVcs": "No shareable {{vcLabel}} are available.",
"sharingVc": "Sharing {{vcLabel}}",
"bluetoothStateAndroid": "Please turn on bluetooth from quick settings to support local sharing",
@@ -317,7 +365,9 @@
}
},
"status": {
"connecting": "Connecting...",
"inProgress": "In Progress",
"establishingConnection": "Establishing Connection",
"sharingInProgress": "Sharing in Progress",
"connectingTimeout": "It's taking a while to establish the connection. Is the other device open for connections?",
"exchangingDeviceInfo": "Exchanging device info...",
"exchangingDeviceInfoTimeout": "It's taking a while to exchange device info. You may have to reconnect.",
@@ -331,8 +381,9 @@
"timeoutHint": "It's taking longer than expected to share. There could be a problem with the connection."
},
"accepted": {
"title": "Success!",
"message": "Your {{vcLabel}} has been successfully shared with {{receiver}}"
"title": "ID shared successfully",
"message": "Your ID has been successfully shared with ",
"gotohome": "Go to home"
},
"rejected": {
"title": "Notice",
@@ -351,11 +402,22 @@
"verifyAndShare": "Verify Identity & Share"
},
"SendVcScreen": {
"reasonForSharing": "Reason for sharing (optional)",
"acceptRequest": "Share",
"acceptRequestAndVerify": "Share with Selfie",
"reject": "Reject",
"consentToPhotoVerification": "I give consent to have my photo taken for authentication"
"reasonForSharing": "Reason for sharing",
"approveRequest": "Approve request and choose {{vcLabel}}",
"approveRequestAndVerify": "Approve request and verify",
"consentToPhotoVerification": "I give consent to have my photo taken for authentication",
"pleaseSelectAnId": "Please select an ID",
"reject": "Reject"
},
"Credits": {
"header": "Credits and legal notices",
"back": "Back",
"reasonForSharing": "Reason for sharing",
"approveRequest": "Approve request and choose {{vcLabel}}",
"approveRequestAndVerify": "Approve request and verify",
"consentToPhotoVerification": "I give consent to have my photo taken for authentication",
"pleaseSelectAnId": "Please select an ID",
"reject": "Reject"
},
"VerifyIdentityOverlay": {
"status": {
@@ -374,6 +436,11 @@
"getStarted": "Get started",
"unlockApp": "Unlock application"
},
"SetupLanguage": {
"header": "Choose Language",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
"save": "Save Preference"
},
"common": {
"cancel": "Cancel",
"save": "Save",
@@ -390,4 +457,4 @@
"genericError": "Something went wrong. Please try again after some time!"
}
}
}
}

View File

@@ -89,6 +89,14 @@
"receivedVcsTab": "Nakuhang\n{{vcLabel}}",
"historyTab": "Pangyayari"
},
"SettingScreen": {
"header": "Mga setting",
"profile": "Profile",
"vcLabel": "Label ng Vc",
"language": "Wika",
"bioUnlock": "Bio Unlock",
"logout": "Log out"
},
"AddVcModal": {
"requestingCredential": "Humihiling ng kredensyal...",
"errors": {
@@ -147,9 +155,9 @@
"header": "Pag-verify ng OTP"
},
"MyVcsTab": {
"addVcButton": "Magdagdag ng {{vcLabel}}",
"generateVc": "Gumawa ng iyong {{vcLabel}}",
"generateVcDescription": "Pindutin ang \"Magdagdag ng {{vcLabel}}\" sa ibaba upang makuha ang iyong {{vcLabel}}"
"downloadID": "I-download ang ID",
"bringYourDigitalID": "Dalhin ang iyong digital identity",
"generateVcDescription": "I-tap ang \"Download {{vcLabel}}\" sa ibaba para i-download ang iyong {{vcLabel}}"
},
"OnboardingOverlay": {
"stepOneTitle": "Mabuhay!",
@@ -186,7 +194,8 @@
"home": "Bahay",
"scan": "Scan",
"request": "Request",
"settings": "mga setting"
"settings": "mga setting",
"history": "Kasaysayan"
},
"PasscodeScreen": {
"header": "Magtakda ng passcode upang masigurado ang iyong aplikasyon",
@@ -284,7 +293,8 @@
"gotoSettings": "Pumunta sa setting"
},
"ScanScreen": {
"header": "I-scan ang QR Code",
"scanningGuide": "Hawakan ang telepono at i-scan ang QR code",
"requester": "Humihiling",
"noShareableVcs": "Walang magagamit na maibabahaging {{vcLabel}}.",
"bluetoothStateAndroid": "Mangyaring i-on ang bluetooth mula sa mga mabilisang setting upang suportahan ang lokal na pagbabahagi",
"bluetoothStateIos": "Mangyaring i-on ang bluetooth mula sa control center upang suportahan ang lokal na pagbabahagi",
@@ -355,6 +365,11 @@
"getStarted": "Magsimula",
"unlockApp": "Buksan ang aplikasyon"
},
"SetupLanguage": {
"header": "Piliin ang Wika",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
"save": "I-save ang Kagustuhan"
},
"common": {
"cancel": "Kanselahin",
"save": "I-save",

View File

@@ -149,9 +149,9 @@
"header": "ओटीपी सत्यापन"
},
"MyVcsTab": {
"addVcButton": "{{vcLabel}} जोड़ें",
"generateVc": "अपना {{vcLabel}}जेनरेट करें",
"generateVcDescription": "अपना {{vcLabel}} डाउनलोड करने के लिए नीचे \"जोड़ें {{vcLabel}}\" पर टैप करें"
"downloadID": "डाउनलोड आईडी",
"bringYourDigitalID": "अपनी डिजिटल पहचान लाओ",
"generateVcDescription": "अपना {{vcLabel}} डाउनलोड करने के लिए नीचे डाउनलोड {{vcLabel}} पर टैप करें"
},
"OnboardingOverlay": {
"stepOneTitle": "स्वागत है!",
@@ -185,10 +185,11 @@
}
},
"MainLayout": {
"home": "घर",
"home": "होम",
"scan": "स्कैन",
"request": "अनुरोध",
"settings": "सेटिंग्स"
"settings": "सेटिंग्स",
"history": "इतिहास"
},
"PasscodeScreen": {
"header": "अपना आवेदन सुरक्षित करने के लिए पासकोड सेट करें",
@@ -201,8 +202,9 @@
"useBle": "Powered by BLE",
"useGoogleNearby": "Powered by GoogleNearby"
},
"ProfileScreen": {
"name": "नाम",
"SettingScreen": {
"header": "समायोजन",
"profile": "प्रोफ़ाइल",
"vcLabel": "वीसी लेबल",
"language": "भाषा",
"bioUnlock": "बायोमेट्रिक्स से अनलॉक करें",
@@ -287,7 +289,8 @@
"gotoSettings": "सेटिंग्स में जाओ"
},
"ScanScreen": {
"header": "क्यूआर कोड स्कैन करे",
"scanningGuide": "फोन को पकड़ें और क्यूआर कोड को स्कैन करे",
"requester": "अनुरोधकर्ता",
"noShareableVcs": "कोई साझा करने योग्य {{vcLabel}} उपलब्ध नहीं है।",
"bluetoothStateAndroid": "स्थानीय साझाकरण का समर्थन करने के लिए कृपया त्वरित सेटिंग से ब्लूटूथ चालू करें",
"bluetoothStateIos": " स्थानीय साझाकरण का समर्थन करने के लिए कृपया नियंत्रण केंद्र से ब्लूटूथ चालू करें",
@@ -374,6 +377,11 @@
"getStarted": "आरंभ करें",
"unlockApp": "एप्लिकेशन अनलॉक करें"
},
"SetupLanguage": {
"header": "भाषा चुनें",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
"save": "वरीयता सहेजें"
},
"common": {
"cancel": "रद्द करें",
"save": "सहेजें",

View File

@@ -124,6 +124,7 @@ export type ActivityLogType =
| 'VC_RECEIVED_NOT_SAVED'
| 'VC_DELETED'
| 'VC_DOWNLOADED'
| 'VC_UPDATED'
| 'VC_REVOKED'
| 'VC_SHARED_WITH_VERIFICATION_CONSENT'
| 'VC_RECEIVED_WITH_PRESENCE_VERIFIED'

View File

@@ -17,7 +17,6 @@ import * as BLERequest from './openIdBle/request';
import * as BLEScan from './openIdBle/scan';
import { createScanMachine, scanMachine } from './scan';
import { createRevokeMachine, revokeVidsMachine } from './revoke';
import { pure, respond } from 'xstate/lib/actions';
import { AppServices } from '../shared/GlobalContext';
import { request } from '../shared/request';

View File

@@ -1,7 +1,9 @@
import { init } from 'mosip-inji-face-sdk';
import { ContextFrom, EventFrom, send, StateFrom } from 'xstate';
import { assign, ContextFrom, EventFrom, send, StateFrom } from 'xstate';
import { createModel } from 'xstate/lib/model';
import { downloadModel } from '../shared/commonprops/commonProps';
import getAllConfigurations, {
downloadModel,
} from '../shared/commonprops/commonProps';
import { AppServices } from '../shared/GlobalContext';
import { StoreEvents, StoreResponseEvent } from './store';
@@ -11,6 +13,7 @@ const model = createModel(
passcode: '',
biometrics: '',
canUseBiometrics: false,
selectLanguage: false,
},
{
events: {
@@ -19,6 +22,8 @@ const model = createModel(
LOGOUT: () => ({}),
LOGIN: () => ({}),
STORE_RESPONSE: (response?: unknown) => ({ response }),
SELECT: () => ({}),
NEXT: () => ({}),
},
}
);
@@ -60,21 +65,36 @@ export const authMachine = model.createMachine(
},
checkingAuth: {
always: [
{ cond: 'hasLanguageset', target: 'languagesetup' },
{ cond: 'hasPasscodeSet', target: 'unauthorized' },
{ cond: 'hasBiometricSet', target: 'unauthorized' },
{ target: 'settingUp' },
],
},
languagesetup: {
on: {
SELECT: {
target: 'introSlider',
},
},
},
introSlider: {
on: {
NEXT: {
target: 'settingUp',
},
},
},
settingUp: {
on: {
SETUP_PASSCODE: {
target: 'authorized',
actions: ['setPasscode', 'storeContext'],
actions: ['setPasscode', 'storeContext', 'setLanguage'],
},
SETUP_BIOMETRICS: {
// Note! dont authorized yet we need to setup passcode too as discuss
// target: 'authorized',
actions: ['setBiometrics', 'storeContext'],
actions: ['setBiometrics', 'storeContext', 'setLanguage'],
},
},
},
@@ -127,11 +147,25 @@ export const authMachine = model.createMachine(
setBiometrics: model.assign({
biometrics: (_, event: SetupBiometricsEvent) => event.biometrics,
}),
setLanguage: assign({
selectLanguage: (context) => !context.selectLanguage,
}),
},
services: {
downloadFaceSdkModel: () => () => {
downloadModel();
downloadFaceSdkModel: () => async () => {
var injiProp = null;
try {
var injiProp = await getAllConfigurations();
const resp: string =
injiProp != null ? injiProp.faceSdkModelUrl : null;
if (resp != null) {
init(resp, false);
}
} catch (error) {
console.log(error);
}
},
},
@@ -144,6 +178,9 @@ export const authMachine = model.createMachine(
hasBiometricSet: (context) => {
return context.biometrics !== '' && context.passcode !== '';
},
hasLanguageset: (context) => {
return !context.selectLanguage;
},
},
}
);
@@ -180,3 +217,10 @@ export function selectUnauthorized(state: State) {
export function selectSettingUp(state: State) {
return state.matches('settingUp');
}
export function selectLanguagesetup(state: State) {
return state.matches('languagesetup');
}
export function selectIntroSlider(state: State) {
return state.matches('introSlider');
}

View File

@@ -24,6 +24,7 @@ export interface Typegen0 {
requestStoredContext: 'xstate.init';
setBiometrics: 'SETUP_BIOMETRICS';
setContext: 'STORE_RESPONSE';
setLanguage: 'SETUP_BIOMETRICS' | 'SETUP_PASSCODE';
setPasscode: 'SETUP_PASSCODE';
storeContext:
| 'SETUP_BIOMETRICS'
@@ -35,6 +36,7 @@ export interface Typegen0 {
'eventsCausingGuards': {
hasBiometricSet: '';
hasData: 'STORE_RESPONSE';
hasLanguageset: '';
hasPasscodeSet: '';
};
'eventsCausingServices': {
@@ -44,6 +46,8 @@ export interface Typegen0 {
| 'authorized'
| 'checkingAuth'
| 'init'
| 'introSlider'
| 'languagesetup'
| 'savingDefaults'
| 'settingUp'
| 'unauthorized';

View File

@@ -6,6 +6,7 @@ import { EventFrom, Receiver, sendParent, send, sendUpdate } from 'xstate';
import { createModel } from 'xstate/lib/model';
import { generateSecureRandom } from 'react-native-securerandom';
import { log } from 'xstate/lib/actions';
import { VC_ITEM_STORE_KEY } from '../shared/constants';
const ENCRYPTION_ID = 'c7c22a6c-9759-4605-ac88-46f4041d863d';
@@ -21,6 +22,7 @@ const model = createModel(
SET: (key: string, value: unknown) => ({ key, value }),
APPEND: (key: string, value: unknown) => ({ key, value }),
PREPEND: (key: string, value: unknown) => ({ key, value }),
UPDATE: (key: string, value: string) => ({ key, value }),
REMOVE: (key: string, value: string) => ({ key, value }),
REMOVE_ITEMS: (key: string, values: string[]) => ({ key, values }),
CLEAR: () => ({}),
@@ -116,6 +118,9 @@ export const storeMachine =
PREPEND: {
actions: 'forwardStoreRequest',
},
UPDATE: {
actions: 'forwardStoreRequest',
},
REMOVE: {
actions: 'forwardStoreRequest',
},
@@ -205,6 +210,16 @@ export const storeMachine =
response = event.value;
break;
}
case 'UPDATE': {
await updateItem(
event.key,
event.value,
context.encryptionKey
);
response = event.value;
break;
}
case 'REMOVE': {
await removeItem(
event.key,
@@ -341,7 +356,30 @@ export async function prependItem(
throw e;
}
}
export async function updateItem(
key: string,
value: string,
encryptionKey: string
) {
try {
const list = await getItem(key, [], encryptionKey);
const newList = [
value,
...list.map((item) => {
const vc = item.split(':');
if (vc[3] !== value.split(':')[3]) {
vc[4] = 'false';
return vc.join(':');
}
}),
].filter((value) => value != undefined && value !== null);
await setItem(key, newList, encryptionKey);
} catch (e) {
console.error('error prependItem:', e);
throw e;
}
}
export async function removeItem(
key: string,
value: string,

View File

@@ -36,7 +36,8 @@ export interface Typegen0 {
| 'PREPEND'
| 'REMOVE'
| 'REMOVE_ITEMS'
| 'SET';
| 'SET'
| 'UPDATE';
notifyParent:
| 'KEY_RECEIVED'
| 'done.invoke.store.resettingStorage:invocation[0]';

View File

@@ -26,8 +26,10 @@ const model = createModel(
STORE_RESPONSE: (response: unknown) => ({ response }),
STORE_ERROR: (error: Error) => ({ error }),
VC_ADDED: (vcKey: string) => ({ vcKey }),
VC_UPDATED: (vcKey: string) => ({ vcKey }),
VC_RECEIVED: (vcKey: string) => ({ vcKey }),
VC_DOWNLOADED: (vc: VC) => ({ vc }),
VC_UPDATE: (vc: VC) => ({ vc }),
REFRESH_MY_VCS: () => ({}),
REFRESH_MY_VCS_TWO: (vc: VC) => ({ vc }),
REFRESH_RECEIVED_VCS: () => ({}),
@@ -133,9 +135,15 @@ export const vcMachine =
VC_ADDED: {
actions: 'prependToMyVcs',
},
VC_UPDATED: {
actions: ['updateMyVcs', 'setUpdateVc'],
},
VC_DOWNLOADED: {
actions: 'setDownloadedVc',
},
VC_UPDATE: {
actions: 'setVcUpdate',
},
VC_RECEIVED: [
{
actions: 'moveExistingVcToTop',
@@ -181,10 +189,35 @@ export const vcMachine =
context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc;
},
setVcUpdate: (context, event) => {
context.vcs[VC_ITEM_STORE_KEY(event.vc)] = event.vc;
},
setUpdateVc: send(
(_context, event) => {
return StoreEvents.UPDATE(MY_VCS_STORE_KEY, event.vcKey);
},
{ to: (context) => context.serviceRefs.store }
),
prependToMyVcs: model.assign({
myVcs: (context, event) => [event.vcKey, ...context.myVcs],
}),
updateMyVcs: model.assign({
myVcs: (context, event) =>
[
event.vcKey,
...context.myVcs.map((value) => {
const vc = value.split(':');
if (vc[3] !== event.vcKey.split(':')[3]) {
vc[4] = 'false';
return vc.join(':');
}
}),
].filter((value) => value != undefined),
}),
prependToReceivedVcs: model.assign({
receivedVcs: (context, event) => [
event.vcKey,

View File

@@ -0,0 +1,57 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
'internalEvents': {
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
delays: never;
guards: never;
services: never;
};
'eventsCausingActions': {
getReceivedVcsResponse: 'GET_RECEIVED_VCS';
getVcItemResponse: 'GET_VC_ITEM';
loadMyVcs: 'REFRESH_MY_VCS' | 'xstate.init';
loadReceivedVcs: 'REFRESH_RECEIVED_VCS' | 'STORE_RESPONSE';
moveExistingVcToTop: 'VC_RECEIVED';
prependToMyVcs: 'VC_ADDED';
prependToReceivedVcs: 'VC_RECEIVED';
setDownloadedVc: 'VC_DOWNLOADED';
setMyVcs: 'STORE_RESPONSE';
setReceivedVcs: 'STORE_RESPONSE';
setUpdateVc: 'VC_UPDATED';
setVcUpdate: 'VC_UPDATE';
updateMyVcs: 'VC_UPDATED';
};
'eventsCausingDelays': {};
'eventsCausingGuards': {
hasExistingReceivedVc: 'VC_RECEIVED';
};
'eventsCausingServices': {};
'matchesStates':
| 'init'
| 'init.myVcs'
| 'init.receivedVcs'
| 'ready'
| 'ready.myVcs'
| 'ready.myVcs.idle'
| 'ready.myVcs.refreshing'
| 'ready.receivedVcs'
| 'ready.receivedVcs.idle'
| 'ready.receivedVcs.refreshing'
| {
init?: 'myVcs' | 'receivedVcs';
ready?:
| 'myVcs'
| 'receivedVcs'
| {
myVcs?: 'idle' | 'refreshing';
receivedVcs?: 'idle' | 'refreshing';
};
};
'tags': never;
}

View File

@@ -25,7 +25,7 @@ import {
import getAllConfigurations, {
DownloadProps,
} from '../shared/commonprops/commonProps';
import i18n from '../i18n';
import { VcEvents } from './vc';
const model = createModel(
{
@@ -38,6 +38,7 @@ const model = createModel(
verifiableCredential: null as VerifiableCredential,
requestId: '',
isVerified: false,
isPinned: false,
lastVerifiedOn: null,
locked: false,
otp: '',
@@ -75,6 +76,9 @@ const model = createModel(
ADD_WALLET_BINDING_ID: () => ({}),
CANCEL: () => ({}),
CONFIRM: () => ({}),
PIN_CARD: () => ({}),
KEBAB_POPUP: () => ({}),
SHOW_ACTIVITY: () => ({}),
},
}
);
@@ -213,6 +217,150 @@ export const vcItemMachine =
ADD_WALLET_BINDING_ID: {
target: 'showBindingWarning',
},
PIN_CARD: {
target: 'pinCard',
actions: 'setPinCard',
},
KEBAB_POPUP: {
target: 'kebabPopUp',
},
DISMISS: {
target: 'checkingVc',
},
},
},
pinCard: {
entry: 'storeContext',
on: {
STORE_RESPONSE: {
actions: 'sendVcUpdated',
target: 'idle',
},
},
},
kebabPopUp: {
on: {
DISMISS: {
target: 'idle',
},
ADD_WALLET_BINDING_ID: {
target: '#vc-item.kebabPopUp.showBindingWarning',
},
PIN_CARD: {
target: '#vc-item.pinCard',
actions: 'setPinCard',
},
SHOW_ACTIVITY: {
target: '#vc-item.kebabPopUp.showActivities',
},
},
initial: 'idle',
states: {
idle: {},
showBindingWarning: {
on: {
CONFIRM: {
target: '#vc-item.kebabPopUp.requestingBindingOtp',
},
CANCEL: {
target: '#vc-item.kebabPopUp',
},
},
},
requestingBindingOtp: {
invoke: {
src: 'requestBindingOtp',
onDone: [
{
target: '#vc-item.kebabPopUp.acceptingBindingOtp',
},
],
onError: [
{
actions: 'setWalletBindingError',
target: '#vc-item.kebabPopUp.showingWalletBindingError',
},
],
},
},
showingWalletBindingError: {
on: {
CANCEL: {
target: '#vc-item.kebabPopUp',
actions: 'setWalletBindingErrorEmpty',
},
},
},
acceptingBindingOtp: {
entry: ['clearOtp'],
on: {
INPUT_OTP: {
target: '#vc-item.kebabPopUp.addKeyPair',
actions: ['setOtp'],
},
DISMISS: {
target: '#vc-item.kebabPopUp',
actions: ['clearOtp', 'clearTransactionId'],
},
},
},
addKeyPair: {
invoke: {
src: 'generateKeyPair',
onDone: {
target: '#vc-item.kebabPopUp.addingWalletBindingId',
actions: ['setPublicKey', 'setPrivateKey'],
},
onError: [
{
actions: 'setWalletBindingError',
target: '#vc-item.kebabPopUp.showingWalletBindingError',
},
],
},
},
addingWalletBindingId: {
invoke: {
src: 'addWalletBindnigId',
onDone: [
{
target: '#vc-item.kebabPopUp.updatingPrivateKey',
actions: ['setWalletBindingId'],
},
],
onError: [
{
actions: 'setWalletBindingError',
target: '#vc-item.kebabPopUp.showingWalletBindingError',
},
],
},
},
updatingPrivateKey: {
invoke: {
src: 'updatePrivateKey',
onDone: {
actions: [
'storeContext',
'updatePrivateKey',
'updateVc',
'setWalletBindingErrorEmpty',
'logWalletBindingSuccess',
],
target: '#vc-item.kebabPopUp',
},
onError: {
actions: 'setWalletBindingError',
target: '#vc-item.kebabPopUp.showingWalletBindingError',
},
},
},
showActivities: {
on: {
DISMISS: '#vc-item.kebabPopUp',
},
},
},
},
editingTag: {
@@ -479,7 +627,6 @@ export const vcItemMachine =
invoke: {
src: 'updatePrivateKey',
onDone: {
target: 'idle',
actions: [
'storeContext',
'updatePrivateKey',
@@ -487,6 +634,7 @@ export const vcItemMachine =
'setWalletBindingErrorEmpty',
'logWalletBindingSuccess',
],
target: 'idle',
},
onError: {
actions: ['setWalletBindingError', 'logWalletBindingFailure'],
@@ -526,6 +674,21 @@ export const vcItemMachine =
event.data as WalletBindingResponse,
}),
setPinCard: assign((context) => {
return {
...context,
isPinned: !context.isPinned,
};
}),
sendVcUpdated: send(
(_context, event) =>
VcEvents.VC_UPDATED(VC_ITEM_STORE_KEY(event.response) as string),
{
to: (context) => context.serviceRefs.vc,
}
),
updateVc: send(
(context) => {
const { serviceRefs, ...vc } = context;
@@ -739,7 +902,7 @@ export const vcItemMachine =
authFactorType: 'WLA',
format: 'jwt',
individualId: context.id,
transactionId: context.bindingTransactionId,
transactionId: context.transactionId,
publicKey: context.publicKey,
challengeList: [
{
@@ -852,6 +1015,7 @@ export const vcItemMachine =
tag: '',
requestId: context.requestId,
isVerified: false,
isPinned: context.isPinned,
lastVerifiedOn: null,
locked: context.locked,
walletBindingResponse: null,
@@ -1005,6 +1169,9 @@ export function selectIsOtpError(state: State) {
export function selectOtpError(state: State) {
return state.context.otpError;
}
export function selectIsPinned(state: State) {
return state.context.isPinned;
}
export function selectIsLockingVc(state: State) {
return state.matches('lockingVc');
@@ -1026,25 +1193,21 @@ export function selectIsAcceptingRevokeInput(state: State) {
return state.matches('acceptingRevokeInput');
}
export function selectIsRequestBindingOtp(state: State) {
return state.matches('requestingBindingOtp');
}
export function selectWalletBindingId(state: State) {
return state.context.walletBindingResponse;
}
export function selectEmptyWalletBindingId(state: State) {
var val = state.context.walletBindingResponse
? state.context.walletBindingResponse.walletBindingId
: undefined;
return val === undefined || val == null || val.length <= 0 ? true : false;
return val == undefined || val == null || val.length <= 0 ? true : false;
}
export function selectWalletBindingError(state: State) {
return state.context.walletBindingError;
}
export function selectRequestBindingOtp(state: State) {
return state.matches('requestingBindingOtp');
}
export function selectAcceptingBindingOtp(state: State) {
return state.matches('acceptingBindingOtp');
}
@@ -1053,7 +1216,7 @@ export function selectShowWalletBindingError(state: State) {
return state.matches('showingWalletBindingError');
}
export function isWalletBindingInProgress(state: State) {
export function selectWalletBindingInProgress(state: State) {
return state.matches('requestingBindingOtp') ||
state.matches('addingWalletBindingId') ||
state.matches('addKeyPair') ||
@@ -1062,6 +1225,38 @@ export function isWalletBindingInProgress(state: State) {
: false;
}
export function isShowingBindingWarning(state: State) {
export function selectBindingWarning(state: State) {
return state.matches('showBindingWarning');
}
export function selectKebabPopUp(state: State) {
return state.matches('kebabPopUp');
}
export function selectKebabPopUpRequestBindingOtp(state: State) {
return state.matches('kebabPopUp.requestingBindingOtp');
}
export function selectKebabPopUpAcceptingBindingOtp(state: State) {
return state.matches('kebabPopUp.acceptingBindingOtp');
}
export function selectKebabPopUpShowWalletBindingError(state: State) {
return state.matches('kebabPopUp.showingWalletBindingError');
}
export function selectKebabPopUpWalletBindingInProgress(state: State) {
return state.matches('kebabPopUp.requestingBindingOtp') ||
state.matches('kebabPopUp.addingWalletBindingId') ||
state.matches('kebabPopUp.addKeyPair') ||
state.matches('kebabPopUp.updatingPrivateKey')
? true
: false;
}
export function selectKebabPopUpBindingWarning(state: State) {
return state.matches('kebabPopUp.showBindingWarning');
}
export function selectShowActivities(state: State) {
return state.matches('kebabPopUp.showActivities');
}

View File

@@ -29,6 +29,26 @@ export interface Typegen0 {
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]': {
type: 'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]': {
type: 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]': {
type: 'done.invoke.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]': {
type: 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.vc-item.requestingBindingOtp:invocation[0]': {
type: 'done.invoke.vc-item.requestingBindingOtp:invocation[0]';
data: unknown;
@@ -75,6 +95,26 @@ export interface Typegen0 {
type: 'error.platform.vc-item.addingWalletBindingId:invocation[0]';
data: unknown;
};
'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]': {
type: 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
data: unknown;
};
'error.platform.vc-item.kebabPopUp.addKeyPair:invocation[0]': {
type: 'error.platform.vc-item.kebabPopUp.addKeyPair:invocation[0]';
data: unknown;
};
'error.platform.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]': {
type: 'error.platform.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
data: unknown;
};
'error.platform.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]': {
type: 'error.platform.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]';
data: unknown;
};
'error.platform.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]': {
type: 'error.platform.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]';
data: unknown;
};
'error.platform.vc-item.requestingBindingOtp:invocation[0]': {
type: 'error.platform.vc-item.requestingBindingOtp:invocation[0]';
data: unknown;
@@ -98,16 +138,24 @@ export interface Typegen0 {
'xstate.init': { type: 'xstate.init' };
};
'invokeSrcNameMap': {
addWalletBindnigId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
addWalletBindnigId:
| 'done.invoke.vc-item.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
checkDownloadExpiryLimit: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
checkStatus: 'done.invoke.checkStatus';
downloadCredential: 'done.invoke.downloadCredential';
generateKeyPair: 'done.invoke.vc-item.addKeyPair:invocation[0]';
requestBindingOtp: 'done.invoke.vc-item.requestingBindingOtp:invocation[0]';
generateKeyPair:
| 'done.invoke.vc-item.addKeyPair:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]';
requestBindingOtp:
| 'done.invoke.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item.requestingBindingOtp:invocation[0]';
requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
requestOtp: 'done.invoke.vc-item.requestingOtp:invocation[0]';
requestRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
updatePrivateKey: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
updatePrivateKey:
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
verifyCredential: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
'missingImplementations': {
@@ -123,6 +171,7 @@ export interface Typegen0 {
| 'DISMISS'
| 'REVOKE_VC'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item.requestingBindingOtp:invocation[0]'
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
@@ -141,22 +190,37 @@ export interface Typegen0 {
incrementDownloadCounter: 'POLL';
logDownloaded: 'CREDENTIAL_DOWNLOADED';
logRevoked: 'STORE_RESPONSE';
logWalletBindingFailure:
| 'error.platform.vc-item.addKeyPair:invocation[0]'
| 'error.platform.vc-item.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
logWalletBindingSuccess:
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'xstate.init';
requestVcContext: 'DISMISS' | 'xstate.init';
revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
sendVcUpdated: 'STORE_RESPONSE';
setCredential:
| 'CREDENTIAL_DOWNLOADED'
| 'GET_VC_RESPONSE'
| 'STORE_RESPONSE';
setDownloadInterval: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
setMaxDownloadCount: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
setOtp: 'INPUT_OTP';
setOtpError:
| 'error.platform.vc-item.requestingLock:invocation[0]'
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
setPrivateKey: 'done.invoke.vc-item.addKeyPair:invocation[0]';
setPublicKey: 'done.invoke.vc-item.addKeyPair:invocation[0]';
setPinCard: 'PIN_CARD';
setPrivateKey:
| 'done.invoke.vc-item.addKeyPair:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]';
setPublicKey:
| 'done.invoke.vc-item.addKeyPair:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]';
setRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
setTag: 'SAVE_TAG';
setTransactionId:
@@ -168,22 +232,34 @@ export interface Typegen0 {
setWalletBindingError:
| 'error.platform.vc-item.addKeyPair:invocation[0]'
| 'error.platform.vc-item.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item.kebabPopUp.addKeyPair:invocation[0]'
| 'error.platform.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]'
| 'error.platform.vc-item.kebabPopUp.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'error.platform.vc-item.requestingBindingOtp:invocation[0]'
| 'error.platform.vc-item.updatingPrivateKey:invocation[0]';
setWalletBindingErrorEmpty:
| 'CANCEL'
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
setWalletBindingId: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
setWalletBindingId:
| 'done.invoke.vc-item.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
storeContext:
| 'CREDENTIAL_DOWNLOADED'
| 'PIN_CARD'
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
storeLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
storeTag: 'SAVE_TAG';
updatePrivateKey: 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
updatePrivateKey:
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
updateVc:
| 'CREDENTIAL_DOWNLOADED'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
@@ -194,16 +270,22 @@ export interface Typegen0 {
isVcValid: '';
};
'eventsCausingServices': {
addWalletBindnigId: 'done.invoke.vc-item.addKeyPair:invocation[0]';
addWalletBindnigId:
| 'done.invoke.vc-item.addKeyPair:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addKeyPair:invocation[0]';
checkDownloadExpiryLimit: 'STORE_RESPONSE';
checkStatus: 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
checkStatus:
| 'done.invoke.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]'
| 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]';
downloadCredential: 'DOWNLOAD_READY';
generateKeyPair: 'INPUT_OTP';
requestBindingOtp: 'CONFIRM';
requestLock: 'INPUT_OTP';
requestOtp: 'LOCK_VC';
requestRevoke: 'INPUT_OTP';
updatePrivateKey: 'done.invoke.vc-item.addingWalletBindingId:invocation[0]';
updatePrivateKey:
| 'done.invoke.vc-item.addingWalletBindingId:invocation[0]'
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]';
verifyCredential: '' | 'VERIFY';
};
'matchesStates':
@@ -224,8 +306,19 @@ export interface Typegen0 {
| 'invalid'
| 'invalid.backend'
| 'invalid.otp'
| 'kebabPopUp'
| 'kebabPopUp.acceptingBindingOtp'
| 'kebabPopUp.addKeyPair'
| 'kebabPopUp.addingWalletBindingId'
| 'kebabPopUp.idle'
| 'kebabPopUp.requestingBindingOtp'
| 'kebabPopUp.showActivities'
| 'kebabPopUp.showBindingWarning'
| 'kebabPopUp.showingWalletBindingError'
| 'kebabPopUp.updatingPrivateKey'
| 'lockingVc'
| 'loggingRevoke'
| 'pinCard'
| 'requestingBindingOtp'
| 'requestingLock'
| 'requestingOtp'
@@ -242,6 +335,16 @@ export interface Typegen0 {
| 'downloadingCredential'
| 'verifyingDownloadLimitExpiry';
invalid?: 'backend' | 'otp';
kebabPopUp?:
| 'acceptingBindingOtp'
| 'addKeyPair'
| 'addingWalletBindingId'
| 'idle'
| 'requestingBindingOtp'
| 'showActivities'
| 'showBindingWarning'
| 'showingWalletBindingError'
| 'updatingPrivateKey';
};
'tags': never;
}

1627
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
"@digitalbazaar/rsa-signature-2018": "digitalbazaar/rsa-signature-2018#initial",
"@digitalbazaar/rsa-verification-key-2018": "digitalbazaar/rsa-verification-key-2018#initial",
"@digitalcredentials/vc": "^1.1.2",
"@expo-google-fonts/inter": "^0.2.3",
"@expo-google-fonts/poppins": "^0.2.0",
"@expo/metro-config": "^0.3.12",
"@idpass/smartshare-react-native": "0.2.3-beta.2",
@@ -49,15 +50,18 @@
"react": "17.0.1",
"react-i18next": "^11.16.6",
"react-native": "0.64.4",
"react-native-animated-pagination-dot": "^0.4.0",
"react-native-app-intro-slider": "^4.0.4",
"react-native-biometrics-changed": "^1.1.8",
"react-native-bluetooth-state-manager": "^1.3.2",
"react-native-cli": "^2.0.1",
"react-native-device-info": "^8.4.8",
"react-native-dotenv": "^3.3.1",
"react-native-elements": "^3.4.2",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.1.0",
"react-native-keychain": "^8.0.0",
"react-native-linear-gradient": "^2.6.2",
"react-native-location-enabler": "^4.1.0",
"react-native-openid4vp-ble": "github:mosip/tuvali#v0.3.10",
"react-native-permissions": "^3.6.0",

View File

@@ -9,19 +9,24 @@ import { WelcomeScreen } from '../screens/WelcomeScreen';
import { PasscodeScreen } from '../screens/PasscodeScreen';
import { MainLayout } from '../screens/MainLayout';
import { NotificationsScreen } from '../screens/NotificationsScreen';
import { Image } from 'react-native';
import { SetupLanguageScreen } from '../screens/SetupLanguageScreen';
import { IntroSlidersScreen } from '../screens/Home/IntroSlidersScreen';
export const baseRoutes: Screen[] = [
{
name: 'Language',
component: SetupLanguageScreen,
},
{
name: 'IntroSliders',
component: IntroSlidersScreen,
options: {
headerShown: false,
},
},
{
name: 'Welcome',
component: WelcomeScreen,
options: {
headerLeft: () =>
React.createElement(Image, {
source: require('../assets/idpass-logo.png'),
style: { width: 124, height: 27, resizeMode: 'contain' },
}),
},
},
{
name: 'Auth',
@@ -52,6 +57,8 @@ export const authRoutes: Screen[] = [
];
export type RootStackParamList = {
Language: undefined;
IntroSliders: undefined;
Welcome: undefined;
Auth: undefined;
Passcode: {

View File

@@ -3,11 +3,12 @@ import {
BottomTabNavigationOptions,
BottomTabScreenProps,
} from '@react-navigation/bottom-tabs';
import { Image } from 'react-native';
import { HomeScreen } from '../screens/Home/HomeScreen';
import { ProfileScreen } from '../screens/Profile/ProfileScreen';
import { RootStackParamList } from './index';
import { RequestLayout } from '../screens/Request/RequestLayout';
import { ScanLayout } from '../screens/Scan/ScanLayout';
import { HistoryScreen } from '../screens/History/HistoryScreen';
import i18n from '../i18n';
import { Platform } from 'react-native';
import { isGoogleNearbyEnabled } from '../lib/smartshare';
@@ -17,7 +18,12 @@ const home: TabScreen = {
component: HomeScreen,
icon: 'home',
options: {
title: i18n.t('MainLayout:home'),
headerTitle: '',
headerLeft: () =>
React.createElement(Image, {
source: require('../assets/inji-home-logo.png'),
style: { width: 124, height: 27, resizeMode: 'contain' },
}),
},
};
const scan: TabScreen = {
@@ -38,12 +44,13 @@ const request: TabScreen = {
headerShown: false,
},
};
const settings: TabScreen = {
name: 'Settings',
component: ProfileScreen,
icon: 'settings',
const history: TabScreen = {
name: 'History',
component: HistoryScreen,
icon: 'history',
options: {
title: i18n.t('MainLayout:Settings'),
title: i18n.t('MainLayout:history'),
headerRight: null,
},
};
@@ -55,7 +62,7 @@ if (Platform.OS !== 'ios' || isGoogleNearbyEnabled) {
mainRoutes.push(request);
}
mainRoutes.push(settings);
mainRoutes.push(history);
export type MainBottomTabParamList = {
Home: {
@@ -63,7 +70,7 @@ export type MainBottomTabParamList = {
};
Scan: undefined;
Request: undefined;
Settings: undefined;
History: undefined;
};
export interface TabScreen {

View File

@@ -6,8 +6,6 @@ import {
} from '@react-navigation/native-stack';
import { authRoutes, baseRoutes } from '../routes';
import { useAppLayout } from './AppLayoutController';
import { Icon } from 'react-native-elements';
import { Theme } from '../components/ui/styleUtils';
import { StatusBar } from 'react-native';
const { Navigator, Screen } = createNativeStackNavigator();
@@ -25,7 +23,15 @@ export const AppLayout: React.FC = () => {
return (
<NavigationContainer>
<StatusBar animated={true} barStyle="dark-content" />
<Navigator initialRouteName={baseRoutes[0].name} screenOptions={options}>
<Navigator
initialRouteName={
controller.isLanguagesetup
? baseRoutes[0].name
: controller.isUnAuthorized
? baseRoutes[2].name
: baseRoutes[1].name
}
screenOptions={options}>
{baseRoutes.map((route) => (
<Screen key={route.name} {...route} />
))}

View File

@@ -1,13 +1,19 @@
import { useSelector } from '@xstate/react';
import { useContext } from 'react';
import { selectAuthorized } from '../machines/auth';
import {
selectAuthorized,
selectLanguagesetup,
selectUnauthorized,
} from '../machines/auth';
import { GlobalContext } from '../shared/GlobalContext';
export function useAppLayout() {
const { appService } = useContext(GlobalContext);
const authService = appService.children.get('auth');
const isLanguagesetup = useSelector(authService, selectLanguagesetup);
return {
isAuthorized: useSelector(authService, selectAuthorized),
isUnAuthorized: useSelector(authService, selectUnauthorized),
isLanguagesetup,
};
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
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 { Button, Column, Text } from '../components/ui';
import { Theme } from '../components/ui/styleUtils';
import { RootRouteProps } from '../routes';
import { useAuthScreen } from './AuthScreenController';
@@ -15,21 +15,29 @@ export const AuthScreen: React.FC<RootRouteProps> = (props) => {
<Column
fill
padding={[32, 32, 32, 32]}
backgroundColor={Theme.Colors.whiteBackgroundColor}>
backgroundColor={Theme.Colors.whiteBackgroundColor}
align="space-between">
<MessageOverlay
isVisible={controller.alertMsg != ''}
onBackdropPress={controller.hideAlert}
title={controller.alertMsg}
/>
<Column>
<Text align="center">{t('header')}</Text>
<Icon name="fingerprint" size={80} color={Theme.Colors.Icon} />
<Column margin="20 0 0 0">
<Text weight="semibold" align="center">
{t('header')}
</Text>
<Text align="center" color={Theme.Colors.GrayText}>
{t('Description')}
</Text>
</Column>
</Column>
<Centered fill>
<Icon name="fingerprint" size={180} color={Theme.Colors.Icon} />
</Centered>
<Column>
<Button
title={t('useBiometrics')}
type="radius"
margin="0 0 8 0"
disabled={!controller.isBiometricsAvailable}
onPress={controller.useBiometrics}

View File

@@ -2,21 +2,21 @@ import React from 'react';
import { RefreshControl } from 'react-native';
import { Icon } from 'react-native-elements';
import { useTranslation } from 'react-i18next';
import { Centered, Column, Text } from '../../components/ui';
import { useHistoryTab } from './HistoryTabController';
import { HomeScreenTabProps } from './HomeScreen';
import { useHistoryTab } from './HistoryScreenController';
import { ActivityLogText } from '../../components/ActivityLogText';
import { MainRouteProps } from '../../routes/main';
import { Theme } from '../../components/ui/styleUtils';
export const HistoryTab: React.FC<HomeScreenTabProps> = (props) => {
const { t } = useTranslation('HistoryTab');
export const HistoryScreen: React.FC<MainRouteProps> = () => {
const { t } = useTranslation('HistoryScreen');
const controller = useHistoryTab();
return (
<Column fill style={{ display: props.isVisible ? 'flex' : 'none' }}>
<Column fill backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Column
scroll
padding="32 0"
padding="7 0"
refreshControl={
<RefreshControl
refreshing={controller.isRefreshing}

View File

@@ -3,7 +3,6 @@ import { Tab } from 'react-native-elements';
import { Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { HomeRouteProps } from '../../routes/main';
import { HistoryTab } from './HistoryTab';
import { MyVcsTab } from './MyVcsTab';
import { ReceivedVcsTab } from './ReceivedVcsTab';
import { ViewVcModal } from './ViewVcModal';
@@ -20,14 +19,6 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
return (
<React.Fragment>
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Tab
value={controller.activeTab}
onChange={controller.SELECT_TAB}
indicatorStyle={Theme.Styles.tabIndicator}>
{TabItem(t('myVcsTab', { vcLabel: controller.vcLabel.plural }))}
{TabItem(t('receivedVcsTab', { vcLabel: controller.vcLabel.plural }))}
{TabItem(t('historyTab'))}
</Tab>
{controller.haveTabsLoaded && (
<Column fill>
<MyVcsTab
@@ -40,11 +31,6 @@ export const HomeScreen: React.FC<HomeRouteProps> = (props) => {
service={controller.tabRefs.receivedVcs}
vcItemActor={controller.selectedVc}
/>
<HistoryTab
isVisible={controller.activeTab === 2}
vcItemActor={controller.selectedVc}
service={controller.tabRefs.history}
/>
</Column>
)}
</Column>

View File

@@ -9,10 +9,6 @@ import {
import { createModel } from 'xstate/lib/model';
import { vcItemMachine } from '../../machines/vcItem';
import { AppServices } from '../../shared/GlobalContext';
import {
createHistoryTabMachine,
HistoryTabMachine,
} from './HistoryTabMachine';
import { createMyVcsTabMachine, MyVcsTabMachine } from './MyVcsTabMachine';
import {
createReceivedVcsTabMachine,
@@ -25,7 +21,6 @@ const model = createModel(
tabRefs: {
myVcs: {} as ActorRefFrom<typeof MyVcsTabMachine>,
receivedVcs: {} as ActorRefFrom<typeof ReceivedVcsTabMachine>,
history: {} as ActorRefFrom<typeof HistoryTabMachine>,
},
selectedVc: null as ActorRefFrom<typeof vcItemMachine>,
activeTab: 0,
@@ -45,14 +40,12 @@ const model = createModel(
const MY_VCS_TAB_REF_ID = 'myVcsTab';
const RECEIVED_VCS_TAB_REF_ID = 'receivedVcsTab';
const HISTORY_TAB_REF_ID = 'historyTab';
export const HomeScreenEvents = model.events;
export type TabRef =
| ActorRefFrom<typeof MyVcsTabMachine>
| ActorRefFrom<typeof ReceivedVcsTabMachine>
| ActorRefFrom<typeof HistoryTabMachine>;
| ActorRefFrom<typeof ReceivedVcsTabMachine>;
export const HomeScreenMachine = model.createMachine(
{
@@ -143,10 +136,6 @@ export const HomeScreenMachine = model.createMachine(
createReceivedVcsTabMachine(context.serviceRefs),
RECEIVED_VCS_TAB_REF_ID
),
history: spawn(
createHistoryTabMachine(context.serviceRefs),
HISTORY_TAB_REF_ID
),
}),
}),

View File

@@ -0,0 +1,146 @@
import React, { useRef, useContext } from 'react';
import AppIntroSlider from 'react-native-app-intro-slider';
import { Dimensions, Image, StatusBar, View } from 'react-native';
import { Centered, Column, Row, Text, Button } from '../../components/ui';
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';
import { RootRouteProps } from '../../routes';
import { useWelcomeScreen } from '../WelcomeScreenController';
import LinearGradient from 'react-native-linear-gradient';
export const IntroSlidersScreen: React.FC<RootRouteProps> = (props) => {
const slider = useRef<AppIntroSlider>();
const { t } = useTranslation('OnboardingOverlay');
const { appService } = useContext(GlobalContext);
const settingsService = appService.children.get('settings');
const vcLabel = useSelector(settingsService, selectVcLabel);
const controller = useWelcomeScreen(props);
const slides = [
{
key: 'one',
title: t('stepOneTitle'),
text: t('stepOneText', { vcLabel: vcLabel.plural }),
image: Theme.sharingIntro,
},
{
key: 'two',
title: t('stepTwoTitle', { vcLabel: vcLabel.singular }),
text: t('stepTwoText', { vcLabel: vcLabel.plural }),
image: Theme.walletIntro,
},
{
key: 'three',
title: t('stepThreeTitle'),
text: t('stepThreeText', { vcLabel: vcLabel.plural }),
image: Theme.IntroScanner,
},
];
const renderItem = ({ item }) => {
return (
<LinearGradient colors={Theme.Colors.gradientBtn}>
<Centered>
<Row crossAlign="center">
<Column
style={{
flex: 3,
alignItems: 'flex-end',
marginRight: 75,
}}>
<Image
style={{ marginTop: 50, marginBottom: 30 }}
source={Theme.injiSmallLogo}
/>
</Column>
<Column
style={{
flex: 1,
alignItems: 'flex-end',
}}>
<Button
type="plain"
title={t('skip')}
onPress={controller.NEXT}
/>
</Column>
</Row>
<Image source={item.image} />
<Column
style={Theme.OnboardingOverlayStyles.bottomContainer}
crossAlign="center"
backgroundColor={Theme.Colors.whiteText}
width={Dimensions.get('screen').width}>
<Text weight="semibold" margin="0 0 18 0">
{item.title}
</Text>
<Text margin="0 0 150 0" color={Theme.Colors.GrayText}>
{item.text}
</Text>
{item.footer}
</Column>
</Centered>
</LinearGradient>
);
};
const renderNextButton = () => {
return (
<View>
<LinearGradient
colors={Theme.Colors.gradientBtn}
style={{ borderRadius: 10, height: 50, marginTop: -10 }}>
<Text
weight="semibold"
align="center"
color="#FFFFFF"
margin="10 0 0 0">
{t('next')}
</Text>
</LinearGradient>
</View>
);
};
const renderDoneButton = () => {
return (
<View>
<LinearGradient
colors={Theme.Colors.gradientBtn}
style={{ borderRadius: 10, height: 50, marginTop: -10 }}>
<Text
weight="semibold"
align="center"
color="#FFFFFF"
margin="10 0 0 0">
{t('stepThreeButton')}
</Text>
</LinearGradient>
</View>
);
};
return (
<View style={{ flex: 1 }}>
<StatusBar translucent={true} backgroundColor="transparent" />
<AppIntroSlider
data={slides}
renderDoneButton={renderDoneButton}
renderNextButton={renderNextButton}
bottomButton
ref={slider}
activeDotStyle={{
backgroundColor: Theme.Colors.Icon,
width: 30,
marginBottom: 90,
}}
dotStyle={{ backgroundColor: Theme.Colors.dotColor, marginBottom: 90 }}
renderItem={renderItem}
onDone={() => controller.NEXT()}
/>
</View>
);
};

View File

@@ -18,6 +18,7 @@ export const AddVcModal: React.FC<AddVcModalProps> = (props) => {
}
onDismiss={controller.DISMISS}
onPress={props.onPress}
headerTitle={t('inputIdHeader')}
/>
<OtpVerificationModal

View File

@@ -23,6 +23,7 @@ const model = createModel(
otpError: '',
transactionId: '',
requestId: '',
isPinned: false,
},
{
events: {
@@ -99,7 +100,7 @@ export const AddVcModalMachine =
},
],
SELECT_ID_TYPE: {
actions: ['setIdType', 'clearId'],
actions: ['clearIdError', 'setIdType', 'clearId'],
},
},
},
@@ -134,7 +135,7 @@ export const AddVcModalMachine =
},
],
SELECT_ID_TYPE: {
actions: ['setIdType', 'clearId'],
actions: ['clearIdError', 'setIdType', 'clearId'],
target: 'idle',
},
},

View File

@@ -38,7 +38,7 @@ export interface Typegen0 {
};
'eventsCausingActions': {
clearId: 'SELECT_ID_TYPE';
clearIdError: 'INPUT_ID' | 'VALIDATE_INPUT';
clearIdError: 'INPUT_ID' | 'SELECT_ID_TYPE' | 'VALIDATE_INPUT';
clearOtp:
| 'DISMISS'
| 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]'

View File

@@ -8,7 +8,7 @@ import { Theme } from '../../../components/ui/styleUtils';
export const BindingVcWarningOverlay: React.FC<QrLoginWarningProps> = (
props
) => {
const { t } = useTranslation('VcDetails');
const { t } = useTranslation('BindingVcWarningOverlay');
return (
<Overlay
@@ -18,8 +18,9 @@ export const BindingVcWarningOverlay: React.FC<QrLoginWarningProps> = (
align="space-between"
crossAlign="center"
padding={'10'}
width={Dimensions.get('screen').width * 0.8}>
<Row align="center" crossAlign="center" margin={'0 80 0 0'}>
width={Dimensions.get('screen').width * 0.8}
height={Dimensions.get('screen').height * 0}>
<Row align="center" crossAlign="center" margin={'0 80 -10 0'}>
<Image source={Theme.WarningLogo} resizeMethod="auto" />
<Text
margin={'0 0 0 -80'}
@@ -29,18 +30,22 @@ export const BindingVcWarningOverlay: React.FC<QrLoginWarningProps> = (
</Text>
</Row>
<Text size="regular" weight="bold">
{t('Alert')}
</Text>
<Column crossAlign="center" margin="0 0 30 0">
<Text weight="semibold">{t('alert')}</Text>
<Text align="center" size="smaller">
{t('BindingWarning')}
</Text>
<Text
align="center"
size="small"
weight="semibold"
color={Theme.Colors.GrayText}>
{t('BindingWarning')}
</Text>
</Column>
<Button
margin={'10 0 0 0'}
type="radius"
title={t('yes_confirm')}
margin={'30 0 0 0'}
type="gradient"
title={t('yesConfirm')}
onPress={props.onConfirm}
/>

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { Icon, ListItem } from 'react-native-elements';
import { useTranslation } from 'react-i18next';
import { Modal } from '../../../components/ui/Modal';
import { Centered, Column, Text } from '../../../components/ui';
import { ActivityLogText } from '../../../components/ActivityLogText';
import { ActorRefFrom } from 'xstate';
import { vcItemMachine } from '../../../machines/vcItem';
import { useKebabPopUp } from '../../../components/KebabPopUpController';
import { Theme } from '../../../components/ui/styleUtils';
export const HistoryTab: React.FC<HistoryTabProps> = (props) => {
const { t } = useTranslation('HistoryTab');
const controller = useKebabPopUp(props);
console.log(controller.isShowActivities, 'log from activities');
return (
<ListItem bottomDivider onPress={controller.SHOW_ACTIVITY}>
<ListItem.Content>
<ListItem.Title>
<Text
size="small"
weight="semibold"
color={Theme.Colors.walletbindingLabel}>
{props.label}
</Text>
</ListItem.Title>
</ListItem.Content>
<Modal
headerLabel={props.vcKey.split(':')[2]}
isVisible={controller.isShowActivities}
onDismiss={controller.DISMISS}>
<Column fill>
{controller.activities.map((activity) => {
if (activity._vcKey == props.vcKey) {
return (
<ActivityLogText
key={`${activity.timestamp}-${activity._vcKey}`}
activity={activity}
/>
);
}
})}
{controller.activities.length === 0 && (
<Centered fill>
<Icon
style={{ marginBottom: 20 }}
size={40}
name="sentiment-dissatisfied"
/>
<Text align="center" weight="semibold" margin="0 0 4 0">
{t('noHistory')}
</Text>
</Centered>
)}
</Column>
</Modal>
</ListItem>
);
};
export interface HistoryTabProps {
label: string;
vcKey: string;
service: ActorRefFrom<typeof vcItemMachine>;
}

View File

@@ -39,20 +39,26 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
<Modal
onDismiss={dismissInput}
isVisible={props.isVisible}
onShow={setIndividualID}>
onShow={setIndividualID}
headerTitle={t('title')}
headerElevation={2}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<Column fill align="space-between" pY={32} pX={24}>
<Text align="center">
{t('header', { vcLabel: controller.vcLabel.singular })}
</Text>
<Column>
<Row crossAlign="flex-end">
<Text
align="left"
size="regular"
style={Theme.TextStyles.retrieveIdLabel}>
{t('guideLabel', { vcLabel: controller.vcLabel.singular })}
</Text>
<Row crossAlign="flex-end" style={{ marginTop: 20 }}>
<Column
width="33%"
style={{
borderBottomWidth: 1,
marginBottom: 2,
borderColor:
Platform.OS === 'ios'
? 'transparent'
@@ -69,49 +75,51 @@ export const IdInputModal: React.FC<IdInputModalProps> = (props) => {
</Column>
<Column fill>
<Input
inputContainerStyle={
controller.id ? Theme.Styles.VidInputBottom : null
}
placeholder={!controller.id ? inputLabel : ''}
label={controller.id ? inputLabel : ''}
labelStyle={{
color: controller.isInvalid
? Theme.Colors.errorMessage
: Theme.Colors.textValue,
textAlign: 'left',
}}
inputStyle={{
textAlign: I18nManager.isRTL ? 'right' : 'left',
fontWeight: '700',
}}
selectionColor={Theme.Colors.Cursor}
value={controller.id}
keyboardType="number-pad"
rightIcon={
controller.isInvalid ? (
<Icon
name="error"
size={18}
color={Theme.Colors.errorMessage}
/>
) : null
}
errorStyle={{ color: Theme.Colors.errorMessage }}
errorStyle={Theme.TextStyles.error}
errorMessage={controller.idError}
onChangeText={controller.INPUT_ID}
ref={setIdInputRef}
/>
</Column>
</Row>
</Column>
<Column>
<Button
title={t('generateVc', { vcLabel: controller.vcLabel.singular })}
type="gradient"
title={t('downloadID')}
disabled={!controller.id}
margin="24 0 0 0"
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>
<Row style={Theme.Styles.getId}>
<Text
color={Theme.Colors.getVidColor}
weight="semibold"
size="small">
{t('noUIN/VID')}
</Text>
<TouchableOpacity activeOpacity={1} onPress={props.onPress}>
<Text
color={Theme.Colors.AddIdBtnBg}
weight="semibold"
size="small">
{t('getItHere')}
</Text>
</TouchableOpacity>
</Row>
)}
</Column>
</Column>

View File

@@ -1,44 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { PinInput } from '../../../components/PinInput';
import { Column, Text } from '../../../components/ui';
import { ModalProps, Modal } from '../../../components/ui/Modal';
import { Theme } from '../../../components/ui/styleUtils';
import { Image } from 'react-native';
import { Icon } from 'react-native-elements';
export const OtpVerification: React.FC<OtpVerificationModalProps> = (props) => {
const { t } = useTranslation('OtpVerificationModal');
return (
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
headerElevation={2}
headerTitle={t('header')}
headerRight={<Icon name={''} />}>
<Column
fill
padding="32"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column fill align="space-between" crossAlign="center">
<Text align="center">{t('enterOtp')}</Text>
<Image source={Theme.OtpLogo} resizeMethod="auto" />
<Text
align="center"
color={Theme.Colors.errorMessage}
margin="16 0 0 0">
{props.error}
</Text>
<PinInput length={6} onDone={props.onInputDone} />
</Column>
<Column fill></Column>
</Column>
</Modal>
);
};
interface OtpVerificationModalProps extends ModalProps {
onInputDone: (otp: string) => void;
error?: string;
}

View File

@@ -1,3 +1,6 @@
{
"enterOtp": "Enter the 6-digit verification code we sent you"
"title": "OTP Verification",
"otpSentMessage": "We've sent the 6 digit code to your registered mobile number!",
"resendTheCode": "You can resend the code in ",
"resendCode": "Resend Code"
}

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
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 { ModalProps, Modal } from '../../../components/ui/Modal';
import { Theme } from '../../../components/ui/styleUtils';
import { Image, TouchableOpacity } from 'react-native';
export const OtpVerificationModal: React.FC<OtpVerificationModalProps> = (
props
@@ -13,10 +13,29 @@ export const OtpVerificationModal: React.FC<OtpVerificationModalProps> = (
return (
<Modal isVisible={props.isVisible} onDismiss={props.onDismiss}>
<Column fill padding="32">
<Icon name="lock" color={Theme.Colors.Icon} size={60} />
<Column fill align="space-between">
<Text align="center">{t('enterOtp')}</Text>
<Column
fill
padding="32"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Column fill align="space-between" crossAlign="center">
<Column crossAlign="center">
<Image source={Theme.OtpLogo} resizeMethod="auto" />
<Text
margin="24 0 6 0"
weight="bold"
style={Theme.TextStyles.header}>
{t('title')}
</Text>
<Text
margin="0 24 15 24"
color={Theme.Colors.RetrieveIdLabel}
weight="semibold"
size="small"
align="center">
{t('otpSentMessage')}
</Text>
</Column>
<Text
align="center"
color={Theme.Colors.errorMessage}
@@ -24,6 +43,20 @@ export const OtpVerificationModal: React.FC<OtpVerificationModalProps> = (
{props.error}
</Text>
<PinInput length={6} onDone={props.onInputDone} />
<Text
margin="36 0 0 0"
color={Theme.Colors.RetrieveIdLabel}
weight="semibold"
size="small">
{t('resendTheCode')}
</Text>
<TouchableOpacity activeOpacity={1}>
<Text color={Theme.Colors.AddIdBtnBg} weight="bold" size="regular">
{t('resendCode')}
</Text>
</TouchableOpacity>
</Column>
<Column fill></Column>
</Column>

View File

@@ -0,0 +1,102 @@
import React from 'react';
import { Icon, ListItem } from 'react-native-elements';
import { Row, Text } from '../../../components/ui';
import { Theme } from '../../../components/ui/styleUtils';
import { useTranslation } from 'react-i18next';
import { BindingVcWarningOverlay } from './BindingVcWarningOverlay';
import { OtpVerificationModal } from './OtpVerificationModal';
import { MessageOverlay } from '../../../components/MessageOverlay';
import { useKebabPopUp } from '../../../components/KebabPopUpController';
import { Dimensions } from 'react-native';
import { ActorRefFrom } from 'xstate';
import { vcItemMachine } from '../../../machines/vcItem';
export const WalletBinding: React.FC<WalletBindingProps> = (props) => {
const controller = useKebabPopUp(props);
const WalletVerified: React.FC = () => {
return (
<Icon
name="verified-user"
color={Theme.Colors.VerifiedIcon}
size={28}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
const { t } = useTranslation('WalletBinding');
return controller.emptyWalletBindingId ? (
<ListItem bottomDivider onPress={controller.ADD_WALLET_BINDING_ID}>
{props.Icon && (
<Icon
name={props.Icon}
type="font-awesome"
size={20}
style={Theme.Styles.profileIconBg}
color={Theme.Colors.Icon}
/>
)}
<ListItem.Content>
<ListItem.Title>
<Text weight="bold" size="small">
{props.label}
</Text>
</ListItem.Title>
<Text
weight="semibold"
color={Theme.Colors.walletbindingContent}
size="smaller">
{props.content}
</Text>
</ListItem.Content>
<BindingVcWarningOverlay
isVisible={controller.isBindingWarning}
onConfirm={controller.CONFIRM}
onCancel={controller.CANCEL}
/>
<OtpVerificationModal
isVisible={controller.isAcceptingOtpInput}
onDismiss={controller.DISMISS}
onInputDone={controller.INPUT_OTP}
error={controller.otpError}
/>
<MessageOverlay
isVisible={controller.isWalletBindingError}
title={controller.walletBindingError}
onCancel={controller.CANCEL}
/>
<MessageOverlay
isVisible={controller.WalletBindingInProgress}
title={t('inProgress')}
progress
/>
</ListItem>
) : (
<ListItem bottomDivider>
<Row
width={Dimensions.get('screen').width * 0.8}
align="space-between"
crossAlign="center">
<Row crossAlign="center" style={{ flex: 1 }}>
<WalletVerified />
<Text
color={Theme.Colors.Details}
weight="bold"
size="small"
margin="10 10 10 10"
children={t('profileAuthenticated')}></Text>
</Row>
</Row>
</ListItem>
);
};
interface WalletBindingProps {
label: string;
content?: string;
Icon?: string;
service: ActorRefFrom<typeof vcItemMachine>;
}

View File

@@ -0,0 +1,86 @@
import { useSelector, useInterpret } from '@xstate/react';
import { useContext, useRef, useState } from 'react';
import { GlobalContext } from '../../../shared/GlobalContext';
import { selectMyVcs, VcEvents } from '../../../machines/vc';
import {
createVcItemMachine,
isShowingBindingWarning,
selectAcceptingBindingOtp,
isWalletBindingInProgress,
VcItemEvents,
selectIsAcceptingOtpInput,
selectOtpError,
selectShowWalletBindingError,
selectWalletBindingError,
} from '../../../machines/vcItem';
import { useTranslation } from 'react-i18next';
import { ActorRefFrom } from 'xstate';
export function useWalletBinding(props) {
const { t } = useTranslation('ProfileScreen');
const { appService } = useContext(GlobalContext);
const machine = useRef(
createVcItemMachine(
appService.getSnapshot().context.serviceRefs,
props.vcKey
)
);
const bindingService = useInterpret(machine.current, { devTools: __DEV__ });
const vcService = appService.children.get('vc');
const vcKeys = useSelector(vcService, selectMyVcs);
const otpError = useSelector(bindingService, selectOtpError);
const [isRevoking, setRevoking] = useState(false);
const [isAuthenticating, setAuthenticating] = useState(false);
const [isViewing, setIsViewing] = useState(false);
const [isBindingWarning, setisBindingWarning] = useState(false);
const [toastVisible, setToastVisible] = useState(false);
const [message, setMessage] = useState('');
const [selectedIndex, setSelectedIndex] = useState<number>(null);
const [selectedVidKeys, setSelectedVidKeys] = useState<string[]>([]);
const selectVcItem = (index: number, vcKey: string) => {
return () => {
setSelectedIndex(index);
};
};
const WalletBindingInProgress = useSelector(
bindingService,
isWalletBindingInProgress
);
return {
isBindingWarning,
setisBindingWarning,
otpError,
message,
toastVisible,
WalletBindingInProgress,
isAcceptingOtpInput: useSelector(bindingService, selectIsAcceptingOtpInput),
isAcceptingBindingOtp: useSelector(
bindingService,
selectAcceptingBindingOtp
),
isBindingError: useSelector(bindingService, selectShowWalletBindingError),
walletBindingError: useSelector(bindingService, selectWalletBindingError),
DISMISS: () => bindingService.send(VcItemEvents.DISMISS()),
CONFIRM: () => bindingService.send(VcItemEvents.CONFIRM()),
CANCEL: () => bindingService.send(VcItemEvents.CANCEL()),
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
setAuthenticating,
selectVcItem,
setIsViewing,
setRevoking,
};
}

View File

@@ -1,17 +1,15 @@
import React from 'react';
import { Button, Column, Text, Centered } from '../../components/ui';
import { Icon } from 'react-native-elements';
import { Button, Column, Row, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { RefreshControl } from 'react-native';
import { RefreshControl, Image, View } 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 { VcItem } from '../../components/VcItem';
import { GET_INDIVIDUAL_ID } from '../../shared/constants';
import { Icon } from 'react-native-elements';
export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
const { t } = useTranslation('MyVcsTab');
@@ -26,68 +24,114 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
GET_INDIVIDUAL_ID('');
};
{
controller.isRequestSuccessful
? setTimeout(() => {
controller.DISMISS();
}, 6000)
: null;
}
const DownloadingIdPopUp: React.FC = () => {
return (
<View
style={{ display: controller.isRequestSuccessful ? 'flex' : 'none' }}>
<Row style={Theme.Styles.popUp}>
<Text color={Theme.Colors.whiteText} weight="semibold" size="smaller">
{t('downloadingYourId')}
</Text>
<Icon
name="close"
onPress={() => {
controller.DISMISS();
clearIndividualId();
}}
color={Theme.Colors.whiteText}
size={19}
/>
</Row>
</View>
);
};
return (
<React.Fragment>
<Column fill style={{ display: props.isVisible ? 'flex' : 'none' }}>
<Column fill pY={32} pX={24}>
<DownloadingIdPopUp />
<Column fill pY={10} pX={18}>
{controller.vcKeys.length > 0 && (
<React.Fragment>
<Column
scroll
margin="0 0 20 0"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}
refreshControl={
<RefreshControl
refreshing={controller.isRefreshingVcs}
onRefresh={controller.REFRESH}
/>
}>
{controller.vcKeys.map((vcKey, index) => (
<VcItem
key={`${vcKey}-${index}`}
vcKey={vcKey}
margin="0 2 8 2"
onPress={controller.VIEW_VC}
/>
))}
</Column>
<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}
/>
{controller.vcKeys.map((vcKey, index) => {
if (vcKey.split(':')[4] === 'true') {
return (
<VcItem
key={`${vcKey}-${index}`}
vcKey={vcKey}
margin="0 2 8 2"
onPress={controller.VIEW_VC}
iconName="pushpin"
iconType="antdesign"
/>
);
}
})}
{controller.vcKeys.map((vcKey, index) => {
if (vcKey.split(':')[4] === 'false') {
return (
<VcItem
key={`${vcKey}-${index}`}
vcKey={vcKey}
margin="0 2 8 2"
onPress={controller.VIEW_VC}
/>
);
}
})}
</Column>
<Button
type="gradient"
isVcThere
disabled={controller.isRefreshingVcs}
title={t('downloadID')}
onPress={controller.DOWNLOAD_ID}
/>
</React.Fragment>
)}
{controller.vcKeys.length === 0 && (
<React.Fragment>
<Centered fill>
<Text weight="semibold" margin="0 0 8 0">
{t('generateVc', { vcLabel: controller.vcLabel.plural })}
<Column fill style={Theme.Styles.homeScreenContainer}>
<Image source={Theme.DigitalIdentityLogo} />
<Text weight="bold" margin="33 0 6 0" lineHeight={1}>
{t('bringYourDigitalID', {
vcLabel: controller.vcLabel.plural,
})}
</Text>
<Text color={Theme.Colors.textLabel} align="center">
<Text
style={Theme.TextStyles.bold}
color={Theme.Colors.textLabel}
align="center"
margin="0 12 30 12">
{t('generateVcDescription', {
vcLabel: controller.vcLabel.singular,
})}
</Text>
<Icon
name="arrow-downward"
containerStyle={{ marginTop: 20 }}
color={Theme.Colors.Icon}
<Button
type="gradient"
disabled={controller.isRefreshingVcs}
title={t('downloadID')}
onPress={controller.DOWNLOAD_ID}
/>
</Centered>
<Button
type="addId"
disabled={controller.isRefreshingVcs}
title={t('addVcButton', {
vcLabel: controller.vcLabel.singular,
})}
onPress={controller.ADD_VC}
/>
</Column>
</React.Fragment>
)}
</Column>
@@ -100,20 +144,6 @@ export const MyVcsTab: React.FC<HomeScreenTabProps> = (props) => {
{controller.GetVcModalService && (
<GetVcModal service={controller.GetVcModalService} />
)}
{controller.isRequestSuccessful && (
<DownloadingVcModal
isVisible={controller.isRequestSuccessful}
onDismiss={controller.DISMISS}
onShow={clearIndividualId}
/>
)}
<OnboardingOverlay
isVisible={controller.isOnboarding}
onDone={controller.ONBOARDING_DONE}
onAddVc={controller.ADD_VC}
/>
</React.Fragment>
);
};

View File

@@ -39,7 +39,7 @@ export function useMyVcsTab(props: HomeScreenTabProps) {
DISMISS: () => service.send(MyVcsTabEvents.DISMISS()),
ADD_VC: () => service.send(MyVcsTabEvents.ADD_VC()),
DOWNLOAD_ID: () => service.send(MyVcsTabEvents.ADD_VC()),
GET_VC: () => service.send(MyVcsTabEvents.GET_VC()),

View File

@@ -18,9 +18,9 @@ export interface Typegen0 {
'invokeSrcNameMap': {};
'missingImplementations': {
actions: never;
services: never;
guards: never;
delays: never;
guards: never;
services: 'AddVcModal' | 'GetVcModal';
};
'eventsCausingActions': {
completeOnboarding: 'ADD_VC' | 'ONBOARDING_DONE';
@@ -29,11 +29,14 @@ export interface Typegen0 {
storeVcItem: 'done.invoke.AddVcModal';
viewVcFromParent: 'VIEW_VC';
};
'eventsCausingServices': {};
'eventsCausingDelays': {};
'eventsCausingGuards': {
isOnboardingDone: 'STORE_RESPONSE';
};
'eventsCausingDelays': {};
'eventsCausingServices': {
AddVcModal: 'ADD_VC' | 'done.invoke.GetVcModal';
GetVcModal: 'GET_VC';
};
'matchesStates':
| 'addingVc'
| 'addingVc.addVcSuccessful'

View File

@@ -10,7 +10,7 @@ import { OIDcAuthenticationModal } from '../../components/OIDcAuth';
import { useViewVcModal, ViewVcModalProps } from './ViewVcModalController';
import { useTranslation } from 'react-i18next';
import { VcDetails } from '../../components/VcDetails';
import { OtpVerification } from './MyVcs/OtpVerification';
import { OtpVerificationModal } from './MyVcs/OtpVerificationModal';
import { BindingVcWarningOverlay } from './MyVcs/BindingVcWarningOverlay';
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
@@ -22,12 +22,12 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
idType: 'VID',
label: t('revoke'),
icon: 'close-circle-outline',
onPress: () => controller.CONFIRM_REVOKE_VC(),
onPress: controller.CONFIRM_REVOKE_VC,
},
{
label: t('editTag'),
icon: 'pencil',
onPress: () => controller.EDIT_TAG(),
onPress: controller.EDIT_TAG,
},
];
@@ -35,19 +35,8 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
<Modal
isVisible={props.isVisible}
onDismiss={props.onDismiss}
headerTitle={
controller.vc.verifiableCredential.credentialSubject.UIN
? controller.vc.verifiableCredential.credentialSubject.UIN
: controller.vc.verifiableCredential.credentialSubject.VID
}
headerElevation={2}
headerRight={
<DropdownIcon
icon="dots-vertical"
idType={controller.vc.idType}
items={DATA}
/>
}>
headerTitle={t('title')}
headerElevation={2}>
<Column scroll>
<Column fill>
<VcDetails
@@ -87,7 +76,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
)}
{controller.isAcceptingOtpInput && (
<OtpVerification
<OtpVerificationModal
isVisible={controller.isAcceptingOtpInput}
onDismiss={controller.DISMISS}
onInputDone={controller.inputOtp}
@@ -96,7 +85,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
)}
{controller.isAcceptingBindingOtp && (
<OtpVerification
<OtpVerificationModal
isVisible={controller.isAcceptingBindingOtp}
onDismiss={controller.DISMISS}
onInputDone={controller.inputOtp}

View File

@@ -17,12 +17,12 @@ import {
VcItemEvents,
vcItemMachine,
selectWalletBindingError,
selectIsRequestBindingOtp,
selectRequestBindingOtp,
selectAcceptingBindingOtp,
selectEmptyWalletBindingId,
isWalletBindingInProgress,
selectWalletBindingInProgress,
selectShowWalletBindingError,
isShowingBindingWarning,
selectBindingWarning,
} from '../../machines/vcItem';
import { selectPasscode } from '../../machines/auth';
import { biometricsMachine, selectIsSuccess } from '../../machines/biometrics';
@@ -130,7 +130,7 @@ export function useViewVcModal({
selectIsAcceptingRevokeInput
),
storedPasscode: useSelector(authService, selectPasscode),
isBindingOtp: useSelector(vcItemActor, selectIsRequestBindingOtp),
isBindingOtp: useSelector(vcItemActor, selectRequestBindingOtp),
isAcceptingBindingOtp: useSelector(vcItemActor, selectAcceptingBindingOtp),
walletBindingError: useSelector(vcItemActor, selectWalletBindingError),
isWalletBindingPending: useSelector(
@@ -139,10 +139,10 @@ export function useViewVcModal({
),
isWalletBindingInProgress: useSelector(
vcItemActor,
isWalletBindingInProgress
selectWalletBindingInProgress
),
isBindingError: useSelector(vcItemActor, selectShowWalletBindingError),
isBindingWarning: useSelector(vcItemActor, isShowingBindingWarning),
isBindingWarning: useSelector(vcItemActor, selectBindingWarning),
CONFIRM_REVOKE_VC: () => {
setRevoking(true);

View File

@@ -8,6 +8,9 @@ import { mainRoutes } from '../routes/main';
import { RootRouteProps } from '../routes';
import { Theme } from '../components/ui/styleUtils';
import { useTranslation } from 'react-i18next';
import { Row } from '../components/ui';
import { Image, Pressable } from 'react-native';
import { SettingScreen } from './Settings/SettingScreen';
const { Navigator, Screen } = createBottomTabNavigator();
@@ -15,17 +18,63 @@ export const MainLayout: React.FC<RootRouteProps> = () => {
const { t } = useTranslation('MainLayout');
const options: BottomTabNavigationOptions = {
headerLeft: () => <Icon name="notifications" color={Theme.Colors.Icon} />,
headerLeftContainerStyle: { paddingStart: 16 },
headerRightContainerStyle: { paddingEnd: 16 },
headerTitleAlign: 'center',
tabBarShowLabel: false,
headerRight: () => (
<Row align="space-between">
<Pressable
onPress={() => {
console.log('Help Page');
}}>
<Image
source={require('../assets/help-icon.png')}
style={{ width: 36, height: 36 }}
/>
</Pressable>
<SettingScreen
triggerComponent={
<Icon
name="settings"
type="simple-line-icon"
size={21}
style={Theme.Styles.IconContainer}
color={Theme.Colors.Icon}
/>
}
navigation={undefined}
route={undefined}
/>
</Row>
),
headerTitleStyle: {
fontFamily: 'Inter_600SemiBold',
fontSize: 30,
margin: 8,
},
headerRightContainerStyle: { paddingEnd: 13 },
headerLeftContainerStyle: { paddingEnd: 13 },
tabBarShowLabel: true,
tabBarLabelStyle: {
fontSize: 12,
color: Theme.Colors.IconBg,
fontFamily: 'Inter_600SemiBold',
fontSize: 30,
margin: 8,
},
headerRightContainerStyle: { paddingEnd: 13 },
headerLeftContainerStyle: { paddingEnd: 13 },
tabBarShowLabel: true,
tabBarActiveTintColor: Theme.Colors.IconBg,
tabBarStyle: {
height: 86,
paddingHorizontal: 36,
height: 82,
paddingHorizontal: 10,
},
tabBarLabelStyle: {
fontSize: 12,
fontFamily: 'Inter_600SemiBold',
},
tabBarItemStyle: {
height: 86,
height: 83,
padding: 11,
},
};
@@ -38,12 +87,12 @@ export const MainLayout: React.FC<RootRouteProps> = () => {
component={route.component}
options={{
...route.options,
title: t(route.name.toLowerCase()).toUpperCase(),
title: t(route.name),
tabBarIcon: ({ focused }) => (
<Icon
name={route.icon}
color={focused ? Theme.Colors.IconBg : Theme.Colors.Icon}
reverse={focused}
color={focused ? Theme.Colors.Icon : Theme.Colors.GrayIcon}
style={focused ? Theme.Styles.bottomTabIconStyle : null}
/>
),
}}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Icon } from 'react-native-elements';
import { Image } from 'react-native';
import { MAX_PIN, PasscodeVerify } from '../components/PasscodeVerify';
import { PinInput } from '../components/PinInput';
import { Column, Text } from '../components/ui';
@@ -15,12 +15,27 @@ export const PasscodeScreen: React.FC<PasscodeRouteProps> = (props) => {
const passcodeSetup =
controller.passcode === '' ? (
<React.Fragment>
<Text align="center">{t('header')}</Text>
<Column>
<Text align="center" style={Theme.TextStyles.header}>
{t('header')}
</Text>
<Text align="center" weight="semibold" color={Theme.Colors.GrayText}>
{t('description')}
</Text>
</Column>
<PinInput length={MAX_PIN} onDone={controller.setPasscode} />
</React.Fragment>
) : (
<React.Fragment>
<Text align="center">{t('confirmPasscode')}</Text>
<Column>
<Text align="center" style={Theme.TextStyles.header}>
{t('confirmPasscode')}
</Text>
<Text align="center" weight="semibold" color={Theme.Colors.GrayText}>
{t('description')}
</Text>
</Column>
<PasscodeVerify
onSuccess={controller.SETUP_PASSCODE}
onError={controller.setError}
@@ -34,9 +49,9 @@ export const PasscodeScreen: React.FC<PasscodeRouteProps> = (props) => {
fill
padding="32"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Icon name="lock" color={Theme.Colors.Icon} size={60} />
<Image source={Theme.LockIcon} style={{ alignSelf: 'center' }} />
{props.route.params?.setup ? (
<Column fill align="space-between" width="100%">
<Column fill align="space-around" width="100%">
{passcodeSetup}
</Column>
) : (

View File

@@ -77,8 +77,8 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
<Icon
name="fingerprint"
type="fontawesome"
size={20}
style={Theme.Styles.profileIconBg}
size={25}
style={Theme.Styles.IconContainer}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
@@ -105,7 +105,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
name="unlock"
size={20}
type="antdesign"
style={Theme.Styles.profileIconBg}
style={Theme.Styles.IconContainer}
color={Theme.Colors.Icon}
/>
<ListItem.Content>
@@ -125,7 +125,7 @@ export const ProfileScreen: React.FC<MainRouteProps> = (props) => {
name="logout"
type="fontawesome"
size={20}
style={Theme.Styles.profileIconBg}
style={Theme.Styles.IconContainer}
color={Theme.Colors.Icon}
/>
<ListItem.Content>

View File

@@ -5,11 +5,11 @@ import { Icon } from 'react-native-elements';
import { Theme } from '../../components/ui/styleUtils';
import { SendVcScreen } from './SendVcScreen';
import { MessageOverlay } from '../../components/MessageOverlay';
import { useScanLayout } from './ScanLayoutController';
import { ScanScreen } from './ScanScreen';
import { I18nManager, Platform } from 'react-native';
import { Message } from '../../components/Message';
import { ProgressingModal } from '../../components/ProgressingModal';
import { MessageOverlay } from '../../components/MessageOverlay';
const ScanStack = createNativeStackNavigator();
@@ -22,14 +22,21 @@ export const ScanLayout: React.FC = () => {
<ScanStack.Navigator
initialRouteName="ScanScreen"
screenOptions={{
headerTitleAlign: 'center',
headerLeft: () =>
I18nManager.isRTL && Platform.OS !== 'ios' ? (
<LanguageSelector
triggerComponent={
<Icon name="language" color={Theme.Colors.Icon} />
}
/>
) : null,
}}>
{!controller.isDone && (
<ScanStack.Screen
name="SendVcScreen"
component={SendVcScreen}
options={{
title: t('sharingVc', {
title: t('requester', {
vcLabel: controller.vcLabel.singular,
}),
headerBackVisible: false,
@@ -40,23 +47,29 @@ export const ScanLayout: React.FC = () => {
name="ScanScreen"
component={ScanScreen}
options={{
title: t('MainLayout:scan').toUpperCase(),
headerTitleStyle: { fontSize: 30, fontFamily: 'Inter_600SemiBold' },
title: t('MainLayout:scan'),
}}
/>
</ScanStack.Navigator>
<MessageOverlay
<ProgressingModal
isVisible={controller.statusOverlay != null}
title={controller.statusOverlay?.title}
message={controller.statusOverlay?.message}
hint={controller.statusOverlay?.hint}
timeoutHint={controller.statusOverlay?.hint}
isVisible={controller.statusOverlay != null}
title={controller.statusOverlay?.title}
timeoutHint={controller.statusOverlay?.hint}
label={controller.statusOverlay?.message}
onCancel={controller.statusOverlay?.onCancel}
progress={controller.statusOverlay?.progress}
onBackdropPress={controller.statusOverlay?.onBackdropPress}
requester={controller.statusOverlay?.requester}
/>
{controller.isDisconnected && (
<Message
<MessageOverlay
isVisible={controller.isDisconnected}
title={t('RequestScreen:status.disconnected.title')}
message={t('RequestScreen:status.disconnected.message')}
onBackdropPress={controller.DISMISS}

View File

@@ -93,16 +93,24 @@ export function useScanLayout() {
const onCancel = () => scanService.send(ScanEvents.CANCEL());
let statusOverlay: Pick<
MessageOverlayProps,
'title' | 'message' | 'hint' | 'onCancel' | 'progress' | 'onBackdropPress'
| 'title'
| 'message'
| 'hint'
| 'onCancel'
| 'progress'
| 'onBackdropPress'
| 'requester'
> = null;
if (isConnecting) {
statusOverlay = {
message: t('status.connecting'),
title: t('status.inProgress'),
message: t('status.establishingConnection'),
progress: true,
};
} else if (isConnectingTimeout) {
statusOverlay = {
message: t('status.connecting'),
title: t('status.sharingInProgress'),
requester: t('status.sharingInProgress'),
hint: t('status.connectingTimeout'),
onCancel,
progress: true,

View File

@@ -93,13 +93,11 @@ export const ScanScreen: React.FC = () => {
<Column
fill
padding="24 0"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Centered
fill
align="space-evenly"
backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Text align="center">{t('header')}</Text>
backgroundColor={Theme.Colors.whiteBackgroundColor}>
{controller.isLocationDisabled || controller.isLocationDenied ? (
<Column padding="24" fill align="space-between">
<Centered fill>

View File

@@ -1,9 +1,7 @@
import React, { useContext, useEffect, useRef } from 'react';
import { CheckBox, Input } from 'react-native-elements';
import { useTranslation } from 'react-i18next';
import { useFocusEffect } from '@react-navigation/native';
import { DeviceInfoList } from '../../components/DeviceInfoList';
import { Button, Column, Row } from '../../components/ui';
import { Button, Column, Row, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { MessageOverlay } from '../../components/MessageOverlay';
import { useSendVcScreen } from './SendVcScreenController';
@@ -55,23 +53,50 @@ export const SendVcScreen: React.FC = () => {
return (
<React.Fragment>
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<Column padding="16 0" scroll>
<DeviceInfoList of="receiver" deviceInfo={controller.receiverInfo} />
<Column padding="24">
<Column>
<Column
padding="24 19 14 19"
backgroundColor={Theme.Colors.whiteBackgroundColor}
style={{ position: 'relative' }}>
<Input
value={controller.reason ? controller.reason : ''}
placeholder={!controller.reason ? reasonLabel : ''}
label={controller.reason ? reasonLabel : ''}
labelStyle={{ textAlign: 'left' }}
onChangeText={controller.UPDATE_REASON}
containerStyle={{ marginBottom: 24 }}
containerStyle={{ marginBottom: 6 }}
inputStyle={{ textAlign: I18nManager.isRTL ? 'right' : 'left' }}
selectionColor={Theme.Colors.Cursor}
/>
<CheckBox
containerStyle={Theme.SelectVcOverlayStyles.consentCheckContainer}
title={t('consentToPhotoVerification')}
checked={controller.selectedVc.shouldVerifyPresence}
onPress={controller.TOGGLE_USER_CONSENT}
/>
</Column>
<Column>
{controller.vcKeys.map((vcKey, index) => (
<Text
margin="15 0 13 24"
weight="bold"
color={Theme.Colors.textValue}
style={{ position: 'relative' }}>
{t('pleaseSelectAnId')}
</Text>
</Column>
<Column scroll>
{controller.vcKeys.length === 1 && (
<SingleVcItem
key={controller.vcKeys[0]}
vcKey={controller.vcKeys[0]}
margin="0 2 8 2"
onPress={controller.SELECT_VC_ITEM(0)}
selectable
selected={0 === controller.selectedIndex}
/>
)}
{controller.vcKeys.length > 1 &&
controller.vcKeys.map((vcKey, index) => (
<VcItem
key={vcKey}
vcKey={vcKey}
@@ -79,27 +104,24 @@ export const SendVcScreen: React.FC = () => {
onPress={controller.SELECT_VC_ITEM(index)}
selectable
selected={index === controller.selectedIndex}
activeTab={'sharingVcScreen'}
/>
))}
</Column>
</Column>
<Column
backgroundColor={Theme.Colors.whiteBackgroundColor}
padding="16 24"
margin="2 0 0 0"
elevation={2}>
<Column padding="12 0" elevation={2}>
<Button
title={t('acceptRequest')}
margin="12 0 12 0"
type="gradient"
title={t('approveRequest', {
vcLabel: controller.vcLabel.singular,
})}
disabled={controller.selectedIndex == null}
onPress={controller.ACCEPT_REQUEST}
/>
{!controller.selectedVc.shouldVerifyPresence && (
<Button
type="outline"
title={t('acceptRequestAndVerify')}
margin="12 0 12 0"
type="gradient"
title={t('approveRequestAndVerify')}
styles={{ marginTop: '12' }}
disabled={controller.selectedIndex == null}
onPress={controller.VERIFY_AND_ACCEPT_REQUEST}
/>

View File

@@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Theme } from '../../components/ui/styleUtils';
import { Modal } from '../../components/ui/Modal';
import { Image } from 'react-native';
import { Centered, Column, Text } from '../../components/ui';
import { DeviceInfoList } from '../../components/DeviceInfoList';
import { Button } from '../../components/ui';
import { useScanLayout } from './ScanLayoutController';
import { useSendVcScreen } from './SendVcScreenController';
export const SharingSuccessModal: React.FC<SharingSuccessModalProps> = (
props
) => {
const { t } = useTranslation('ScanScreen');
const controller = useScanLayout();
const controller1 = useSendVcScreen();
const [showSuccessMessage, setIsShowSuccessMessage] = useState(false);
const toggle = () => setIsShowSuccessMessage(!showSuccessMessage);
return (
<React.Fragment>
<Modal isVisible={controller.isDone}>
<Column
margin="64 0"
crossAlign="center"
style={Theme.SelectVcOverlayStyles.sharedSuccessfully}>
<Image source={Theme.SuccessLogo} height={22} width={22} />
<Text style={Theme.TextStyles.bold}>
{t('ScanScreen:status.accepted.title')}
</Text>
<Text
align="center"
style={Theme.TextStyles.bold}
color={Theme.Colors.profileValue}>
{t('ScanScreen:status.accepted.message')}
</Text>
<Text style={Theme.TextStyles.bold}>
<DeviceInfoList deviceInfo={controller1.receiverInfo} />
</Text>
</Column>
<Column margin="0 0 0">
<Button
type="gradient"
title={t('ScanScreen:status.accepted.gotohome')}
onPress={controller.DISMISS}
/>
</Column>
</Modal>
</React.Fragment>
);
};
interface SharingSuccessModalProps {
isVisible: boolean;
}

View File

@@ -1,14 +1,13 @@
import React, { useState } from 'react';
import Markdown from 'react-native-simple-markdown';
import { useTranslation } from 'react-i18next';
import { I18nManager, Image, SafeAreaView, View } from 'react-native';
import { I18nManager, SafeAreaView, View } from 'react-native';
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
import { Button, Text, Row, Column } from '../../components/ui';
import { Button, Text, Row } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import appMetaData from '../../AppMetaData.md';
import { getVersion } from 'react-native-device-info';
import { isBLEEnabled } from '../../lib/smartshare';
export const AppMetaData: React.FC<AppMetaDataProps> = (props) => {
const { t } = useTranslation('AppMetaData');

View File

@@ -0,0 +1,4 @@
{
"header": "Credits and legal notices",
"back": "Back"
}

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