mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-08 21:18:14 -05:00
feat:languase selection and intro sliders
This commit is contained in:
committed by
Sri Kanth Kola
parent
dad70faf92
commit
52ff018f49
BIN
assets/Secure-Sharing.png
Normal file
BIN
assets/Secure-Sharing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
assets/Secure-Sharing2.png
Normal file
BIN
assets/Secure-Sharing2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 214 KiB |
BIN
assets/inji_small_logo.png
Normal file
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
BIN
assets/intro-scanner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
assets/intro-wallet-binding.png
Normal file
BIN
assets/intro-wallet-binding.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
65
components/KebabPopUp.tsx
Normal file
65
components/KebabPopUp.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { t } from 'i18next';
|
||||
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';
|
||||
|
||||
export const KebabPopUpMenu: React.FC<KebabPopUpMenuProps> = (props) => {
|
||||
return (
|
||||
<BottomSheet
|
||||
isVisible={props.isVisible}
|
||||
containerStyle={{
|
||||
backgroundColor: 'transparent',
|
||||
elevation: 4,
|
||||
marginHorizontal: 4,
|
||||
borderTopLeftRadius: 30,
|
||||
borderTopRightRadius: 30,
|
||||
}}>
|
||||
<Row
|
||||
style={{
|
||||
backgroundColor: 'white',
|
||||
borderTopLeftRadius: 30,
|
||||
borderTopRightRadius: 24,
|
||||
padding: 18,
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<Centered></Centered>
|
||||
<Text
|
||||
weight="bold"
|
||||
style={{ ...Theme.TextStyles.base, flex: 1, alignSelf: 'center' }}>
|
||||
{t('More Options')}
|
||||
</Text>
|
||||
<Icon
|
||||
name="close"
|
||||
onPress={props.close}
|
||||
color={Theme.Colors.Details}
|
||||
size={25}
|
||||
/>
|
||||
</Row>
|
||||
<Column>
|
||||
<ListItem bottomDivider>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title>
|
||||
<Text size="small" style={Theme.TextStyles.bold}>
|
||||
{t('Unpin Card')}
|
||||
</Text>
|
||||
</ListItem.Title>
|
||||
</ListItem.Content>
|
||||
</ListItem>
|
||||
|
||||
<WalletBinding
|
||||
label={t('Offline authentication disabled!')}
|
||||
Content={t(
|
||||
'Click here to enable the credentials to be used for offline authentication.'
|
||||
)}
|
||||
/>
|
||||
</Column>
|
||||
</BottomSheet>
|
||||
);
|
||||
};
|
||||
|
||||
interface KebabPopUpMenuProps {
|
||||
isVisible: boolean;
|
||||
close: () => void;
|
||||
}
|
||||
@@ -44,6 +44,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}
|
||||
|
||||
@@ -18,7 +18,8 @@ const Colors = {
|
||||
Warning: '#f0ad4e',
|
||||
LightOrange: '#fce7e3',
|
||||
GrayText: '#6F6F6F',
|
||||
GrayText: '#6F6F6F',
|
||||
dorColor: '#CBCBCB',
|
||||
plainText: '#FFD6A7',
|
||||
};
|
||||
|
||||
export type ElevationLevel = 0 | 1 | 2 | 3 | 4 | 5;
|
||||
@@ -69,6 +70,8 @@ export const DefaultTheme = {
|
||||
ProfileIconBg: Colors.LightOrange,
|
||||
GrayText: Colors.GrayText,
|
||||
gradientBtn: ['#F59B4B', '#E86E04'],
|
||||
dotColor: Colors.dorColor,
|
||||
plainText: Colors.plainText,
|
||||
},
|
||||
Styles: StyleSheet.create({
|
||||
title: {
|
||||
@@ -695,6 +698,14 @@ export const DefaultTheme = {
|
||||
top: 40,
|
||||
zIndex: 1,
|
||||
},
|
||||
bottomContainer: {
|
||||
padding: 20,
|
||||
borderTopLeftRadius: 30,
|
||||
borderTopRightRadius: 30,
|
||||
marginTop: -185,
|
||||
paddingBottom: 100,
|
||||
height: 220,
|
||||
},
|
||||
}),
|
||||
claimsContainer: StyleSheet.create({
|
||||
container: {
|
||||
@@ -710,7 +721,10 @@ export const DefaultTheme = {
|
||||
WarningLogo: require('../../../assets/warningLogo.png'),
|
||||
OtpLogo: require('../../../assets/otp-mobile-logo.png'),
|
||||
SuccessLogo: require('../../../assets/success-logo.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/
|
||||
|
||||
|
||||
@@ -156,13 +156,15 @@
|
||||
"generateVcDescription": "Tap on \"Add {{vcLabel}}\" below to download your {{vcLabel}}"
|
||||
},
|
||||
"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 and can be renamed or shared at any time.",
|
||||
"stepThreeButton": "Get Started",
|
||||
"skip": "Skip",
|
||||
"next": "Next"
|
||||
},
|
||||
"ReceivedVcsTab": {
|
||||
"noReceivedVcsTitle": "No {{vcLabel}} available yet",
|
||||
|
||||
94
machines/WalletBinding.typegen.ts
Normal file
94
machines/WalletBinding.typegen.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true;
|
||||
'internalEvents': {
|
||||
'done.invoke.walletBinding.addKeyPair:invocation[0]': {
|
||||
type: 'done.invoke.walletBinding.addKeyPair:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.walletBinding.addingWalletBindingId:invocation[0]': {
|
||||
type: 'done.invoke.walletBinding.addingWalletBindingId:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.walletBinding.requestingBindingOtp:invocation[0]': {
|
||||
type: 'done.invoke.walletBinding.requestingBindingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'done.invoke.walletBinding.updatingPrivateKey:invocation[0]': {
|
||||
type: 'done.invoke.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
data: unknown;
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.';
|
||||
};
|
||||
'error.platform.walletBinding.addKeyPair:invocation[0]': {
|
||||
type: 'error.platform.walletBinding.addKeyPair:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.walletBinding.addingWalletBindingId:invocation[0]': {
|
||||
type: 'error.platform.walletBinding.addingWalletBindingId:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.walletBinding.requestingBindingOtp:invocation[0]': {
|
||||
type: 'error.platform.walletBinding.requestingBindingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'error.platform.walletBinding.updatingPrivateKey:invocation[0]': {
|
||||
type: 'error.platform.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
data: unknown;
|
||||
};
|
||||
'xstate.init': { type: 'xstate.init' };
|
||||
};
|
||||
'invokeSrcNameMap': {
|
||||
addWalletBindnigId: 'done.invoke.walletBinding.addingWalletBindingId:invocation[0]';
|
||||
generateKeyPair: 'done.invoke.walletBinding.addKeyPair:invocation[0]';
|
||||
requestBindingOtp: 'done.invoke.walletBinding.requestingBindingOtp:invocation[0]';
|
||||
updatePrivateKey: 'done.invoke.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
};
|
||||
'missingImplementations': {
|
||||
actions: 'clearTransactionId';
|
||||
delays: never;
|
||||
guards: never;
|
||||
services: never;
|
||||
};
|
||||
'eventsCausingActions': {
|
||||
clearOtp:
|
||||
| 'DISMISS'
|
||||
| 'done.invoke.walletBinding.requestingBindingOtp:invocation[0]';
|
||||
clearTransactionId: 'DISMISS';
|
||||
setOtp: 'INPUT_OTP';
|
||||
setPrivateKey: 'done.invoke.walletBinding.addKeyPair:invocation[0]';
|
||||
setPublicKey: 'done.invoke.walletBinding.addKeyPair:invocation[0]';
|
||||
setWalletBindingError:
|
||||
| 'error.platform.walletBinding.addKeyPair:invocation[0]'
|
||||
| 'error.platform.walletBinding.addingWalletBindingId:invocation[0]'
|
||||
| 'error.platform.walletBinding.requestingBindingOtp:invocation[0]'
|
||||
| 'error.platform.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
setWalletBindingErrorEmpty:
|
||||
| 'CANCEL'
|
||||
| 'done.invoke.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
setWalletBindingId: 'done.invoke.walletBinding.addingWalletBindingId:invocation[0]';
|
||||
storeContext: 'done.invoke.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
updatePrivateKey: 'done.invoke.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
updateVc: 'done.invoke.walletBinding.updatingPrivateKey:invocation[0]';
|
||||
};
|
||||
'eventsCausingDelays': {};
|
||||
'eventsCausingGuards': {};
|
||||
'eventsCausingServices': {
|
||||
addWalletBindnigId: 'done.invoke.walletBinding.addKeyPair:invocation[0]';
|
||||
generateKeyPair: 'INPUT_OTP';
|
||||
requestBindingOtp: 'CONFIRM';
|
||||
updatePrivateKey: 'done.invoke.walletBinding.addingWalletBindingId:invocation[0]';
|
||||
};
|
||||
'matchesStates':
|
||||
| 'acceptingBindingOtp'
|
||||
| 'addKeyPair'
|
||||
| 'addingWalletBindingId'
|
||||
| 'requestingBindingOtp'
|
||||
| 'showBindingWarning'
|
||||
| 'showingWalletBindingError'
|
||||
| 'updatingPrivateKey';
|
||||
'tags': never;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ const model = createModel(
|
||||
LOGIN: () => ({}),
|
||||
STORE_RESPONSE: (response?: unknown) => ({ response }),
|
||||
SELECT: () => ({}),
|
||||
NEXT: () => ({}),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -71,6 +72,13 @@ export const authMachine = model.createMachine(
|
||||
languagesetup: {
|
||||
on: {
|
||||
SELECT: {
|
||||
target: 'introSlider',
|
||||
},
|
||||
},
|
||||
},
|
||||
introSlider: {
|
||||
on: {
|
||||
NEXT: {
|
||||
target: 'settingUp',
|
||||
},
|
||||
},
|
||||
@@ -200,3 +208,6 @@ export function selectSettingUp(state: State) {
|
||||
export function selectLanguagesetup(state: State) {
|
||||
return state.matches('languagesetup');
|
||||
}
|
||||
export function selectIntroSlider(state: State) {
|
||||
return state.matches('introSlider');
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface Typegen0 {
|
||||
| 'authorized'
|
||||
| 'checkingAuth'
|
||||
| 'init'
|
||||
| 'introSlider'
|
||||
| 'languagesetup'
|
||||
| 'savingDefaults'
|
||||
| 'settingUp'
|
||||
|
||||
@@ -75,6 +75,10 @@ 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.requestingBindingOtp:invocation[0]': {
|
||||
type: 'error.platform.vc-item.requestingBindingOtp:invocation[0]';
|
||||
data: unknown;
|
||||
@@ -149,6 +153,7 @@ export interface Typegen0 {
|
||||
| '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';
|
||||
@@ -196,7 +201,9 @@ export interface Typegen0 {
|
||||
'eventsCausingServices': {
|
||||
addWalletBindnigId: 'done.invoke.vc-item.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';
|
||||
|
||||
345
machines/walletBinding.ts
Normal file
345
machines/walletBinding.ts
Normal file
@@ -0,0 +1,345 @@
|
||||
import { TextInput } from 'react-native';
|
||||
import { assign, ErrorPlatformEvent, StateFrom, send, EventFrom } from 'xstate';
|
||||
import { log } from 'xstate/lib/actions';
|
||||
|
||||
import i18n from '../i18n';
|
||||
import { AppServices } from '../shared/GlobalContext';
|
||||
import { ActivityLogEvents } from './activityLog';
|
||||
import { StoreEvents } from './store';
|
||||
import { createModel } from 'xstate/lib/model';
|
||||
import { request } from '../shared/request';
|
||||
import { VcIdType } from '../types/vc';
|
||||
import { MY_VCS_STORE_KEY, VC_ITEM_STORE_KEY } from '../shared/constants';
|
||||
import {
|
||||
generateKeys,
|
||||
WalletBindingResponse,
|
||||
} from '../shared/cryptoutil/cryptoUtil';
|
||||
import { KeyPair } from 'react-native-rsa-native';
|
||||
import {
|
||||
getBindingCertificateConstant,
|
||||
savePrivateKey,
|
||||
} from '../shared/keystore/SecureKeystore';
|
||||
|
||||
const model = createModel(
|
||||
{
|
||||
serviceRefs: {} as AppServices,
|
||||
idType: 'VID' as VcIdType,
|
||||
id: '',
|
||||
idError: '',
|
||||
otp: '',
|
||||
otpError: '',
|
||||
transactionId: '',
|
||||
requestId: '',
|
||||
VIDs: [] as string[],
|
||||
bindingTransactionId: '',
|
||||
walletBindingResponse: null as WalletBindingResponse,
|
||||
walletBindingError: '',
|
||||
publicKey: '',
|
||||
privateKey: '',
|
||||
},
|
||||
{
|
||||
events: {
|
||||
INPUT_OTP: (otp: string) => ({ otp }),
|
||||
VALIDATE_INPUT: () => ({}),
|
||||
READY: (idInputRef: TextInput) => ({ idInputRef }),
|
||||
DISMISS: () => ({}),
|
||||
SELECT_ID_TYPE: (idType: VcIdType) => ({ idType }),
|
||||
REVOKE_VCS: (vcKeys: string[]) => ({ vcKeys }),
|
||||
STORE_RESPONSE: (response: string[]) => ({ response }),
|
||||
ERROR: (data: Error) => ({ data }),
|
||||
SUCCESS: () => ({}),
|
||||
CONFIRM: () => ({}),
|
||||
CANCEL: () => ({}),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const walletBindingMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QCUwDcD2BrMA1AlhLAHSEA2YAxMgKK4DyA0jQPq4DCAyoqAA4ax8AF3wYAdjxAAPRACYAHAFZiARhUB2WQDZFABgCcO-boDMWgDQgAnohWyALLOK6X9k-J3zZirfYC+fpaomDgERKRiaACGZISUAJIAcgAKAKoAKiz06cmS-IIi4pIyCOq6TuomjvYqWlpe3iaWNgiKik4u5fK69vr2uipmAUHo2HiEJPiRMXEAIvGcALIL3Egg+cKiEmslZRVVDrX1so3Nclr6zi61pvLyKoY9wyDBY2EkUQDGn2C8ImJQXDxWaTCAUah0JisDirPgCTZFHaIEyydTEe6yEyaQzqXrtM4IeQmXRXcrqDz9dQPEzPV6hCbEL4-P5TQHAkgAJzAAEcAK5wf5QehCXiUCDiMAREKSunjcJM36CoEg4hcvkC1nC3gIKaYT5RQpiADaugAunl4Ybioh8dZbHtiHoevpFESlIp9DTAi9RvT5d9FazlZyefzYIKtZQwByORgOcReGQDQAzOMAW2IsvejIDLIBwdVoY1AK1OsiGH1hpN5rWGytSIQJix6LsWNkOLxsgJKLRnVMlXJdxq-m9WYZCrzbJBlHmSxWFoKW2tjdRLcx2K0uJdXbtCGMpN03Qe-X03VktN9co+uYjIviYl4vKECRSGSyOQXCO2oBKKkPaNdRQqXsRRBi0EwXQJep5EdFxURdTczB0C9pWzCdb14e9H2fWdlk4WF1ktJcGwUZQ1E0HQDCMUwLF3NQ+lJVFN3kXFvBQt4GTVMNBVlMUJSlMZM0vbMuOLKBZTLPUDS2atP3rH9bH-R0lGA0CzAgxQCRqS4nQ0F0-10SoRxGVDOKLcNWV46NY3jRMU3TITTPCUSLIBCTdQraTxFk2siMRBTSh3FpBhdWCeiAqkiSA4yfSckMQiDT4ZwWPCCLrYiAq8AlBgeYg6jKLF1DaB5ygCb0xAwCA4EkMdwnIMA5Iy6RbEMPKNGJSo2nuD17Gyh5lDqC59HUTwVBqL0TI4urpliCBiAwEVGv85rWipZx5GGxRiV8GjNN3BQALg8bMS0citHYv1JhmwhiAAIy+HAxAgJbvxWto0T6doTlkFwtBOFRuzaMK1G6TdqWGi6rylGZnt8xdlpKLaTGIT7vp+3Q-tkAH9pUZRdNOux9KxGLauvZklXZUgwQauGv2XH7LhMOwai+zcfvkXrd3sHQwsPal7C8WpFEhtCbyDSmXIwl7l00btud5twLj-Op5BF8cxfzdlpYbAXspYhW9iG3w1f9cnNTvB8n21gKBbROpHEAhn20UTngp+lRiCx7nN1uAxMRNkN1Vc8TL2tlaiTRQZtBMCKBe+gkXZJJ1tB6ECBf0FQA8LBL80+MPf3bGC7lRDHT1TptuzuMLDhRMwzoD-O5Fd2wYOuVF-raXx7HUMq-CAA */
|
||||
model.createMachine(
|
||||
{
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
tsTypes: {} as import('./WalletBinding.typegen').Typegen0,
|
||||
schema: {
|
||||
context: model.initialContext,
|
||||
events: {} as EventFrom<typeof model>,
|
||||
},
|
||||
id: 'walletBinding',
|
||||
initial: 'showBindingWarning',
|
||||
states: {
|
||||
showBindingWarning: {
|
||||
on: {
|
||||
CONFIRM: {
|
||||
target: 'requestingBindingOtp',
|
||||
},
|
||||
CANCEL: {
|
||||
target: 'showBindingWarning',
|
||||
},
|
||||
},
|
||||
},
|
||||
requestingBindingOtp: {
|
||||
invoke: {
|
||||
src: 'requestBindingOtp',
|
||||
onDone: [
|
||||
{
|
||||
target: 'acceptingBindingOtp',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: 'setWalletBindingError',
|
||||
target: 'showingWalletBindingError',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
showingWalletBindingError: {
|
||||
on: {
|
||||
CANCEL: {
|
||||
target: 'showBindingWarning',
|
||||
actions: 'setWalletBindingErrorEmpty',
|
||||
},
|
||||
},
|
||||
},
|
||||
acceptingBindingOtp: {
|
||||
entry: ['clearOtp'],
|
||||
on: {
|
||||
INPUT_OTP: {
|
||||
target: 'addKeyPair',
|
||||
actions: ['setOtp'],
|
||||
},
|
||||
DISMISS: {
|
||||
target: '',
|
||||
actions: ['clearOtp', 'clearTransactionId'],
|
||||
},
|
||||
},
|
||||
},
|
||||
addKeyPair: {
|
||||
invoke: {
|
||||
src: 'generateKeyPair',
|
||||
onDone: {
|
||||
target: 'addingWalletBindingId',
|
||||
actions: ['setPublicKey', 'setPrivateKey'],
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
actions: 'setWalletBindingError',
|
||||
target: 'showingWalletBindingError',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
addingWalletBindingId: {
|
||||
invoke: {
|
||||
src: 'addWalletBindnigId',
|
||||
onDone: [
|
||||
{
|
||||
target: 'updatingPrivateKey',
|
||||
actions: ['setWalletBindingId'],
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: 'setWalletBindingError',
|
||||
target: 'showingWalletBindingError',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
updatingPrivateKey: {
|
||||
invoke: {
|
||||
src: 'updatePrivateKey',
|
||||
onDone: {
|
||||
target: '',
|
||||
actions: [
|
||||
'storeContext',
|
||||
'updatePrivateKey',
|
||||
'updateVc',
|
||||
'setWalletBindingErrorEmpty',
|
||||
],
|
||||
},
|
||||
onError: {
|
||||
actions: 'setWalletBindingError',
|
||||
target: 'showingWalletBindingError',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
setWalletBindingError: assign({
|
||||
walletBindingError: (context, event) => (event.data as Error).message,
|
||||
}),
|
||||
|
||||
setWalletBindingErrorEmpty: assign({
|
||||
walletBindingError: () => '',
|
||||
}),
|
||||
|
||||
setPublicKey: assign({
|
||||
publicKey: (context, event) => (event.data as KeyPair).public,
|
||||
}),
|
||||
|
||||
setPrivateKey: assign({
|
||||
privateKey: (context, event) => (event.data as KeyPair).private,
|
||||
}),
|
||||
|
||||
updatePrivateKey: assign({
|
||||
privateKey: () => '',
|
||||
}),
|
||||
|
||||
setWalletBindingId: assign({
|
||||
walletBindingResponse: (context, event) =>
|
||||
event.data as WalletBindingResponse,
|
||||
}),
|
||||
|
||||
setOtp: model.assign({
|
||||
otp: (_context, event) => event.otp,
|
||||
}),
|
||||
|
||||
clearOtp: assign({ otp: '' }),
|
||||
|
||||
storeContext: send(
|
||||
(context) => {
|
||||
const { serviceRefs, ...data } = context;
|
||||
return StoreEvents.SET(VC_ITEM_STORE_KEY(context), data);
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.store,
|
||||
}
|
||||
),
|
||||
|
||||
updateVc: send(
|
||||
(context) => {
|
||||
const { serviceRefs, ...vc } = context;
|
||||
return { type: 'VC_DOWNLOADED', vc };
|
||||
},
|
||||
{
|
||||
to: (context) => context.serviceRefs.vc,
|
||||
}
|
||||
),
|
||||
},
|
||||
|
||||
services: {
|
||||
addWalletBindnigId: async (context) => {
|
||||
const response = await request(
|
||||
'POST',
|
||||
'/residentmobileapp/wallet-binding',
|
||||
{
|
||||
requestTime: String(new Date().toISOString()),
|
||||
request: {
|
||||
authFactorType: 'WLA',
|
||||
format: 'jwt',
|
||||
individualId: context.id,
|
||||
transactionId: context.bindingTransactionId,
|
||||
publicKey: context.publicKey,
|
||||
challengeList: [
|
||||
{
|
||||
authFactorType: 'OTP',
|
||||
challenge: context.otp,
|
||||
format: 'alpha-numeric',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
const certificate = response.response.certificate;
|
||||
await savePrivateKey(
|
||||
getBindingCertificateConstant(context.id),
|
||||
certificate
|
||||
);
|
||||
|
||||
const walletResponse: WalletBindingResponse = {
|
||||
walletBindingId: response.response.encryptedWalletBindingId,
|
||||
keyId: response.response.keyId,
|
||||
thumbprint: response.response.thumbprint,
|
||||
expireDateTime: response.response.expireDateTime,
|
||||
};
|
||||
return walletResponse;
|
||||
},
|
||||
|
||||
updatePrivateKey: async (context) => {
|
||||
const hasSetPrivateKey: boolean = await savePrivateKey(
|
||||
context.walletBindingResponse.walletBindingId,
|
||||
context.privateKey
|
||||
);
|
||||
if (!hasSetPrivateKey) {
|
||||
throw new Error('Could not store private key in keystore.');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
generateKeyPair: async (context) => {
|
||||
let keyPair: KeyPair = await generateKeys();
|
||||
return keyPair;
|
||||
},
|
||||
|
||||
requestBindingOtp: async (context) => {
|
||||
console.log('requesting otp');
|
||||
const response = await request(
|
||||
'POST',
|
||||
'/residentmobileapp/binding-otp',
|
||||
{
|
||||
requestTime: String(new Date().toISOString()),
|
||||
request: {
|
||||
individualId: context.id,
|
||||
otpChannels: ['EMAIL', 'PHONE'],
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.response == null) {
|
||||
throw new Error('Could not process request');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
guards: {},
|
||||
}
|
||||
);
|
||||
|
||||
export function createWalletBindingMachine(serviceRefs: AppServices) {
|
||||
return walletBindingMachine.withContext({
|
||||
...walletBindingMachine.context,
|
||||
serviceRefs,
|
||||
});
|
||||
}
|
||||
|
||||
type State = StateFrom<typeof walletBindingMachine>;
|
||||
|
||||
export const WalletBindingEvents = model.events;
|
||||
|
||||
export function selectIdType(state: State) {
|
||||
return state.context.idType;
|
||||
}
|
||||
|
||||
export function selectIdError(state: State) {
|
||||
return state.context.idError;
|
||||
}
|
||||
|
||||
export function selectOtpError(state: State) {
|
||||
return state.context.otpError;
|
||||
}
|
||||
export function selectWalletBindingError(state: State) {
|
||||
return state.context.walletBindingError;
|
||||
}
|
||||
export function selectIsBindingWarning(state: State) {
|
||||
return state.matches('showBindingWarning');
|
||||
}
|
||||
|
||||
export function selectIsAcceptingOtpInput(state: State) {
|
||||
return state.matches('acceptingBindingOtp');
|
||||
}
|
||||
|
||||
export function walletBindingInProgress(state: State) {
|
||||
return state.matches('requestingBindingOtp') ||
|
||||
state.matches('addingWalletBindingId') ||
|
||||
state.matches('addKeyPair') ||
|
||||
state.matches('updatingPrivateKey')
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
|
||||
export function selectShowWalletBindingError(state: State) {
|
||||
return state.matches('showingWalletBindingError');
|
||||
}
|
||||
@@ -10,12 +10,20 @@ import { PasscodeScreen } from '../screens/PasscodeScreen';
|
||||
import { MainLayout } from '../screens/MainLayout';
|
||||
import { NotificationsScreen } from '../screens/NotificationsScreen';
|
||||
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,
|
||||
@@ -50,6 +58,7 @@ export const authRoutes: Screen[] = [
|
||||
|
||||
export type RootStackParamList = {
|
||||
Language: undefined;
|
||||
IntroSliders: undefined;
|
||||
Welcome: undefined;
|
||||
Auth: undefined;
|
||||
Passcode: {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const AppLayout: React.FC = () => {
|
||||
<StatusBar animated={true} barStyle="dark-content" />
|
||||
<Navigator
|
||||
initialRouteName={
|
||||
controller.isLanguagesetup ? baseRoutes[0].name : baseRoutes[1].name
|
||||
controller.isLanguagesetup ? baseRoutes[0].name : baseRoutes[2].name
|
||||
}
|
||||
screenOptions={options}>
|
||||
{baseRoutes.map((route) => (
|
||||
|
||||
147
screens/Home/IntroSlidersScreen.tsx
Normal file
147
screens/Home/IntroSlidersScreen.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import React, { useRef, useContext } from 'react';
|
||||
import AppIntroSlider from 'react-native-app-intro-slider';
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
StatusBar,
|
||||
TouchableOpacity,
|
||||
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 align="space-between" crossAlign="center">
|
||||
<Column
|
||||
style={{
|
||||
flex: 3,
|
||||
alignItems: 'flex-end',
|
||||
marginRight: 75,
|
||||
}}>
|
||||
<Image
|
||||
style={{ marginVertical: 61 }}
|
||||
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"
|
||||
align="space-between"
|
||||
backgroundColor={Theme.Colors.whiteText}
|
||||
width={Dimensions.get('screen').width}>
|
||||
<Text weight="semibold">{item.title}</Text>
|
||||
<Text 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 }}>
|
||||
<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 }}>
|
||||
<TouchableOpacity onPress={controller.NEXT}>
|
||||
<Text
|
||||
weight="semibold"
|
||||
align="center"
|
||||
color="#FFFFFF"
|
||||
margin="10 0 0 0">
|
||||
{t('stepThreeButton')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</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 }}
|
||||
dotStyle={{ backgroundColor: Theme.Colors.dotColor }}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
86
screens/Home/MyVcs/WalletBinding.tsx
Normal file
86
screens/Home/MyVcs/WalletBinding.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
I18nManager,
|
||||
RefreshControl,
|
||||
SafeAreaView,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Divider, Icon, ListItem, Overlay } from 'react-native-elements';
|
||||
import { Button, Column, Centered, Row, Text } from '../../../components/ui';
|
||||
import { VidItem } from '../../../components/VidItem';
|
||||
import { Theme } from '../../../components/ui/styleUtils';
|
||||
import { ToastItem } from '../../../components/ui/ToastItem';
|
||||
import { OIDcAuthenticationOverlay } from '../../../components/OIDcAuthModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRevoke } from '../../Profile/RevokeController';
|
||||
import { BindingVcWarningOverlay } from './BindingVcWarningOverlay';
|
||||
import { useViewVcModal } from '../ViewVcModalController';
|
||||
import { OtpVerification } from './OtpVerification';
|
||||
import { useWalletBinding } from './WalletBindingController';
|
||||
import { MessageOverlay } from '../../../components/MessageOverlay';
|
||||
|
||||
export const WalletBinding: React.FC<WalletBindingProps> = (props) => {
|
||||
const controller = useWalletBinding();
|
||||
|
||||
const { t } = useTranslation('ProfileScreen');
|
||||
|
||||
return (
|
||||
<ListItem bottomDivider onPress={() => controller.setBindingWarning(true)}>
|
||||
{props.Icon && (
|
||||
<Icon
|
||||
name={props.Icon}
|
||||
type="font-awesome"
|
||||
size={20}
|
||||
style={Theme.Styles.profileIconBg}
|
||||
color={Theme.Colors.Icon}
|
||||
/>
|
||||
)}
|
||||
<ListItem.Content>
|
||||
<ListItem.Title>
|
||||
<Text
|
||||
size="small"
|
||||
weight="semibold"
|
||||
color={Theme.Colors.walletbindingLabel}>
|
||||
{props.label}
|
||||
</Text>
|
||||
</ListItem.Title>
|
||||
<Text
|
||||
weight="bold"
|
||||
color={Theme.Colors.walletbindingContent}
|
||||
size="smaller">
|
||||
{props.Content}
|
||||
</Text>
|
||||
</ListItem.Content>
|
||||
|
||||
<BindingVcWarningOverlay
|
||||
isVisible={controller.isBindingWarning}
|
||||
onConfirm={controller.CONFIRM}
|
||||
onCancel={controller.DISMISS}
|
||||
/>
|
||||
|
||||
<OtpVerification
|
||||
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.isWalletBindingInProgress}
|
||||
title={t('inProgress')}
|
||||
progress
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
interface WalletBindingProps {
|
||||
label: string;
|
||||
Content?: string;
|
||||
Icon?: string;
|
||||
}
|
||||
95
screens/Home/MyVcs/WalletBindingController.tsx
Normal file
95
screens/Home/MyVcs/WalletBindingController.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { useSelector } from '@xstate/react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import { GlobalContext } from '../../../shared/GlobalContext';
|
||||
import { selectMyVcs, VcEvents } from '../../../machines/vc';
|
||||
import { vcItemMachine } from '../../../machines/vcItem';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ActorRefFrom } from 'xstate';
|
||||
import {
|
||||
selectIsAcceptingOtpInput,
|
||||
walletBindingInProgress,
|
||||
selectOtpError,
|
||||
WalletBindingEvents,
|
||||
selectShowWalletBindingError,
|
||||
selectWalletBindingError,
|
||||
} from '../../../machines/walletBinding';
|
||||
|
||||
export function useWalletBinding() {
|
||||
const { t } = useTranslation('ProfileScreen');
|
||||
const { appService } = useContext(GlobalContext);
|
||||
const vcService = appService.children.get('vc');
|
||||
const walletBindingService = appService.children.get('walletBinding');
|
||||
const vcKeys = useSelector(vcService, selectMyVcs);
|
||||
// const isBindingWarning = useSelector(
|
||||
// walletBindingService,
|
||||
// selectIsBindingWarning
|
||||
// );
|
||||
const isAcceptingOtpInput = useSelector(
|
||||
walletBindingService,
|
||||
selectIsAcceptingOtpInput
|
||||
);
|
||||
|
||||
const isWalletBindingInProgress = useSelector(
|
||||
walletBindingService,
|
||||
walletBindingInProgress
|
||||
);
|
||||
const isWalletBindingError = useSelector(
|
||||
walletBindingService,
|
||||
selectShowWalletBindingError
|
||||
);
|
||||
|
||||
const walletBindingError = useSelector(
|
||||
walletBindingService,
|
||||
selectWalletBindingError
|
||||
);
|
||||
|
||||
const otpError = useSelector(walletBindingService, selectOtpError);
|
||||
const [isRevoking, setRevoking] = useState(false);
|
||||
const [isAuthenticating, setAuthenticating] = useState(false);
|
||||
const [isViewing, setIsViewing] = useState(false);
|
||||
const [toastVisible, setToastVisible] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(null);
|
||||
const [selectedVidKeys, setSelectedVidKeys] = useState<string[]>([]);
|
||||
const [isBindingWarning, setBindingWarning] = useState(false);
|
||||
const [isAcceptingBindingOtp, setAcceptingBindingOtp] = useState(false);
|
||||
const selectVcItem = (index: number, vcKey: string) => {
|
||||
return () => {
|
||||
setSelectedIndex(index);
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
isBindingWarning,
|
||||
otpError,
|
||||
isAcceptingOtpInput,
|
||||
isWalletBindingInProgress,
|
||||
isWalletBindingError,
|
||||
walletBindingError,
|
||||
|
||||
DISMISS: () => {
|
||||
walletBindingService.send(WalletBindingEvents.DISMISS());
|
||||
},
|
||||
INPUT_OTP: (otp: string) =>
|
||||
walletBindingService.send(WalletBindingEvents.INPUT_OTP(otp)),
|
||||
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
|
||||
|
||||
CONFIRM: () => {
|
||||
walletBindingService.send(WalletBindingEvents.CONFIRM());
|
||||
},
|
||||
CANCEL: () => {
|
||||
walletBindingService.send(WalletBindingEvents.CANCEL());
|
||||
},
|
||||
setBindingWarning,
|
||||
setAuthenticating,
|
||||
selectVcItem,
|
||||
setIsViewing,
|
||||
setRevoking,
|
||||
};
|
||||
}
|
||||
|
||||
export interface RevokeProps {
|
||||
service: ActorRefFrom<typeof vcItemMachine>;
|
||||
}
|
||||
@@ -28,9 +28,12 @@ export function useWelcomeScreen(props: RootRouteProps) {
|
||||
return {
|
||||
isSettingUp,
|
||||
isLanguagesetup,
|
||||
NEXT: () => {
|
||||
authService.send(AuthEvents.NEXT()), props.navigation.navigate('Auth');
|
||||
},
|
||||
SELECT: () => {
|
||||
authService.send(AuthEvents.SELECT()),
|
||||
props.navigation.navigate('Welcome');
|
||||
props.navigation.navigate('IntroSliders');
|
||||
},
|
||||
unlockPage: () => {
|
||||
// prioritize biometrics
|
||||
|
||||
Reference in New Issue
Block a user