feat:languase selection and intro sliders

This commit is contained in:
srikanth716
2023-03-04 23:05:28 +05:30
committed by Sri Kanth Kola
parent dad70faf92
commit 52ff018f49
20 changed files with 893 additions and 12 deletions

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

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

65
components/KebabPopUp.tsx Normal file
View 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;
}

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -46,6 +46,7 @@ export interface Typegen0 {
| 'authorized'
| 'checkingAuth'
| 'init'
| 'introSlider'
| 'languagesetup'
| 'savingDefaults'
| 'settingUp'

View File

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

View File

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

View File

@@ -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) => (

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

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

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

View File

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