diff --git a/.env.sample b/.env.sample index 9119960b..dd1dcdd3 100644 --- a/.env.sample +++ b/.env.sample @@ -14,4 +14,11 @@ OCA_URL=https://raw.githubusercontent.com/credebl/credebl-aries-oca-bundles/rele PUBLIC_ORG=https://example.com #PROOF_TEMPLATE_URL -PROOF_TEMPLATE_URL= \ No newline at end of file +PROOF_TEMPLATE_URL= + +# Google Signin +GOOGLE_WEB_CLIENT_ID=CLIENT_ID +GOOGLE_IOS_CLIENT_ID=CLIENT_ID + +# DATA ENCYPTION KEY +DATA_ENCRYPTION_KEY=DATA_ENCRYPTION_KEY diff --git a/.gitignore b/.gitignore index 2b2c9587..d8a221e3 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ yarn-error.log .env google-services.json +# VS Code +.vscode/ \ No newline at end of file diff --git a/App.tsx b/App.tsx index 1c7febee..9073981f 100644 --- a/App.tsx +++ b/App.tsx @@ -2,9 +2,11 @@ global.Buffer = require('buffer').Buffer import { AdeyaAgentProvider } from '@adeya/ssi' +import { GoogleSignin } from '@react-native-google-signin/google-signin' import * as React from 'react' import { useEffect, useMemo } from 'react' import { StatusBar } from 'react-native' +import { Config } from 'react-native-config' import SplashScreen from 'react-native-splash-screen' import Toast from 'react-native-toast-message' @@ -38,6 +40,15 @@ const App = () => { // Hide the native splash / loading screen so that our // RN version can be displayed SplashScreen.hide() + + if (Config.GOOGLE_WEB_CLIENT_ID && Config.GOOGLE_IOS_CLIENT_ID) { + GoogleSignin.configure({ + webClientId: Config.GOOGLE_WEB_CLIENT_ID, + iosClientId: Config.GOOGLE_IOS_CLIENT_ID, + offlineAccess: true, + scopes: ['https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.metadata'], + }) + } }, []) return ( diff --git a/android/app/build.gradle b/android/app/build.gradle index 1761656b..336ca720 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -81,8 +81,8 @@ android { applicationId "id.credebl.adeya" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 12 - versionName "1.0.9" + versionCode 16 + versionName "1.0.10" missingDimensionStrategy 'react-native-camera', 'general' } @@ -114,6 +114,15 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } + + packagingOptions { + pickFirst 'lib/x86/libc++_shared.so' + pickFirst 'lib/x86_64/libjsc.so' + pickFirst 'lib/arm64-v8a/libjsc.so' + pickFirst 'lib/arm64-v8a/libc++_shared.so' + pickFirst 'lib/x86_64/libc++_shared.so' + pickFirst 'lib/armeabi-v7a/libc++_shared.so' + } } dependencies { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0b232e36..01db3c5f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -59,9 +59,7 @@ - - - + diff --git a/android/build.gradle b/android/build.gradle index 3b3cb80d..e4389a69 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,7 +3,7 @@ buildscript { ext { buildToolsVersion = "33.0.0" - minSdkVersion = 21 + minSdkVersion = 24 compileSdkVersion = 34 targetSdkVersion = 34 diff --git a/app/components/listItems/NotificationListItem.tsx b/app/components/listItems/NotificationListItem.tsx index c61ea2b4..38994fce 100644 --- a/app/components/listItems/NotificationListItem.tsx +++ b/app/components/listItems/NotificationListItem.tsx @@ -7,6 +7,8 @@ import { declineProofRequest as declineProof, getProofRequestAgentMessage, } from '@adeya/ssi' +// eslint-disable-next-line import/no-extraneous-dependencies +import { V2RequestPresentationMessage } from '@credo-ts/core' import { useNavigation } from '@react-navigation/core' import { StackNavigationProp } from '@react-navigation/stack' import React, { useState, useEffect } from 'react' @@ -67,6 +69,7 @@ const NotificationListItem: React.FC = ({ notificatio const { ColorPallet, TextTheme } = useTheme() const { agent } = useAppAgent() const [declineModalVisible, setDeclineModalVisible] = useState(false) + const [notificationDetails, setNotificationDetails] = useState(null) const [details, setDetails] = useState({ type: InfoBoxType.Info, title: undefined, @@ -215,6 +218,7 @@ const NotificationListItem: React.FC = ({ notificatio case NotificationType.ProofRequest: { const proofId = (notification as ProofExchangeRecord).id getProofRequestAgentMessage(agent, proofId).then(message => { + setNotificationDetails(message) if (message instanceof V1RequestPresentationMessage && message.indyProofRequest) { resolve({ type: InfoBoxType.Info, @@ -222,6 +226,17 @@ const NotificationListItem: React.FC = ({ notificatio body: message.indyProofRequest.name, buttonTitle: undefined, }) + } else if ( + message instanceof V2RequestPresentationMessage && + message?.formats?.length > 0 && + message?.formats[0].format.includes('dif/presentation-exchange') + ) { + resolve({ + type: InfoBoxType.Info, + title: t('ProofRequest.NewProofRequest'), + body: message?.requestAttachments[0]?.data?.json?.presentation_definition?.name ?? 'Proof Request', + buttonTitle: undefined, + }) } else { //TODO:(jl) Should we have a default message or stick with an empty string? resolve({ @@ -277,10 +292,21 @@ const NotificationListItem: React.FC = ({ notificatio } } else { onPress = () => { - navigation.getParent()?.navigate(Stacks.NotificationStack, { - screen: Screens.ProofRequest, - params: { proofId: (notification as ProofExchangeRecord).id }, - }) + // Added this check to navigate to different screen if proof request is of presentation exchange format + if ( + notificationDetails?.formats?.length > 0 && + notificationDetails?.formats[0].format.includes('dif/presentation-exchange') + ) { + navigation.getParent()?.navigate(Stacks.NotificationStack, { + screen: Screens.ProofRequestW3C, + params: { proofId: (notification as ProofExchangeRecord).id }, + }) + } else { + navigation.getParent()?.navigate(Stacks.NotificationStack, { + screen: Screens.ProofRequest, + params: { proofId: (notification as ProofExchangeRecord).id }, + }) + } } } onClose = toggleDeclineModalVisible diff --git a/app/components/misc/CredentialCard10.tsx b/app/components/misc/CredentialCard10.tsx index 558ccfef..39aff1f6 100644 --- a/app/components/misc/CredentialCard10.tsx +++ b/app/components/misc/CredentialCard10.tsx @@ -1,4 +1,4 @@ -import { CredentialExchangeRecord } from '@adeya/ssi' +import { CredentialExchangeRecord, useConnections } from '@adeya/ssi' import { LegacyBrandingOverlay } from '@hyperledger/aries-oca' import { CredentialOverlay } from '@hyperledger/aries-oca/build/legacy' import React, { useEffect, useState } from 'react' @@ -75,7 +75,8 @@ const CredentialCard10: React.FC = ({ credential, style = const { OCABundleResolver } = useConfiguration() const [overlay, setOverlay] = useState>({}) const [isRevoked, setIsRevoked] = useState(false) - const credentialConnectionLabel = getCredentialConnectionLabel(credential) + const { records } = useConnections() + const credentialConnectionLabel = getCredentialConnectionLabel(records, credential) const styles = StyleSheet.create({ container: { diff --git a/app/components/misc/CredentialCard11.tsx b/app/components/misc/CredentialCard11.tsx index 6d389357..97afb7ee 100644 --- a/app/components/misc/CredentialCard11.tsx +++ b/app/components/misc/CredentialCard11.tsx @@ -1,4 +1,4 @@ -import { CredentialExchangeRecord } from '@adeya/ssi' +import { CredentialExchangeRecord, useConnections } from '@adeya/ssi' import { BrandingOverlay } from '@hyperledger/aries-oca' import { Attribute, CredentialOverlay, Predicate } from '@hyperledger/aries-oca/build/legacy' import startCase from 'lodash.startcase' @@ -91,7 +91,8 @@ const CredentialCard11: React.FC = ({ const { ColorPallet, TextTheme, ListItems } = useTheme() const { OCABundleResolver } = useConfiguration() const [isRevoked, setIsRevoked] = useState(credential?.revocationNotification !== undefined) - const credentialConnectionLabel = getCredentialConnectionLabel(credential, connectionLabel) + const { records } = useConnections() + const credentialConnectionLabel = getCredentialConnectionLabel(records, credential, connectionLabel) const [isProofRevoked, setIsProofRevoked] = useState( credential?.revocationNotification !== undefined && !!proof, ) @@ -136,7 +137,7 @@ const CredentialCard11: React.FC = ({ }, primaryBodyContainer: { flexGrow: 1, - padding, + padding: 15, }, imageAttr: { height: 150, @@ -308,6 +309,7 @@ const CredentialCard11: React.FC = ({ styles.textContainer, { lineHeight: 24, + width: '85%', fontWeight: 'bold', }, ]} @@ -379,7 +381,7 @@ const CredentialCard11: React.FC = ({ { fontWeight: 'bold', lineHeight: 24, - flex: 1, + width: '85%', flexWrap: 'wrap', }, ]}> diff --git a/app/components/record/W3CCredentialRecord.tsx b/app/components/record/W3CCredentialRecord.tsx index 7db03dda..71e0791e 100644 --- a/app/components/record/W3CCredentialRecord.tsx +++ b/app/components/record/W3CCredentialRecord.tsx @@ -1,7 +1,7 @@ +import { W3cCredentialRecord } from '@adeya/ssi' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native' -import Icon from 'react-native-vector-icons/Feather' +import { ActivityIndicator, FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native' import { useTheme } from '../../contexts/theme' import { Field, W3CCredentialAttributeField } from '../../types/record' @@ -17,13 +17,31 @@ export interface RecordProps { fields: Field[] hideFieldValues?: boolean tables: W3CCredentialAttributeField[] + w3cCredential?: Pick & { + credential: Pick, 'credential'> & { + prettyVc?: string + } + } + renderCertificate?: () => void + isCertificateLoading?: boolean } -const W3CCredentialRecord: React.FC = ({ header, footer, fields, hideFieldValues = false, tables }) => { +const W3CCredentialRecord: React.FC = ({ + header, + footer, + hideFieldValues = false, + tables, + w3cCredential, + renderCertificate, + isCertificateLoading, +}) => { const { t } = useTranslation() - const [shown, setShown] = useState([]) - const [showAll, setShowAll] = useState(false) - const { ListItems, TextTheme } = useTheme() + const [shown, setShown] = useState([]) + const [showAll, setShowAll] = useState(true) + const { ListItems, TextTheme, ColorPallet } = useTheme() + + const isPrettyVcAvailable = + w3cCredential?.credential?.prettyVc && Object.keys(w3cCredential?.credential?.prettyVc).length > 0 const styles = StyleSheet.create({ linkContainer: { @@ -37,22 +55,28 @@ const W3CCredentialRecord: React.FC = ({ header, footer, fields, hi minHeight: TextTheme.normal.fontSize, paddingVertical: 2, }, + container: { + padding: 16, + flexDirection: 'row', + }, + rowContainer: { + flexDirection: 'row', + justifyContent: isPrettyVcAvailable ? 'space-between' : 'flex-end', + backgroundColor: ColorPallet.grayscale.white, + }, + linkText: { + fontWeight: 'bold', + }, }) const resetShown = (): void => { - setShown(fields.map(() => showAll)) + setShown(tables.map(table => table.rows.map(() => showAll))) setShowAll(!showAll) } - const toggleShownState = (newShowStates: boolean[]): void => { - if (newShowStates.filter(shownState => shownState === showAll).length > Math.floor(fields.length / 2)) { - setShowAll(!showAll) - } - } - useEffect(() => { resetShown() - }, []) + }, [tables]) return ( = ({ header, footer, fields, hi keyExtractor={({ title }, index) => title || index.toString()} renderItem={({ item: table, index }) => ( - - {table.depth > 1 && } + {table.title && ( = ({ header, footer, fields, hi hideFieldValue={hideFieldValues} onToggleViewPressed={() => { const newShowState = [...shown] - newShowState[index] = !shown[index] + newShowState[index][idx] = !shown[index][idx] setShown(newShowState) - toggleShownState(newShowState) }} - shown={hideFieldValues ? !!shown[index] : true} + shown={hideFieldValues ? !!(shown?.[index]?.[idx] ?? false) : true} hideBottomBorder={idx === table.rows.length - 1} /> ))} @@ -100,19 +122,39 @@ const W3CCredentialRecord: React.FC = ({ header, footer, fields, hi header ? ( {header()} - {hideFieldValues ? ( - - resetShown()} - testID={testIdWithKey('HideAll')} - accessible={true} - accessibilityLabel={showAll ? t('Record.ShowAll') : t('Record.HideAll')}> - {showAll ? t('Record.ShowAll') : t('Record.HideAll')} - - - ) : null} + + {isPrettyVcAvailable && ( + + {isCertificateLoading ? ( + + ) : ( + + {t('Record.ViewCertificate')} + + )} + + )} + + {hideFieldValues ? ( + + resetShown()} + testID={testIdWithKey('HideAll')} + accessible={true} + accessibilityLabel={showAll ? t('Record.ShowAll') : t('Record.HideAll')}> + {showAll ? t('Record.ShowAll') : t('Record.HideAll')} + + + ) : null} + ) : null } diff --git a/app/components/record/W3CCredentialRecordField.tsx b/app/components/record/W3CCredentialRecordField.tsx index c8e5c8cc..069fc697 100644 --- a/app/components/record/W3CCredentialRecordField.tsx +++ b/app/components/record/W3CCredentialRecordField.tsx @@ -98,7 +98,7 @@ const W3CCredentialRecordField: React.FC = ({ ) : null} - {} + ) } diff --git a/app/constants.ts b/app/constants.ts index 62896aea..42cb2aff 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -79,3 +79,5 @@ export const domain = 'didcomm://invite' export const tourMargin = 25 export const hitSlop = { top: 44, bottom: 44, left: 44, right: 44 } + +export const CREDENTIAL = 'Credential' diff --git a/app/contexts/auth.tsx b/app/contexts/auth.tsx index 87088413..c6abdfa9 100644 --- a/app/contexts/auth.tsx +++ b/app/contexts/auth.tsx @@ -2,7 +2,9 @@ import 'reflect-metadata' import { isWalletPinCorrect } from '@adeya/ssi' -import React, { PropsWithChildren, createContext, useContext, useState } from 'react' +import AsyncStorage from '@react-native-async-storage/async-storage' +import { GoogleSignin } from '@react-native-google-signin/google-signin' +import React, { PropsWithChildren, createContext, useContext, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { DeviceEventEmitter } from 'react-native' @@ -29,15 +31,41 @@ export interface AuthContext { setPIN: (PIN: string) => Promise commitPIN: (useBiometry: boolean) => Promise isBiometricsActive: () => Promise + isGoogleAccountSignedIn: boolean + googleSignIn: () => Promise + googleSignOut: () => Promise } export const AuthContext = createContext(null as unknown as AuthContext) export const AuthProvider: React.FC = ({ children }) => { const [walletSecret, setWalletSecret] = useState() + const [isGoogleAccountSignedIn, setIsGoogleAccountSignedIn] = useState(false) const [, dispatch] = useStore() const { t } = useTranslation() + useEffect(() => { + const checkGoogleSignInStatus = async () => { + const googleUserInfo = await AsyncStorage.getItem('googleUserInfo') + setIsGoogleAccountSignedIn(googleUserInfo !== null) + } + checkGoogleSignInStatus() + }, []) + + const googleSignIn = async (): Promise => { + setIsGoogleAccountSignedIn(true) + } + + const googleSignOut = async (): Promise => { + try { + await GoogleSignin.signOut() + await AsyncStorage.removeItem('googleUserInfo') + setIsGoogleAccountSignedIn(false) + } catch (error) { + // error message + } + } + const setPIN = async (PIN: string): Promise => { const secret = await secretForPIN(PIN) await storeWalletSecret(secret) @@ -128,6 +156,9 @@ export const AuthProvider: React.FC = ({ children }) => { commitPIN, setPIN, isBiometricsActive, + isGoogleAccountSignedIn, + googleSignIn, + googleSignOut, }}> {children} diff --git a/app/localization/en/index.ts b/app/localization/en/index.ts index ef217349..9d8a226b 100644 --- a/app/localization/en/index.ts +++ b/app/localization/en/index.ts @@ -486,6 +486,7 @@ const translation = { "ScanMyQR": "Scan my QR code", "Developer": "Developer options", "Backup": "backup wallet", + "GoogleDriveBackup": "Google Drive Backup", "Confirmation" : 'Confirmation' }, "TabStack": { @@ -514,6 +515,7 @@ const translation = { "Hidden": "Hidden", "InvalidDate": "Invalid Date: ", "Zoom": "Zoom", + "ViewCertificate": "View Certificate", }, "Screens": { "Splash": "Splash", @@ -566,9 +568,12 @@ const translation = { "Explore": "Explore", "OrganizationDetails": "Organization Details", "ProofChangeCredential": "Choose a credential", + "ProofChangeCredentialW3C": "Choose a W3C credential", "DataRetention": "Data retention", "Organization": "Explore", - "OrganizationConnection": "Connection" + "OrganizationConnection": "Connection", + "RenderCertificate": "Certificate", + "GoogleDriveSignIn": "Google Drive Sign In", }, "Loading": { "TakingTooLong": "This is taking longer than usual. You can return to home or continue waiting.", @@ -692,6 +697,7 @@ const translation = { "you_have_successfully": 'You have successfully selected the words.', "complete_backup": 'Complete Backup', "backup_wallet": 'Backup Wallet', + "backup_google_drive": 'Backup to Google Drive', }, "PushNotifications": { "BulletFour": "new messages", @@ -704,7 +710,27 @@ const translation = { "NotAvailable": " (Not Available)", "Title": "Notifications", "PushNotifications": "Push Notifications", - } + }, + "DIDs":{ + "Dids": "My DID", + }, + "GoogleDrive": { + "Backup": "Backup to Google Drive", + "BackupTitle": "Backup to Google Drive", + "SignInCancelled": "User cancelled the login flow", + "SignInProgress": "Operation is in progress already", + "PlayServicesNotAvailable": "Play services not available or outdated", + "SignInError": "An error occurred during sign-in", + "SignOutGoogle": "Sign Out of Google Account", + "SignOutGoogleSuccess": "Google Sign Out Successful", + "BackupFailed": "Backup failed to upload to Google Drive. Please try again later.", + "BackupSuccess": "Backup successfully uploaded to Google Drive.", + }, + "Restore": { + "RestoreWallet": "Restore Wallet", + "RestoreInstructions": "Note: To restore your wallet, you can use a backup from your cloud storage or local device. Please ensure that the Google Drive app is installed on your device and you are signed in.", + "RestoreInstructionsIOS": "If you can't see Google Drive in the file picker, open the Files app --> tap on the three dots at the top --> select 'Edit' --> enable Google Drive.", + }, } export default translation diff --git a/app/navigators/ContactStack.tsx b/app/navigators/ContactStack.tsx index e0518907..a813bc4f 100644 --- a/app/navigators/ContactStack.tsx +++ b/app/navigators/ContactStack.tsx @@ -15,6 +15,7 @@ import Home from '../screens/Home' import ListContacts from '../screens/ListContacts' import ProofDetails from '../screens/ProofDetails' import ProofRequest from '../screens/ProofRequest' +import ProofRequestW3C from '../screens/ProofRequestW3C' import WhatAreContacts from '../screens/WhatAreContacts' import { ContactStackParams, Screens } from '../types/navigators' import { testIdWithKey } from '../utils/testable' @@ -61,7 +62,7 @@ const ContactStack: React.FC = () => { { component={ProofRequest} options={{ title: t('Screens.ProofRequest') }} /> + { + diff --git a/app/navigators/DeliveryStack.tsx b/app/navigators/DeliveryStack.tsx index 9775f4c7..250c40a5 100644 --- a/app/navigators/DeliveryStack.tsx +++ b/app/navigators/DeliveryStack.tsx @@ -10,6 +10,7 @@ import Connection from '../screens/Connection' import ContactDetails from '../screens/ContactDetails' import CredentialOffer from '../screens/CredentialOffer' import ProofRequest from '../screens/ProofRequest' +import ProofRequestW3C from '../screens/ProofRequestW3C' import { DeliveryStackParams, Screens, TabStacks } from '../types/navigators' import { testIdWithKey } from '../utils/testable' @@ -38,6 +39,11 @@ const DeliveryStack: React.FC = () => { component={ProofRequest} options={{ title: t('Screens.ProofRequest') }} /> + { component={ProofRequest} options={{ title: t('Screens.ProofRequest') }} /> + { component={ProofChangeCredential} options={{ title: t('Screens.ProofChangeCredential') }} /> + { // handle deeplink events useEffect(() => { async function handleDeepLink(deepLink: string) { + let invitationUrl = deepLink try { + if (invitationUrl.includes('?url=')) { + const parts = invitationUrl.split('=') + invitationUrl = parts[1] + } + // check if connection already exists - const isAlreadyConnected = await checkIfAlreadyConnected(agent, deepLink) + const isAlreadyConnected = await checkIfAlreadyConnected(agent, invitationUrl) if (isAlreadyConnected) { Toast.show({ @@ -100,25 +114,64 @@ const RootStack: React.FC = () => { } // Try connection based - const { connectionRecord } = await connectFromInvitation(agent, deepLink) + const { connectionRecord, outOfBandRecord } = await connectFromInvitation(agent, invitationUrl) navigation.navigate(Stacks.ConnectionStack as any, { screen: Screens.Connection, - params: { connectionId: connectionRecord?.id }, + params: { connectionId: connectionRecord?.id, outOfBandId: outOfBandRecord.id }, }) } catch { try { - // Try connectionless here - const message = await getOobDeepLink(deepLink, agent) - navigation.navigate(Stacks.ConnectionStack as any, { - screen: Screens.Connection, - params: { threadId: message['@id'] }, - }) + const json = getJson(invitationUrl) + if (json) { + await agent?.receiveMessage(json) + navigation.getParent()?.navigate(Stacks.ConnectionStack, { + screen: Screens.Connection, + params: { threadId: json['@id'] }, + }) + return + } + + const urlData = await fetchUrlData(invitationUrl) + const isValidURL = isValidUrl(urlData) + + if (isValidURL) { + const isAlreadyConnected = await checkIfAlreadyConnected(agent, urlData) + + if (isAlreadyConnected) { + Toast.show({ + type: ToastType.Warn, + text1: t('Contacts.AlreadyConnected'), + }) + navigation.goBack() + return + } + + const { connectionRecord, outOfBandRecord } = await connectFromInvitation(agent, urlData) + + navigation.getParent()?.navigate(Stacks.ConnectionStack, { + screen: Screens.Connection, + params: { connectionId: connectionRecord?.id, outOfBandId: outOfBandRecord.id }, + }) + return + } + // if scanned value is url -> receive message from it + + const url = getUrl(invitationUrl) + + if (url) { + const message = await receiveMessageFromUrlRedirect(invitationUrl, agent) + navigation.getParent()?.navigate(Stacks.ConnectionStack, { + screen: Screens.Connection, + params: { threadId: message['@id'] }, + }) + return + } } catch (error) { // TODO:(am add error handling here) } } - // set deeplink as inactive + // set deep link as inactive dispatch({ type: DispatchAction.ACTIVE_DEEP_LINK, payload: [undefined], diff --git a/app/navigators/SettingStack.tsx b/app/navigators/SettingStack.tsx index 3ccba712..2a241b21 100644 --- a/app/navigators/SettingStack.tsx +++ b/app/navigators/SettingStack.tsx @@ -10,6 +10,7 @@ import CreateWallet from '../screens/CreateWallet' import DataRetention from '../screens/DataRetention' import ExportWallet from '../screens/ExportWallet' import ExportWalletConfirmation from '../screens/ExportWalletConfirmation' +import GoogleDriveSignIn from '../screens/GoogleDriveSignIn' import ImportSuccess from '../screens/ImportSuccess' import ImportWalletVerify from '../screens/ImportWalletConfirmation' import Language from '../screens/Language' @@ -137,6 +138,11 @@ const SettingStack: React.FC = () => { component={CreateWallet} options={{ title: t('Screens.CreateWallet') }} /> + ) } diff --git a/app/screens/Chat.tsx b/app/screens/Chat.tsx index 21add545..3fef5617 100644 --- a/app/screens/Chat.tsx +++ b/app/screens/Chat.tsx @@ -223,9 +223,18 @@ const Chat: React.FC = ({ navigation, route }) => { [ProofState.PresentationSent]: toProofDetails, [ProofState.PresentationReceived]: toProofDetails, [ProofState.RequestReceived]: () => { - navigation.navigate(Stacks.ContactStack as any, { - screen: Screens.ProofRequest, - params: { proofId: record.id }, + agent.proofs.getFormatData(record.id).then(value => { + if (value?.request?.indy) { + navigation.navigate(Stacks.ContactStack as any, { + screen: Screens.ProofRequest, + params: { proofId: record.id }, + }) + } else { + navigation.navigate(Stacks.ContactStack as any, { + screen: Screens.ProofRequestW3C, + params: { proofId: record.id }, + }) + } }) }, } diff --git a/app/screens/Connection.tsx b/app/screens/Connection.tsx index b390f316..049c4481 100644 --- a/app/screens/Connection.tsx +++ b/app/screens/Connection.tsx @@ -11,7 +11,7 @@ import Button, { ButtonType } from '../components/buttons/Button' import { useAnimatedComponents } from '../contexts/animated-components' import { useConfiguration } from '../contexts/configuration' import { useTheme } from '../contexts/theme' -import { useOutOfBandByConnectionId } from '../hooks/connections' +import { useOutOfBandById } from '../hooks/connections' import { useNotifications } from '../hooks/notifications' import { Screens, TabStacks, DeliveryStackParams, Stacks } from '../types/navigators' import { useAppAgent } from '../utils/agent' @@ -35,7 +35,7 @@ const Connection: React.FC = ({ navigation, route }) => { // delay message, the user should be redirected to the home screen. const { connectionTimerDelay, autoRedirectConnectionToHome } = useConfiguration() const connTimerDelay = connectionTimerDelay ?? 10000 // in ms - const { connectionId, threadId } = route.params + const { connectionId, outOfBandId, threadId } = route.params const timerRef = useRef(null) const connection = connectionId ? useConnectionById(connectionId) : undefined const { t } = useTranslation() @@ -43,7 +43,7 @@ const Connection: React.FC = ({ navigation, route }) => { const { ColorPallet, TextTheme } = useTheme() const { ConnectionLoading } = useAnimatedComponents() const { agent } = useAppAgent() - const oobRecord = useOutOfBandByConnectionId(agent, connectionId ?? '') + const oobRecord = useOutOfBandById(agent, outOfBandId ?? '') const goalCode = oobRecord?.outOfBandInvitation.goalCode const merge: MergeFunction = (current, next) => ({ ...current, ...next }) const [state, dispatch] = useReducer(merge, { @@ -52,6 +52,7 @@ const Connection: React.FC = ({ navigation, route }) => { shouldShowDelayMessage: false, connectionIsActive: false, }) + const styles = StyleSheet.create({ container: { height: '100%', @@ -168,7 +169,7 @@ const Connection: React.FC = ({ navigation, route }) => { if (state.notificationRecord && goalCode) { goalCodeAction(goalCode)() } - }, [connection, goalCode, state.notificationRecord]) + }, [connection, oobRecord, goalCode, state.notificationRecord]) useMemo(() => { startTimer() diff --git a/app/screens/CreateWallet.tsx b/app/screens/CreateWallet.tsx index 24874dc8..9f96732b 100644 --- a/app/screens/CreateWallet.tsx +++ b/app/screens/CreateWallet.tsx @@ -1,6 +1,8 @@ import { useNavigation } from '@react-navigation/core' -import React from 'react' -import { View, StyleSheet, Text } from 'react-native' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { View, StyleSheet, Text, Platform, Modal, TouchableOpacity } from 'react-native' +import Icon from 'react-native-vector-icons/MaterialIcons' import Button, { ButtonType } from '../components/buttons/Button' import { useTheme } from '../contexts/theme' @@ -9,6 +11,20 @@ import { Screens } from '../types/navigators' const CreateWallet: React.FC = () => { const { TextTheme, ColorPallet } = useTheme() const navigation = useNavigation() + const { t } = useTranslation() + const [isModalVisible, setModalVisible] = useState(false) + + const toggleModal = () => { + setModalVisible(!isModalVisible) + } + + const proceedWithRestore = () => { + toggleModal() + setTimeout(() => { + navigation.navigate(Screens.ImportWalletVerify as never) + }, 300) + } + const styles = StyleSheet.create({ container: { flex: 1, @@ -21,6 +37,11 @@ const CreateWallet: React.FC = () => { color: ColorPallet.brand.primary, marginTop: 20, }, + instructionsText: { + fontSize: 16, + color: ColorPallet.brand.primary, + marginVertical: 10, + }, walletButtonView: { marginTop: 'auto', margin: 20, @@ -28,6 +49,29 @@ const CreateWallet: React.FC = () => { restoreWalletView: { marginTop: 20, }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + padding: 20, + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 15, + }, + modalContent: { + backgroundColor: ColorPallet.brand.modalSecondary, + padding: 22, + borderRadius: 4, + }, + modalTitle: { + fontSize: 24, + color: ColorPallet.brand.primary, + fontWeight: 'bold', + }, }) return ( @@ -43,11 +87,35 @@ const CreateWallet: React.FC = () => {