From 93547bcf9a64661ab289e21da508bc88cb48627b Mon Sep 17 00:00:00 2001 From: "Jason C. Leach" Date: Wed, 31 Jan 2024 11:56:10 -0800 Subject: [PATCH 01/77] fix: fix URL issue (#1077) Signed-off-by: Jason C. Leach --- packages/remote-logs/src/transports.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/remote-logs/src/transports.ts b/packages/remote-logs/src/transports.ts index 022ccb3a..8e4d484f 100644 --- a/packages/remote-logs/src/transports.ts +++ b/packages/remote-logs/src/transports.ts @@ -54,7 +54,10 @@ export const lokiTransport: transportFunctionType = (props: LokiTransportProps) }, ], } - const url = new URL(lokiUrl) + + const [credentials, href] = lokiUrl.split('@') + const [username, password] = credentials.split('//')[1].split(':') + const protocol = credentials.split('//')[0] const axiosConfig: AxiosRequestConfig = { method: 'post', url: lokiUrl, @@ -63,16 +66,12 @@ export const lokiTransport: transportFunctionType = (props: LokiTransportProps) // If username and password are present // in the URL, use them for auth - if (url.username && url.password) { + if (username && password) { axiosConfig.auth = { - username: url.username, - password: url.password, + username, + password, } - // Clear the username and password from - // the URL - url.username = '' - url.password = '' - axiosConfig.url = url.href + axiosConfig.url = `${protocol}//${href}` } axios(axiosConfig) From 37e936381b747e9dba652361b1a9d94dae2a4ede Mon Sep 17 00:00:00 2001 From: NidhishVyas Date: Thu, 1 Feb 2024 13:17:39 -0500 Subject: [PATCH 02/77] testing scanner --- packages/legacy/core/App/screens/QRCodeGen.tsx | 15 ++++++++++++++- packages/legacy/core/App/screens/Scan.tsx | 2 +- packages/legacy/core/App/utils/helpers.ts | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/legacy/core/App/screens/QRCodeGen.tsx b/packages/legacy/core/App/screens/QRCodeGen.tsx index 3ce41e6b..2f0686b8 100644 --- a/packages/legacy/core/App/screens/QRCodeGen.tsx +++ b/packages/legacy/core/App/screens/QRCodeGen.tsx @@ -1,5 +1,6 @@ +import { useAgent } from '@aries-framework/react-hooks' import { NavigationProp, ParamListBase } from '@react-navigation/native' -import React from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native' import QRCode from 'react-native-qrcode-svg' @@ -7,6 +8,9 @@ import QRCode from 'react-native-qrcode-svg' import Link from '../components/texts/Link' import { useTheme } from '../contexts/theme' import { Screens, Stacks } from '../types/navigators' +import { createConnectionInvitation } from '../utils/helpers' +// const { agent } = useAgent() + interface WhatAreContactsProps { navigation: NavigationProp @@ -32,6 +36,15 @@ const QRCodeGen: React.FC = () => { }, }) + // const createInvitation = useCallback(async () => { + // setInvitation(undefined) + // const result = await createConnectionInvitation(agent) + // if (result) { + // setRecordId(result.record.id) + // setInvitation(result.invitationUrl) + // } + // }, []) + return ( = ({ navigation, route }) => { return } - if (store.preferences.useConnectionInviterCapability) { + if (!store.preferences.useConnectionInviterCapability) { return ( Date: Thu, 1 Feb 2024 17:38:13 -0500 Subject: [PATCH 03/77] fix: iOS app permissions (#1053) Signed-off-by: OPS Co-authored-by: OPS --- packages/legacy/app/ios/Podfile | 25 +++++++++++++++++++++++++ packages/legacy/app/ios/Podfile.lock | 14 +++++++------- packages/legacy/app/package.json | 2 +- packages/legacy/core/package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/packages/legacy/app/ios/Podfile b/packages/legacy/app/ios/Podfile index 54fc58f6..ba4ea76a 100644 --- a/packages/legacy/app/ios/Podfile +++ b/packages/legacy/app/ios/Podfile @@ -1,9 +1,34 @@ source 'https://cdn.cocoapods.org' require_relative '../node_modules/react-native/scripts/react_native_pods' +require_relative '../node_modules/react-native-permissions/scripts/setup' platform :ios, min_ios_version_supported prepare_react_native_project! +# ⬇️ uncomment wanted permissions +setup_permissions([ + # 'AppTrackingTransparency', + # 'Bluetooth', + # 'Calendars', + # 'CalendarsWriteOnly', + 'Camera', + # 'Contacts', + 'FaceID', + # 'LocationAccuracy', + 'LocationAlways', + 'LocationWhenInUse', + # 'MediaLibrary', + # 'Microphone', + # 'Motion', + # 'Notifications', + # 'PhotoLibrary', + # 'PhotoLibraryAddOnly', + # 'Reminders', + # 'Siri', + # 'SpeechRecognition', + # 'StoreKit', +]) + # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded # diff --git a/packages/legacy/app/ios/Podfile.lock b/packages/legacy/app/ios/Podfile.lock index d09fcb86..deb95b88 100644 --- a/packages/legacy/app/ios/Podfile.lock +++ b/packages/legacy/app/ios/Podfile.lock @@ -483,7 +483,7 @@ PODS: - React-Core - RNLocalize (2.2.6): - React-Core - - RNPermissions (3.9.2): + - RNPermissions (4.0.1): - React-Core - RNReanimated (3.5.4): - DoubleConversion @@ -734,14 +734,14 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: anoncreds: 8e6ab626d5250ae6301c3e96d6fc739186e083f0 aries-askar: 738c677e194913ed3c256adc953db3fe0494f8f8 - boost: 57d2868c099736d80fcd648bf211b4431e51a558 + boost: a7c83b31436843459a1961bfd74b96033dc77234 CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 + DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 FBLazyVector: 71803c074f6325f10b5ec891c443b6bbabef0ca7 FBReactNativeSpec: 448e08a759d29a96e15725ae532445bf4343567c fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 5337263514dd6f09803962437687240c5dc39aa4 hermes-engine: f6cf92a471053245614d9d8097736f6337d5b86c indy-vdr: 85cd66089f151256581323440e78988891f4082e libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 @@ -794,7 +794,7 @@ SPEC CHECKSUMS: RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 - RNPermissions: 7f93fea75d6ea684fe600b08e2c244098a49244b + RNPermissions: e3da722c6f92f8f58cf81c164e6ee917c2e93ee1 RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87 RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105 @@ -802,6 +802,6 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 86fed2e4d425ee4c6eab3813ba1791101ee153c6 -PODFILE CHECKSUM: 702646d9563d22bf09e336926a5fb0e2d3239409 +PODFILE CHECKSUM: 91c24b25407631a4023c96a168470f60d473e28f -COCOAPODS: 1.13.0 +COCOAPODS: 1.11.2 diff --git a/packages/legacy/app/package.json b/packages/legacy/app/package.json index 18e40c64..21046a43 100644 --- a/packages/legacy/app/package.json +++ b/packages/legacy/app/package.json @@ -69,7 +69,7 @@ "react-native-keychain": "^8.1.1", "react-native-localize": "^2.2.4", "react-native-orientation-locker": "^1.6.0", - "react-native-permissions": "^3.8.4", + "react-native-permissions": "^4.0.1", "react-native-qrcode-svg": "^6.2.0", "react-native-reanimated": "^3.4.2", "react-native-safe-area-context": "^3.2.0", diff --git a/packages/legacy/core/package.json b/packages/legacy/core/package.json index 35ba55f2..674b9de9 100644 --- a/packages/legacy/core/package.json +++ b/packages/legacy/core/package.json @@ -125,7 +125,7 @@ "react-native-keychain": "^8.1.1", "react-native-localize": "^2.2.4", "react-native-orientation-locker": "^1.6.0", - "react-native-permissions": "^3.8.4", + "react-native-permissions": "^4.0.1", "react-native-qrcode-svg": "^6.2.0", "react-native-reanimated": "^3.4.2", "react-native-safe-area-context": "^3.2.0", @@ -198,7 +198,7 @@ "react-native-keychain": "^8.1.1", "react-native-localize": "^2.2.4", "react-native-orientation-locker": "*", - "react-native-permissions": "^3.8.4", + "react-native-permissions": "^4.0.1", "react-native-qrcode-svg": "^6.2.0", "react-native-reanimated": "^3.4.2", "react-native-safe-area-context": "^3.2.0", diff --git a/yarn.lock b/yarn.lock index 7f2a08bc..a7cf7169 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4072,7 +4072,7 @@ __metadata: react-native-keychain: ^8.1.1 react-native-localize: ^2.2.4 react-native-orientation-locker: ^1.6.0 - react-native-permissions: ^3.8.4 + react-native-permissions: ^4.0.1 react-native-qrcode-svg: ^6.2.0 react-native-reanimated: ^3.4.2 react-native-safe-area-context: ^3.2.0 @@ -4144,7 +4144,7 @@ __metadata: react-native-keychain: ^8.1.1 react-native-localize: ^2.2.4 react-native-orientation-locker: "*" - react-native-permissions: ^3.8.4 + react-native-permissions: ^4.0.1 react-native-qrcode-svg: ^6.2.0 react-native-reanimated: ^3.4.2 react-native-safe-area-context: ^3.2.0 @@ -7541,7 +7541,7 @@ __metadata: react-native-keychain: ^8.1.1 react-native-localize: ^2.2.4 react-native-orientation-locker: ^1.6.0 - react-native-permissions: ^3.8.4 + react-native-permissions: ^4.0.1 react-native-qrcode-svg: ^6.2.0 react-native-reanimated: ^3.4.2 react-native-safe-area-context: ^3.2.0 @@ -18680,17 +18680,17 @@ __metadata: languageName: node linkType: hard -"react-native-permissions@npm:^3.8.4": - version: 3.9.2 - resolution: "react-native-permissions@npm:3.9.2" +"react-native-permissions@npm:^4.0.1": + version: 4.0.1 + resolution: "react-native-permissions@npm:4.0.1" peerDependencies: - react: ">=16.13.1" - react-native: ">=0.63.3" - react-native-windows: ">=0.62.0" + react: ">=18.1.0" + react-native: ">=0.70.0" + react-native-windows: ">=0.70.0" peerDependenciesMeta: react-native-windows: optional: true - checksum: 047540958d415e62ea2359f8f5da392a345cec11d7bbe14b065459375b1185431bc98ac144595b9db27376ebb059811f79d9f3c80d61508aeecc3cb5e7c49996 + checksum: d8124c032f1cfce114839ad18101d14c13ae74e9075f978f9f3ecdef490411ec9fe7b134744a5ebe58eb4d2307d00db4d2af6fa1985c564488dcae477b6255e7 languageName: node linkType: hard From eade15319b5fb5e624451c8e39d899574e178af0 Mon Sep 17 00:00:00 2001 From: "Jason C. Leach" Date: Thu, 1 Feb 2024 14:42:11 -0800 Subject: [PATCH 04/77] fix: issue with remote logging package (#1078) Signed-off-by: Jason C. Leach --- packages/remote-logs/package.json | 16 ++++++++------ packages/remote-logs/src/logger.ts | 2 +- packages/remote-logs/src/transports.ts | 30 +++++++++++--------------- yarn.lock | 10 +++++++-- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/remote-logs/package.json b/packages/remote-logs/package.json index 0b767c1a..d9a0d0b2 100644 --- a/packages/remote-logs/package.json +++ b/packages/remote-logs/package.json @@ -31,21 +31,25 @@ }, "homepage": "https://github.com/hyperledger/aries-mobile-agent-react-native#readme", "devDependencies": { + "@aries-framework/core": "0.4.0", "@babel/runtime": "^7.20.0", "@typescript-eslint/parser": "^6.6.0", + "axios": "^1.4.0", + "buffer": "^6.0.3", "eslint": "^8.48.0", "eslint-import-resolver-typescript": "^2.5.0", + "react": "18.2.0", "react-native": "0.72.5", "react-native-logs": "^5.0.1", "rimraf": "^5.0.0", "typescript": "^5.0.4" }, - "dependencies": { - "@aries-framework/core": "0.4.0", - "axios": "^1.4.0" - }, "peerDependencies": { - "react-native": "0.72.5", + "@aries-framework/core": "^0.4.0", + "axios": "^1.4.0", + "buffer": "^6.0.3", + "react": "^18.2.0", + "react-native": "^0.72.5", "react-native-logs": "^5.0.1" } -} +} \ No newline at end of file diff --git a/packages/remote-logs/src/logger.ts b/packages/remote-logs/src/logger.ts index 541808c4..dabf93cb 100644 --- a/packages/remote-logs/src/logger.ts +++ b/packages/remote-logs/src/logger.ts @@ -85,7 +85,7 @@ export class RemoteLogger extends BaseLogger { lokiUrl: this.lokiUrl, lokiLabels: { ...this.lokiLabels, - session_id: this.sessionId, + session_id: `${this.sessionId}`, }, } diff --git a/packages/remote-logs/src/transports.ts b/packages/remote-logs/src/transports.ts index 8e4d484f..a55fc23d 100644 --- a/packages/remote-logs/src/transports.ts +++ b/packages/remote-logs/src/transports.ts @@ -1,5 +1,6 @@ -import axios, { AxiosRequestConfig } from 'axios' +import axios from 'axios' import { transportFunctionType } from 'react-native-logs' +import { Buffer } from 'buffer' export interface RemoteLoggerOptions { lokiUrl?: string @@ -58,29 +59,22 @@ export const lokiTransport: transportFunctionType = (props: LokiTransportProps) const [credentials, href] = lokiUrl.split('@') const [username, password] = credentials.split('//')[1].split(':') const protocol = credentials.split('//')[0] - const axiosConfig: AxiosRequestConfig = { - method: 'post', - url: lokiUrl, - data: payload, + const authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}` + const config = { + headers: { + 'Content-Type': 'application/json', + Authorization: authHeader, + }, } - // If username and password are present - // in the URL, use them for auth - if (username && password) { - axiosConfig.auth = { - username, - password, - } - axiosConfig.url = `${protocol}//${href}` - } - - axios(axiosConfig) + axios + .post(`${protocol}//${href}`, payload, config) .then((res) => { if (res.status !== 204) { - throw new Error(`Expected Loki to return 204, received ${res.status}`) + console.warn(`Expected Loki to return 204, received ${res.status}`) } }) .catch((error) => { - throw new Error(`Error while sending to Loki, ${error}`) + console.error(`Error while sending to Loki, ${error}`) }) } diff --git a/yarn.lock b/yarn.lock index a7cf7169..2e98cc81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4170,14 +4170,20 @@ __metadata: "@babel/runtime": ^7.20.0 "@typescript-eslint/parser": ^6.6.0 axios: ^1.4.0 + buffer: ^6.0.3 eslint: ^8.48.0 eslint-import-resolver-typescript: ^2.5.0 + react: 18.2.0 react-native: 0.72.5 react-native-logs: ^5.0.1 rimraf: ^5.0.0 typescript: ^5.0.4 peerDependencies: - react-native: 0.72.5 + "@aries-framework/core": ^0.4.0 + axios: ^1.4.0 + buffer: ^6.0.3 + react: ^18.2.0 + react-native: ^0.72.5 react-native-logs: ^5.0.1 languageName: unknown linkType: soft @@ -19021,7 +19027,7 @@ __metadata: languageName: node linkType: hard -"react@npm:*, react@npm:^18.2.0": +"react@npm:*, react@npm:18.2.0, react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" dependencies: From fcf5a48616ef2c8f3ff71d1ebd1315bc16f2a348 Mon Sep 17 00:00:00 2001 From: "Jason C. Leach" Date: Thu, 1 Feb 2024 15:27:28 -0800 Subject: [PATCH 05/77] fix: rework package dependencies (#1080) Signed-off-by: Jason C. Leach --- packages/remote-logs/src/transports.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/remote-logs/src/transports.ts b/packages/remote-logs/src/transports.ts index a55fc23d..ab7a3bdc 100644 --- a/packages/remote-logs/src/transports.ts +++ b/packages/remote-logs/src/transports.ts @@ -1,6 +1,7 @@ +/* eslint-disable no-console */ import axios from 'axios' -import { transportFunctionType } from 'react-native-logs' import { Buffer } from 'buffer' +import { transportFunctionType } from 'react-native-logs' export interface RemoteLoggerOptions { lokiUrl?: string From daac881e736f32cbc6e97d67b660bb2f74f2845c Mon Sep 17 00:00:00 2001 From: NidhishVyas Date: Fri, 2 Feb 2024 01:07:49 -0500 Subject: [PATCH 06/77] QR Generator added --- packages/legacy/core/.prettierrc | 3 +- .../core/App/components/misc/QRRenderer.tsx | 9 +- packages/legacy/core/App/constants.ts | 2 +- .../legacy/core/App/screens/QRCodeGen.tsx | 221 +++++++++++++----- packages/legacy/core/App/screens/Scan.tsx | 2 +- packages/legacy/core/App/utils/helpers.ts | 2 - packages/legacy/core/App/utils/ledger.tsx | 1 + 7 files changed, 171 insertions(+), 69 deletions(-) diff --git a/packages/legacy/core/.prettierrc b/packages/legacy/core/.prettierrc index cbe842ac..f547d59a 100644 --- a/packages/legacy/core/.prettierrc +++ b/packages/legacy/core/.prettierrc @@ -1,5 +1,6 @@ { "printWidth": 120, "semi": false, - "singleQuote": true + "singleQuote": true, + "endOfLine": "auto" } diff --git a/packages/legacy/core/App/components/misc/QRRenderer.tsx b/packages/legacy/core/App/components/misc/QRRenderer.tsx index e1697220..03c85384 100644 --- a/packages/legacy/core/App/components/misc/QRRenderer.tsx +++ b/packages/legacy/core/App/components/misc/QRRenderer.tsx @@ -43,7 +43,14 @@ const QRRenderer: React.FC = ({ value, onError, size }) => { return ( - {} + {isInvalidQR && {t('QRRender.GenerationError')}} ) diff --git a/packages/legacy/core/App/constants.ts b/packages/legacy/core/App/constants.ts index 3b06618b..b0cd5947 100644 --- a/packages/legacy/core/App/constants.ts +++ b/packages/legacy/core/App/constants.ts @@ -74,7 +74,7 @@ export const PINRules: PINValidationRules = { no_cross_pattern: false, } -export const domain = 'didcomm://invite' +export const domain = 'https://2aba-18-221-246-68.ngrok-free.app' export const tourMargin = 25 diff --git a/packages/legacy/core/App/screens/QRCodeGen.tsx b/packages/legacy/core/App/screens/QRCodeGen.tsx index 2f0686b8..659f74c1 100644 --- a/packages/legacy/core/App/screens/QRCodeGen.tsx +++ b/packages/legacy/core/App/screens/QRCodeGen.tsx @@ -1,63 +1,158 @@ -import { useAgent } from '@aries-framework/react-hooks' -import { NavigationProp, ParamListBase } from '@react-navigation/native' -import React, { useEffect } from 'react' -import { useTranslation } from 'react-i18next' -import { SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native' -import QRCode from 'react-native-qrcode-svg' - -import Link from '../components/texts/Link' -import { useTheme } from '../contexts/theme' -import { Screens, Stacks } from '../types/navigators' -import { createConnectionInvitation } from '../utils/helpers' -// const { agent } = useAgent() - - -interface WhatAreContactsProps { - navigation: NavigationProp -} - -const QRCodeGen: React.FC = () => { - const { ColorPallet, TextTheme } = useTheme() - const { t } = useTranslation() - const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - backgroundColor: ColorPallet.brand.primaryBackground, - }, - title: { - ...TextTheme.headingTwo, - marginBottom: 15, - }, - pageContent: { - marginTop: 30, - paddingLeft: 25, - paddingRight: 25, - }, - }) - - // const createInvitation = useCallback(async () => { - // setInvitation(undefined) - // const result = await createConnectionInvitation(agent) - // if (result) { - // setRecordId(result.record.id) - // setInvitation(result.invitationUrl) - // } - // }, []) - - return ( - - - Scan the QR Code - - - - ) -} - -export default QRCodeGen +import { DidExchangeState } from '@aries-framework/core' +import { useAgent } from '@aries-framework/react-hooks' +import { StackScreenProps } from '@react-navigation/stack' +import React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { View, StyleSheet, Text, ScrollView, useWindowDimensions } from 'react-native' +import { TouchableOpacity } from 'react-native-gesture-handler' +import { SafeAreaView } from 'react-native-safe-area-context' +import Icon from 'react-native-vector-icons/MaterialIcons' + +import LoadingIndicator from '../components/animated/LoadingIndicator' +import QRRenderer from '../components/misc/QRRenderer' +import { domain } from '../constants' +import { useStore } from '../contexts/store' +import { useTheme } from '../contexts/theme' +import { useConnectionByOutOfBandId } from '../hooks/connections' +import { QrCodeScanError } from '../types/error' +import { Screens, Stacks, ConnectStackParams } from '../types/navigators' +import { createConnectionInvitation } from '../utils/helpers' +import { testIdWithKey } from '../utils/testable' + +type ConnectProps = StackScreenProps + +interface Props extends ConnectProps { + defaultToConnect: boolean + handleCodeScan: (value: string) => Promise + error?: QrCodeScanError | null + enableCameraOnError?: boolean +} + +const NewQRView: React.FC = ({ navigation }) => { + const { width } = useWindowDimensions() + const qrSize = width - 40 + const [store] = useStore() + const [invitation, setInvitation] = useState(undefined) + const [recordId, setRecordId] = useState(undefined) + const { t } = useTranslation() + const { ColorPallet, TextTheme, TabTheme } = useTheme() + const { agent } = useAgent() + + const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: ColorPallet.brand.secondaryBackground, + }, + camera: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + cameraViewContainer: { + alignItems: 'center', + flex: 1, + width: '100%', + }, + viewFinder: { + width: 250, + height: 250, + borderRadius: 24, + borderWidth: 2, + borderColor: ColorPallet.grayscale.white, + }, + viewFinderContainer: { + flex: 1, + width: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 20, + paddingHorizontal: 20, + }, + icon: { + color: ColorPallet.grayscale.white, + padding: 4, + }, + tabContainer: { + flexDirection: 'row', + ...TabTheme.tabBarStyle, + }, + qrContainer: { + marginTop: 10, + flex: 1, + }, + walletName: { + ...TextTheme.headingTwo, + textAlign: 'center', + marginBottom: 20, + }, + secondaryText: { + ...TextTheme.normal, + textAlign: 'center', + }, + editButton: { ...TextTheme.headingTwo, marginBottom: 20, marginLeft: 10, color: ColorPallet.brand.primary }, + }) + + const createInvitation = useCallback(async () => { + setInvitation(undefined) + const result = await createConnectionInvitation(agent) + if (result) { + setRecordId(result.record.id) + setInvitation(result.record.outOfBandInvitation.toUrl({ domain })) + // eslint-disable-next-line no-console + console.error(result.invitationUrl) + } + }, []) + + const handleEdit = () => { + navigation.navigate(Screens.NameWallet) + } + + useEffect(() => { + createInvitation() + }, []) + + const record = useConnectionByOutOfBandId(recordId || '') + + useEffect(() => { + if (record?.state === DidExchangeState.Completed) { + navigation.getParent()?.navigate(Stacks.ConnectionStack, { + screen: Screens.Connection, + params: { connectionId: record.id }, + }) + } + }, [record]) + + return ( + + + + + {!invitation && } + {invitation && } + + + + + {store.preferences.walletName} + + + + + + {t('Connection.ShareQR')} + + + + + ) +} + +export default NewQRView diff --git a/packages/legacy/core/App/screens/Scan.tsx b/packages/legacy/core/App/screens/Scan.tsx index 6d4c4063..b0de4e2f 100644 --- a/packages/legacy/core/App/screens/Scan.tsx +++ b/packages/legacy/core/App/screens/Scan.tsx @@ -159,7 +159,7 @@ const Scan: React.FC = ({ navigation, route }) => { return } - if (!store.preferences.useConnectionInviterCapability) { + if (store.preferences.useConnectionInviterCapability) { return ( => { // const [pool] = pools.filter((p) => p.indyNamespace === indyNamespace) const [pool] = pools From 64eb7238f638c285718956c1536e44d4a202ef0d Mon Sep 17 00:00:00 2001 From: Bryce McMath <32586431+bryce-mcmath@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:39:31 -0800 Subject: [PATCH 07/77] feat: implement optional credential help actions (#1081) Signed-off-by: Bryce McMath --- .../App/components/misc/CredentialCard.tsx | 3 + .../App/components/misc/CredentialCard11.tsx | 51 ++++++---- .../misc/CredentialCard11ActionFooter.tsx | 53 +++++++++++ .../core/App/contexts/configuration.tsx | 2 + .../legacy/core/App/localization/en/index.ts | 1 + .../legacy/core/App/localization/fr/index.ts | 1 + .../core/App/localization/pt-br/index.ts | 1 + .../legacy/core/App/screens/ProofRequest.tsx | 1 + .../core/App/types/get-credential-help.ts | 12 +++ packages/legacy/core/App/types/proof-items.ts | 2 + packages/legacy/core/App/utils/helpers.ts | 30 ++++-- .../components/CredentialCard11.test.tsx | 93 +++++++++++++++++++ .../CredentialCard11ActionFooter.test.tsx | 13 +++ ...CredentialCard11ActionFooter.test.tsx.snap | 83 +++++++++++++++++ .../__tests__/screens/ProofRequest.test.tsx | 4 +- 15 files changed, 323 insertions(+), 27 deletions(-) create mode 100644 packages/legacy/core/App/components/misc/CredentialCard11ActionFooter.tsx create mode 100644 packages/legacy/core/App/types/get-credential-help.ts create mode 100644 packages/legacy/core/__tests__/components/CredentialCard11.test.tsx create mode 100644 packages/legacy/core/__tests__/components/CredentialCard11ActionFooter.test.tsx create mode 100644 packages/legacy/core/__tests__/components/__snapshots__/CredentialCard11ActionFooter.test.tsx.snap diff --git a/packages/legacy/core/App/components/misc/CredentialCard.tsx b/packages/legacy/core/App/components/misc/CredentialCard.tsx index 364b855e..51ceed42 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard.tsx @@ -14,6 +14,7 @@ interface CredentialCardProps { credential?: CredentialExchangeRecord credDefId?: string schemaId?: string + proofCredDefId?: string credName?: string onPress?: GenericFn style?: ViewStyle @@ -29,6 +30,7 @@ const CredentialCard: React.FC = ({ credential, credDefId, schemaId, + proofCredDefId, proof, displayItems, credName, @@ -53,6 +55,7 @@ const CredentialCard: React.FC = ({ credName={credName} credDefId={credDefId} schemaId={schemaId} + proofCredDefId={proofCredDefId} credential={credential} handleAltCredChange={handleAltCredChange} hasAltCredentials={hasAltCredentials} diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index b9771993..3d141b11 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -1,6 +1,8 @@ import { CredentialExchangeRecord } from '@aries-framework/core' import { BrandingOverlay } from '@hyperledger/aries-oca' import { Attribute, CredentialOverlay, Predicate } from '@hyperledger/aries-oca/build/legacy' +import { useNavigation } from '@react-navigation/core' +import { StackNavigationProp } from '@react-navigation/stack' import startCase from 'lodash.startcase' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -11,11 +13,13 @@ import Icon from 'react-native-vector-icons/MaterialIcons' import { useConfiguration } from '../../contexts/configuration' import { useTheme } from '../../contexts/theme' import { GenericFn } from '../../types/fn' +import { NotificationStackParams, Screens } from '../../types/navigators' import { credentialTextColor, getCredentialIdentifiers, toImageSource } from '../../utils/credential' import { formatIfDate, getCredentialConnectionLabel, isDataUrl, pTypeToText } from '../../utils/helpers' import { testIdWithKey } from '../../utils/testable' import CardWatermark from './CardWatermark' +import CredentialActionFooter from './CredentialCard11ActionFooter' interface CredentialCard11Props { credential?: CredentialExchangeRecord @@ -29,6 +33,7 @@ interface CredentialCard11Props { credName?: string credDefId?: string schemaId?: string + proofCredDefId?: string proof?: boolean hasAltCredentials?: boolean handleAltCredChange?: () => void @@ -74,6 +79,7 @@ const CredentialCard11: React.FC = ({ credName, credDefId, schemaId, + proofCredDefId, proof, hasAltCredentials, handleAltCredChange, @@ -84,7 +90,7 @@ const CredentialCard11: React.FC = ({ const logoHeight = width * 0.12 const { i18n, t } = useTranslation() const { ColorPallet, TextTheme, ListItems } = useTheme() - const { OCABundleResolver } = useConfiguration() + const { OCABundleResolver, getCredentialHelpDictionary } = useConfiguration() const [isRevoked, setIsRevoked] = useState(credential?.revocationNotification !== undefined) const [flaggedAttributes, setFlaggedAttributes] = useState() const [allPI, setAllPI] = useState() @@ -92,8 +98,10 @@ const CredentialCard11: React.FC = ({ const [isProofRevoked, setIsProofRevoked] = useState( credential?.revocationNotification !== undefined && !!proof ) - + const [helpAction, setHelpAction] = useState() const [overlay, setOverlay] = useState>({}) + // below navigation only to be used from proof request screen + const navigation = useNavigation>() const primaryField = overlay?.presentationFields?.find( (field) => field.name === overlay?.brandingOverlay?.primaryAttribute @@ -282,6 +290,16 @@ const CredentialCard11: React.FC = ({ setIsProofRevoked(credential?.revocationNotification !== undefined && !!proof) }, [credential?.revocationNotification]) + useEffect(() => { + getCredentialHelpDictionary?.some((entry) => { + if (proofCredDefId && entry.credDefIds.includes(proofCredDefId)) { + setHelpAction(() => () => { + entry.action(navigation) + }) + } + }) + }, [proofCredDefId]) + const CredentialCardLogo: React.FC = () => { return ( @@ -461,23 +479,18 @@ const CredentialCard11: React.FC = ({ return renderCardAttribute(item as Attribute & Predicate) }} ListFooterComponent={ - hasAltCredentials ? ( - - - - - {t('ProofRequest.ChangeCredential')} - - - - + hasAltCredentials && handleAltCredChange ? ( + + ) : error && helpAction ? ( + ) : null } /> diff --git a/packages/legacy/core/App/components/misc/CredentialCard11ActionFooter.tsx b/packages/legacy/core/App/components/misc/CredentialCard11ActionFooter.tsx new file mode 100644 index 00000000..5e8f48d2 --- /dev/null +++ b/packages/legacy/core/App/components/misc/CredentialCard11ActionFooter.tsx @@ -0,0 +1,53 @@ +import { StyleSheet, Text, View } from 'react-native' +import { TouchableOpacity } from 'react-native-gesture-handler' +import Icon from 'react-native-vector-icons/MaterialIcons' + +import { useTheme } from '../../contexts/theme' +import { GenericFn } from '../../types/fn' +import { testIdWithKey } from '../../utils/testable' + +interface CredentialActionFooterProps { + onPress: GenericFn + text: string + testID: string +} + +const CredentialActionFooter = ({ onPress, text, testID }: CredentialActionFooterProps) => { + const { ColorPallet, TextTheme } = useTheme() + const styles = StyleSheet.create({ + seperator: { + width: '100%', + height: 2, + marginVertical: 10, + backgroundColor: ColorPallet.grayscale.lightGrey, + }, + touchable: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + credActionText: { + fontSize: 20, + fontWeight: TextTheme.bold.fontWeight, + color: ColorPallet.brand.link, + }, + }) + + return ( + + + + + {text} + + + + + ) +} + +export default CredentialActionFooter diff --git a/packages/legacy/core/App/contexts/configuration.tsx b/packages/legacy/core/App/contexts/configuration.tsx index 03036567..e74a4f83 100644 --- a/packages/legacy/core/App/contexts/configuration.tsx +++ b/packages/legacy/core/App/contexts/configuration.tsx @@ -8,6 +8,7 @@ import { EmptyListProps } from '../components/misc/EmptyList' import { RecordProps } from '../components/record/Record' import { Locales } from '../localization' import OnboardingPages from '../screens/OnboardingPages' +import { GetCredentialHelpEntry } from '../types/get-credential-help' import { ConnectStackParams } from '../types/navigators' import { PINSecurityParams } from '../types/security' import { SettingSection } from '../types/settings' @@ -57,6 +58,7 @@ export interface ConfigurationContext { showScanButton?: boolean globalScreenOptions?: StackNavigationOptions showDetailsInfo?: boolean + getCredentialHelpDictionary?: GetCredentialHelpEntry[] } export const ConfigurationContext = createContext(null as unknown as ConfigurationContext) diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index caf9da17..4b43a46e 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -431,6 +431,7 @@ const translation = { "RequestProcessing": "Just a moment...", "OfferDelay": "Offer delay", "ChangeCredential": "Change credential", + "GetThisCredential": "Get this credential", "RejectThisProof?": "Reject this Proof Request?", "DeclineThisProof?": "Decline this Proof Request?", "MultipleCredentials": "You have multiple credentials to choose from:", diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index c4b73dc1..3189c8b9 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -428,6 +428,7 @@ const translation = { "RequestProcessing": "Juste un instant...", "OfferDelay": "Retard de l'offre", "ChangeCredential": "Change credential (FR)", + "GetThisCredential": "Get this credential (FR)", "RejectThisProof?": "Rejeter cette preuve?", "AcceptingProof": "Acceptation de la preuve", "MultipleCredentials": "You have multiple credentials to choose from: (FR)", diff --git a/packages/legacy/core/App/localization/pt-br/index.ts b/packages/legacy/core/App/localization/pt-br/index.ts index 1b2105e6..35b78851 100644 --- a/packages/legacy/core/App/localization/pt-br/index.ts +++ b/packages/legacy/core/App/localization/pt-br/index.ts @@ -412,6 +412,7 @@ const translation = { "RequestProcessing": "Só um momento...", "OfferDelay": "Atrasar oferta", "ChangeCredential": "Escolher credencial", + "GetThisCredential": "Get this credential (PT-BR)", "RejectThisProof?": "Rejeitar esta Requisição de Prova?", "DeclineThisProof?": "Recusar esta Requisição de Prova?", "AcceptingProof": "Aceitando Prova", diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 117debc6..1d706957 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -543,6 +543,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { credential={item.credExchangeRecord} credDefId={item.credDefId} schemaId={item.schemaId} + proofCredDefId={item.proofCredDefId} displayItems={[ ...(item.attributes ?? []), ...evaluatePredicates(getCredentialsFields(), item.credId)(item), diff --git a/packages/legacy/core/App/types/get-credential-help.ts b/packages/legacy/core/App/types/get-credential-help.ts new file mode 100644 index 00000000..2e87ceb4 --- /dev/null +++ b/packages/legacy/core/App/types/get-credential-help.ts @@ -0,0 +1,12 @@ +import { StackNavigationProp } from '@react-navigation/stack' + +import { NotificationStackParams, Screens } from './navigators' + +export type GetCredentialHelpAction = ( + navigation: StackNavigationProp +) => void + +export interface GetCredentialHelpEntry { + credDefIds: string[] + action: GetCredentialHelpAction +} diff --git a/packages/legacy/core/App/types/proof-items.ts b/packages/legacy/core/App/types/proof-items.ts index 99558263..79ed4122 100644 --- a/packages/legacy/core/App/types/proof-items.ts +++ b/packages/legacy/core/App/types/proof-items.ts @@ -6,6 +6,7 @@ export interface ProofCredentialAttributes { credExchangeRecord?: CredentialExchangeRecord credId: string credDefId?: string + proofCredDefId?: string schemaId?: string credName: string attributes?: Attribute[] @@ -16,6 +17,7 @@ export interface ProofCredentialPredicates { credExchangeRecord?: CredentialExchangeRecord credId: string credDefId?: string + proofCredDefId?: string schemaId?: string credName: string predicates?: Predicate[] diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 79932cde..127b04f5 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -320,6 +320,10 @@ const credNameFromRestriction = (queries?: AnonCredsProofRequestRestriction[]): } } +const credDefIdFromRestrictions = (queries?: AnonCredsProofRequestRestriction[]): string => { + return queries?.filter((rstr) => rstr.cred_def_id)[0]?.cred_def_id ?? '' +} + export const isDataUrl = (value: string | number | null) => { return typeof value === 'string' && value.startsWith('data:image/') } @@ -402,7 +406,10 @@ export const evaluatePredicates = } const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { - const credName = credNameFromRestriction(attrReq.restrictions) + const { name, names, restrictions } = attrReq + const credName = credNameFromRestriction(restrictions) + const proofCredDefId = credDefIdFromRestrictions(restrictions) + //there is no credId in this context so use credName as a placeholder const processedAttributes: ProofCredentialAttributes = { credExchangeRecord: undefined, @@ -411,9 +418,10 @@ const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { schemaId: undefined, credDefId: undefined, credName: credName, + proofCredDefId, attributes: [] as Attribute[], } - const { name, names } = attrReq + for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { processedAttributes.attributes?.push( new Attribute({ @@ -450,7 +458,9 @@ export const processProofAttributes = ( const credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) - const { name, names, non_revoked } = requestedProofAttributes[key] + const { name, names, non_revoked, restrictions } = requestedProofAttributes[key] + + const proofCredDefId = credDefIdFromRestrictions(restrictions) if (credentialList.length <= 0) { const missingAttributes = addMissingDisplayAttributes(requestedProofAttributes[key]) @@ -489,6 +499,7 @@ export const processProofAttributes = ( credId: credential?.credentialId, schemaId: credential?.credentialInfo?.schemaId, credDefId: credential?.credentialInfo?.credentialDefinitionId, + proofCredDefId, credName, attributes: [], } @@ -535,7 +546,11 @@ export const mergeAttributesAndPredicates = ( } const addMissingDisplayPredicates = (predReq: AnonCredsRequestedPredicate) => { - const credName = credNameFromRestriction(predReq.restrictions) + const { name, p_type: pType, p_value: pValue, restrictions } = predReq + + const credName = credNameFromRestriction(restrictions) + const proofCredDefId = credDefIdFromRestrictions(restrictions) + //there is no credId in this context so use credName as a placeholder const processedPredicates: ProofCredentialPredicates = { credExchangeRecord: undefined, @@ -543,10 +558,10 @@ const addMissingDisplayPredicates = (predReq: AnonCredsRequestedPredicate) => { credId: credName, schemaId: undefined, credDefId: undefined, + proofCredDefId, credName: credName, predicates: [] as Predicate[], } - const { name, p_type: pType, p_value: pValue } = predReq processedPredicates.predicates?.push( new Predicate({ @@ -582,7 +597,9 @@ export const processProofPredicates = ( .map((cred) => cred.credentialId) const credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) - const { name, p_type: pType, p_value: pValue, non_revoked } = requestedProofPredicates[key] + const { name, p_type: pType, p_value: pValue, non_revoked, restrictions } = requestedProofPredicates[key] + const proofCredDefId = credDefIdFromRestrictions(restrictions) + if (credentialList.length <= 0) { const missingPredicates = addMissingDisplayPredicates(requestedProofPredicates[key]) if (!processedPredicates[missingPredicates.credName]) { @@ -622,6 +639,7 @@ export const processProofPredicates = ( credId: credential.credentialId, schemaId, credDefId: credentialDefinitionId, + proofCredDefId, credName: credName, predicates: [], } diff --git a/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx b/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx new file mode 100644 index 00000000..3eb216f2 --- /dev/null +++ b/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx @@ -0,0 +1,93 @@ +import { CredentialExchangeRecord } from '@aries-framework/core' +import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock' +import '@testing-library/jest-native/extend-expect' +import { fireEvent, render } from '@testing-library/react-native' +import fs from 'fs' +import path from 'path' +import React from 'react' + +import CredentialCard11 from '../../App/components/misc/CredentialCard11' +import { ConfigurationContext } from '../../App/contexts/configuration' +import { testIdWithKey } from '../../App/utils/testable' +import configurationContext from '../contexts/configuration' + +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter') +jest.mock('@react-native-community/netinfo', () => mockRNCNetInfo) +jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') +jest.mock('@react-navigation/core', () => { + return require('../../__mocks__/custom/@react-navigation/core') +}) +jest.mock('@react-navigation/native', () => { + return require('../../__mocks__/custom/@react-navigation/native') +}) + +jest.mock('@hyperledger/anoncreds-react-native', () => ({})) +jest.mock('@hyperledger/aries-askar-react-native', () => ({})) +jest.mock('@hyperledger/indy-vdr-react-native', () => ({})) +jest.useFakeTimers({ legacyFakeTimers: true }) +jest.spyOn(global, 'setTimeout') + +const credentialPath = path.join(__dirname, '../fixtures/degree-credential.json') +const credential = JSON.parse(fs.readFileSync(credentialPath, 'utf8')) + +describe('CredentialCard11 component', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('In proof form', () => { + test('With existing credential and alt credentials', async () => { + const credentialRecord = new CredentialExchangeRecord(credential) + credentialRecord.credentials.push({ + credentialRecordType: 'anoncreds', + credentialRecordId: '', + }) + credentialRecord.createdAt = new Date(credentialRecord.createdAt) + + const handleAltCredChange = jest.fn() + + const { findByTestId } = render( + + + + ) + + const changeCredentialButton = await findByTestId(testIdWithKey('ChangeCredential')) + + expect(changeCredentialButton).toBeTruthy() + + fireEvent(changeCredentialButton, 'press') + expect(handleAltCredChange).toBeCalled() + }) + + test('Missing credential with help action', async () => { + const helpAction = jest.fn() + + const { findByTestId } = render( + + + + ) + + const getThisCredentialButton = await findByTestId(testIdWithKey('GetThisCredential')) + + expect(getThisCredentialButton).toBeTruthy() + + fireEvent(getThisCredentialButton, 'press') + expect(helpAction).toBeCalled() + }) + }) +}) diff --git a/packages/legacy/core/__tests__/components/CredentialCard11ActionFooter.test.tsx b/packages/legacy/core/__tests__/components/CredentialCard11ActionFooter.test.tsx new file mode 100644 index 00000000..b8f89591 --- /dev/null +++ b/packages/legacy/core/__tests__/components/CredentialCard11ActionFooter.test.tsx @@ -0,0 +1,13 @@ +import '@testing-library/jest-native/extend-expect' +import { render } from '@testing-library/react-native' +import React from 'react' + +import CredentialActionFooter from '../../App/components/misc/CredentialCard11ActionFooter' + +describe('CredentialCard11ActionFooter component', () => { + test('Matches snapshot', async () => { + const tree = render() + + expect(tree).toMatchSnapshot() + }) +}) diff --git a/packages/legacy/core/__tests__/components/__snapshots__/CredentialCard11ActionFooter.test.tsx.snap b/packages/legacy/core/__tests__/components/__snapshots__/CredentialCard11ActionFooter.test.tsx.snap new file mode 100644 index 00000000..78b96002 --- /dev/null +++ b/packages/legacy/core/__tests__/components/__snapshots__/CredentialCard11ActionFooter.test.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CredentialCard11ActionFooter component Matches snapshot 1`] = ` + + + + + + + sample + + +  + + + + + +`; diff --git a/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx b/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx index e8319ffc..57bd51f2 100644 --- a/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx +++ b/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx @@ -12,10 +12,10 @@ import { ConfigurationContext } from '../../App/contexts/configuration' import { NetworkContext, NetworkProvider } from '../../App/contexts/network' import ProofRequest from '../../App/screens/ProofRequest' import { testIdWithKey } from '../../App/utils/testable' +import { useTranslation } from '../../__mocks__/react-i18next' import configurationContext from '../contexts/configuration' import networkContext from '../contexts/network' import timeTravel from '../helpers/timetravel' -import { useTranslation } from '../../__mocks__/react-i18next' jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter') jest.mock('@react-native-community/netinfo', () => mockRNCNetInfo) @@ -406,7 +406,7 @@ describe('displays a proof request screen', () => { Promise.resolve() }) const changeCred = getByText('ProofRequest.ChangeCredential', { exact: false }) - const changeCredButton = getByTestId(testIdWithKey('changeCredential')) + const changeCredButton = getByTestId(testIdWithKey('ChangeCredential')) const contact = getByText('ContactDetails.AContact', { exact: false }) const missingInfo = queryByText('ProofRequest.IsRequestingSomethingYouDontHaveAvailable', { exact: false }) const missingClaim = queryByText('ProofRequest.NotAvailableInYourWallet', { exact: false }) From afb89fe9aa965dcc07d017d0828f374b7eaf15ee Mon Sep 17 00:00:00 2001 From: NidhishVyas Date: Mon, 5 Feb 2024 21:12:24 -0500 Subject: [PATCH 08/77] theme changed --- .vscode/settings.json | 3 + .../legacy/core/App/assets/img/FHWA-Logo.svg | 1 + packages/legacy/core/App/assets/img/logo.png | Bin 0 -> 47710 bytes .../components/inputs/LimitedTextInput.tsx | 4 +- .../core/App/components/misc/QRRenderer.tsx | 6 +- .../App/configs/ledgers/indy/ledgers.json | 2 +- packages/legacy/core/App/constants.ts | 2 +- .../core/App/screens/CredentialOffer.tsx | 1 + packages/legacy/core/App/theme.ts | 71 +++++++++--------- 9 files changed, 50 insertions(+), 40 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 packages/legacy/core/App/assets/img/FHWA-Logo.svg create mode 100644 packages/legacy/core/App/assets/img/logo.png diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e0f15db2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/packages/legacy/core/App/assets/img/FHWA-Logo.svg b/packages/legacy/core/App/assets/img/FHWA-Logo.svg new file mode 100644 index 00000000..2cfba0b8 --- /dev/null +++ b/packages/legacy/core/App/assets/img/FHWA-Logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/legacy/core/App/assets/img/logo.png b/packages/legacy/core/App/assets/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..db57f0ea4cc61b66467b1cc17abf162adf8270e2 GIT binary patch literal 47710 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 = ({ label, limit, handleChangeText, ...textInputProps }) => { const [focused, setFocused] = useState(false) const [characterCount, setCharacterCount] = useState(0) - const { Inputs, TextTheme } = useTheme() + const { Inputs, TextTheme, ColorPallet } = useTheme() const styles = StyleSheet.create({ container: { marginVertical: 10, @@ -23,6 +23,8 @@ const LimitedTextInput: React.FC = ({ label, limit, handleChangeText, ... }, textInput: { ...Inputs.textInput, + color: TextTheme.normal.color, + borderColor: ColorPallet.brand.primary, }, limitCounter: { color: TextTheme.normal.color, diff --git a/packages/legacy/core/App/components/misc/QRRenderer.tsx b/packages/legacy/core/App/components/misc/QRRenderer.tsx index 03c85384..9b8bbd46 100644 --- a/packages/legacy/core/App/components/misc/QRRenderer.tsx +++ b/packages/legacy/core/App/components/misc/QRRenderer.tsx @@ -48,8 +48,10 @@ const QRRenderer: React.FC = ({ value, onError, size }) => { value={value} size={qrSize} onError={handleQRCodeGenerationError} - backgroundColor="black" - color="white" + // backgroundColor="black" + // color="white" + // logo={logo} + // logoSize={150} /> {isInvalidQR && {t('QRRender.GenerationError')}} diff --git a/packages/legacy/core/App/configs/ledgers/indy/ledgers.json b/packages/legacy/core/App/configs/ledgers/indy/ledgers.json index 956e108f..3ce1dc3e 100644 --- a/packages/legacy/core/App/configs/ledgers/indy/ledgers.json +++ b/packages/legacy/core/App/configs/ledgers/indy/ledgers.json @@ -4,6 +4,6 @@ "indyNamespace": "bcovrin:test", "isProduction": false, "connectOnStartup": true, - "genesisTransactions": "{\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node1\",\"blskey\":\"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba\",\"blskey_pop\":\"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1\",\"client_ip\":\"3.145.42.89\",\"client_port\":9702,\"node_ip\":\"3.145.42.89\",\"node_port\":9701,\"services\":[\"VALIDATOR\"]},\"dest\":\"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv\"},\"metadata\":{\"from\":\"Th7MpTaRZVRYnPiabds81Y\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":1,\"txnId\":\"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62\"},\"ver\":\"1\"}\n {\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node2\",\"blskey\":\"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk\",\"blskey_pop\":\"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5\",\"client_ip\":\"3.145.42.89\",\"client_port\":9704,\"node_ip\":\"3.145.42.89\",\"node_port\":9703,\"services\":[\"VALIDATOR\"]},\"dest\":\"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb\"},\"metadata\":{\"from\":\"EbP4aYNeTHL6q385GuVpRV\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":2,\"txnId\":\"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc\"},\"ver\":\"1\"}\n {\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node3\",\"blskey\":\"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5\",\"blskey_pop\":\"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh\",\"client_ip\":\"3.145.42.89\",\"client_port\":9706,\"node_ip\":\"3.145.42.89\",\"node_port\":9705,\"services\":[\"VALIDATOR\"]},\"dest\":\"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya\"},\"metadata\":{\"from\":\"4cU41vWW82ArfxJxHkzXPG\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":3,\"txnId\":\"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4\"},\"ver\":\"1\"}\n {\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node4\",\"blskey\":\"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw\",\"blskey_pop\":\"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP\",\"client_ip\":\"3.145.42.89\",\"client_port\":9708,\"node_ip\":\"3.145.42.89\",\"node_port\":9707,\"services\":[\"VALIDATOR\"]},\"dest\":\"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA\"},\"metadata\":{\"from\":\"TWwCRQRZ2ZHMJFn9TzLp7W\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":4,\"txnId\":\"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008\"},\"ver\":\"1\"}" + "genesisTransactions": "{\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node1\",\"blskey\":\"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba\",\"blskey_pop\":\"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1\",\"client_ip\":\"138.197.138.255\",\"client_port\":9702,\"node_ip\":\"138.197.138.255\",\"node_port\":9701,\"services\":[\"VALIDATOR\"]},\"dest\":\"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv\"},\"metadata\":{\"from\":\"Th7MpTaRZVRYnPiabds81Y\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":1,\"txnId\":\"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62\"},\"ver\":\"1\"}\n{\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node2\",\"blskey\":\"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk\",\"blskey_pop\":\"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5\",\"client_ip\":\"138.197.138.255\",\"client_port\":9704,\"node_ip\":\"138.197.138.255\",\"node_port\":9703,\"services\":[\"VALIDATOR\"]},\"dest\":\"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb\"},\"metadata\":{\"from\":\"EbP4aYNeTHL6q385GuVpRV\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":2,\"txnId\":\"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc\"},\"ver\":\"1\"}\n{\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node3\",\"blskey\":\"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5\",\"blskey_pop\":\"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh\",\"client_ip\":\"138.197.138.255\",\"client_port\":9706,\"node_ip\":\"138.197.138.255\",\"node_port\":9705,\"services\":[\"VALIDATOR\"]},\"dest\":\"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya\"},\"metadata\":{\"from\":\"4cU41vWW82ArfxJxHkzXPG\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":3,\"txnId\":\"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4\"},\"ver\":\"1\"}\n{\"reqSignature\":{},\"txn\":{\"data\":{\"data\":{\"alias\":\"Node4\",\"blskey\":\"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw\",\"blskey_pop\":\"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP\",\"client_ip\":\"138.197.138.255\",\"client_port\":9708,\"node_ip\":\"138.197.138.255\",\"node_port\":9707,\"services\":[\"VALIDATOR\"]},\"dest\":\"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA\"},\"metadata\":{\"from\":\"TWwCRQRZ2ZHMJFn9TzLp7W\"},\"type\":\"0\"},\"txnMetadata\":{\"seqNo\":4,\"txnId\":\"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008\"},\"ver\":\"1\"}" } ] diff --git a/packages/legacy/core/App/constants.ts b/packages/legacy/core/App/constants.ts index b0cd5947..14e65c23 100644 --- a/packages/legacy/core/App/constants.ts +++ b/packages/legacy/core/App/constants.ts @@ -74,7 +74,7 @@ export const PINRules: PINValidationRules = { no_cross_pattern: false, } -export const domain = 'https://2aba-18-221-246-68.ngrok-free.app' +export const domain = 'https://fc83-18-116-10-41.ngrok-free.app' export const tourMargin = 25 diff --git a/packages/legacy/core/App/screens/CredentialOffer.tsx b/packages/legacy/core/App/screens/CredentialOffer.tsx index 25ad0c0b..af2562e9 100644 --- a/packages/legacy/core/App/screens/CredentialOffer.tsx +++ b/packages/legacy/core/App/screens/CredentialOffer.tsx @@ -114,6 +114,7 @@ const CredentialOffer: React.FC = ({ navigation, route }) } const updateCredentialPreview = async () => { + // eslint-disable-next-line no-unsafe-optional-chaining const { ...formatData } = await agent?.credentials.getFormatData(credential.id) const { offer, offerAttributes } = formatData const offerData = offer?.anoncreds ?? offer?.indy diff --git a/packages/legacy/core/App/theme.ts b/packages/legacy/core/App/theme.ts index 786cc107..b33124ae 100644 --- a/packages/legacy/core/App/theme.ts +++ b/packages/legacy/core/App/theme.ts @@ -186,28 +186,28 @@ const GrayscaleColors: IGrayscaleColors = { } const BrandColors: IBrandColors = { - primary: '#42803E', - primaryDisabled: `rgba(53, 130, 63, ${lightOpacity})`, + primary: '#1C6DA5', + primaryDisabled: `rgba(28,109,165, ${lightOpacity})`, secondary: '#FFFFFFFF', - secondaryDisabled: `rgba(53, 130, 63, ${heavyOpacity})`, - primaryLight: `rgba(53, 130, 63, ${lightOpacity})`, + secondaryDisabled: `rgba(28,109,165, ${heavyOpacity})`, + primaryLight: `rgba(28,109,165, ${lightOpacity})`, highlight: '#FCBA19', - primaryBackground: '#000000', - secondaryBackground: '#313132', - modalPrimary: '#42803E', + primaryBackground: '#F5F5F5', + secondaryBackground: '#F5F5F5', + modalPrimary: '#1C6DA5', modalSecondary: '#FFFFFFFF', - modalPrimaryBackground: '#000000', - modalSecondaryBackground: '#313132', - modalIcon: GrayscaleColors.white, - unorderedList: GrayscaleColors.white, - unorderedListModal: GrayscaleColors.white, - link: GrayscaleColors.white, - text: GrayscaleColors.white, - icon: GrayscaleColors.white, + modalPrimaryBackground: '#F5F5F5', + modalSecondaryBackground: '#F5F5F5', + modalIcon: GrayscaleColors.black, + unorderedList: GrayscaleColors.black, + unorderedListModal: GrayscaleColors.black, + link: GrayscaleColors.black, + text: GrayscaleColors.black, + icon: GrayscaleColors.black, headerIcon: GrayscaleColors.white, headerText: GrayscaleColors.white, buttonText: GrayscaleColors.white, - tabBarInactive: GrayscaleColors.white, + tabBarInactive: GrayscaleColors.black, } const SemanticColors: ISemanticColors = { @@ -217,22 +217,22 @@ const SemanticColors: ISemanticColors = { } const NotificationColors: INotificationColors = { - success: '#313132', + success: '#ffffff', successBorder: '#2E8540', successIcon: '#2E8540', - successText: '#FFFFFF', - info: '#313132', + successText: '#000000', + info: '#ffffff', infoBorder: '#0099FF', infoIcon: '#0099FF', - infoText: '#FFFFFF', - warn: '#313132', + infoText: '#000000', + warn: '#ffffff', warnBorder: '#FCBA19', warnIcon: '#FCBA19', - warnText: '#FFFFFF', - error: '#313132', + warnText: '#000000', + error: '#ffffff', errorBorder: '#D8292F', errorIcon: '#D8292F', - errorText: '#FFFFFF', + errorText: '#000000', popupOverlay: `rgba(0, 0, 0, ${mediumOpacity})`, } @@ -313,12 +313,12 @@ export const TextTheme: ITextTheme = { modalNormal: { fontSize: 18, fontWeight: 'normal', - color: ColorPallet.grayscale.white, + color: ColorPallet.grayscale.black, }, modalTitle: { fontSize: 24, fontWeight: 'bold', - color: ColorPallet.grayscale.white, + color: ColorPallet.grayscale.black, }, modalHeadingOne: { fontSize: 38, @@ -351,7 +351,7 @@ export const Inputs: IInputs = StyleSheet.create({ borderRadius, fontSize: 16, backgroundColor: ColorPallet.brand.primaryBackground, - color: ColorPallet.notification.infoText, + color: '#fff', borderWidth: 2, borderColor: ColorPallet.brand.secondary, }, @@ -646,7 +646,7 @@ export const ChatTheme = { alignSelf: 'flex-end', }, leftBubble: { - backgroundColor: ColorPallet.brand.secondaryBackground, + backgroundColor: ColorPallet.brand.secondary, borderRadius: 4, padding: 16, marginLeft: 16, @@ -658,30 +658,30 @@ export const ChatTheme = { marginRight: 16, }, timeStyleLeft: { - color: ColorPallet.grayscale.lightGrey, + color: ColorPallet.grayscale.darkGrey, fontSize: 12, marginTop: 8, }, timeStyleRight: { - color: ColorPallet.grayscale.lightGrey, + color: ColorPallet.grayscale.darkGrey, fontSize: 12, marginTop: 8, }, leftText: { - color: ColorPallet.brand.secondary, + color: ColorPallet.grayscale.black, fontSize: TextTheme.normal.fontSize, }, leftTextHighlighted: { ...TextTheme.bold, - color: ColorPallet.brand.secondary, + color: ColorPallet.grayscale.black, }, rightText: { - color: ColorPallet.brand.secondary, + color: ColorPallet.grayscale.black, fontSize: TextTheme.normal.fontSize, }, rightTextHighlighted: { ...TextTheme.bold, - color: ColorPallet.brand.secondary, + color: ColorPallet.grayscale.black, }, inputToolbar: { backgroundColor: ColorPallet.brand.secondary, @@ -689,6 +689,7 @@ export const ChatTheme = { borderRadius: 10, }, inputText: { + color: ColorPallet.grayscale.black, lineHeight: undefined, fontWeight: '500', fontSize: TextTheme.normal.fontSize, @@ -795,7 +796,7 @@ const PINEnterTheme = { const PINInputTheme = { cell: { backgroundColor: ColorPallet.brand.secondaryBackground, - borderColor: ColorPallet.brand.secondary, + borderColor: ColorPallet.brand.primary, borderWidth: 1, }, focussedCell: { From 01acd3a14bc23f54daa43f8f2d6fdfa80328ec85 Mon Sep 17 00:00:00 2001 From: NidhishVyas Date: Tue, 6 Feb 2024 13:35:29 -0500 Subject: [PATCH 09/77] testing --- .../legacy/core/App/localization/en/index.ts | 1 + .../legacy/core/App/navigators/ContactStack.tsx | 6 ++++++ .../legacy/core/App/screens/ContactDetails.tsx | 17 +++++++++++++++++ .../legacy/core/App/screens/ProofSelection.tsx | 7 +++++++ packages/legacy/core/App/screens/QRCodeGen.tsx | 4 ++-- packages/legacy/core/App/types/navigators.ts | 2 ++ packages/legacy/core/App/utils/helpers.ts | 1 + 7 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 packages/legacy/core/App/screens/ProofSelection.tsx diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index 56558b4e..e0df8415 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -560,6 +560,7 @@ const translation = { "ProofRequesting": 'Proof Requesting', "NameWallet": "Name your wallet", "RenameContact": "Edit Contact Name", + "ProofSelection": "Select for proof selection", }, "Loading": { "TakingTooLong": "This is taking longer than usual. You can return to home or continue waiting.", diff --git a/packages/legacy/core/App/navigators/ContactStack.tsx b/packages/legacy/core/App/navigators/ContactStack.tsx index e47290f9..7ba04faf 100644 --- a/packages/legacy/core/App/navigators/ContactStack.tsx +++ b/packages/legacy/core/App/navigators/ContactStack.tsx @@ -11,6 +11,7 @@ import CredentialOffer from '../screens/CredentialOffer' import ListContacts from '../screens/ListContacts' import ProofDetails from '../screens/ProofDetails' import ProofRequest from '../screens/ProofRequest' +import ProofSelection from '../screens/ProofSelection' import QRCodeGen from '../screens/QRCodeGen' import RenameContact from '../screens/RenameContact' import WhatAreContacts from '../screens/WhatAreContacts' @@ -39,6 +40,11 @@ const ContactStack: React.FC = () => { component={RenameContact} options={{ title: t('Screens.RenameContact') }} /> + diff --git a/packages/legacy/core/App/screens/ContactDetails.tsx b/packages/legacy/core/App/screens/ContactDetails.tsx index b58848b6..97f36571 100644 --- a/packages/legacy/core/App/screens/ContactDetails.tsx +++ b/packages/legacy/core/App/screens/ContactDetails.tsx @@ -13,6 +13,7 @@ import { ToastType } from '../components/toast/BaseToast' import { EventTypes } from '../constants' import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' +import { useConnectionByOutOfBandId } from '../hooks/connections' import { BifoldError } from '../types/error' import { ContactStackParams, Screens, TabStacks } from '../types/navigators' import { ModalUsage } from '../types/remove' @@ -22,6 +23,7 @@ import { testIdWithKey } from '../utils/testable' type ContactDetailsProps = StackScreenProps const ContactDetails: React.FC = ({ route }) => { + // eslint-disable-next-line no-unsafe-optional-chaining const { connectionId } = route?.params const { agent } = useAgent() const { t } = useTranslation() @@ -91,7 +93,13 @@ const ContactDetails: React.FC = ({ route }) => { navigation.navigate(Screens.RenameContact, { connectionId }) } + const handleGoToSelect = () => { + // navigation.navigate(Screens.ProofSelection, { connectionId }) + useConnectionByOutOfBandId() + } + const callGoToRename = useCallback(() => handleGoToRename(), []) + const callGoToProofSelection = useCallback(() => handleGoToSelect(), []) const callOnRemove = useCallback(() => handleOnRemove(), []) const callSubmitRemove = useCallback(() => handleSubmitRemove(), []) const callCancelRemove = useCallback(() => handleCancelRemove(), []) @@ -122,6 +130,15 @@ const ContactDetails: React.FC = ({ route }) => { > {t('Screens.RenameContact')} + + {t('Screens.ProofSelection')} + { + return <> +} + +export default ProofSelection diff --git a/packages/legacy/core/App/screens/QRCodeGen.tsx b/packages/legacy/core/App/screens/QRCodeGen.tsx index 659f74c1..f2e26cda 100644 --- a/packages/legacy/core/App/screens/QRCodeGen.tsx +++ b/packages/legacy/core/App/screens/QRCodeGen.tsx @@ -28,7 +28,7 @@ interface Props extends ConnectProps { enableCameraOnError?: boolean } -const NewQRView: React.FC = ({ navigation }) => { +const QRCodeGen: React.FC = ({ navigation }) => { const { width } = useWindowDimensions() const qrSize = width - 40 const [store] = useStore() @@ -155,4 +155,4 @@ const NewQRView: React.FC = ({ navigation }) => { ) } -export default NewQRView +export default QRCodeGen diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index f1b18371..53247503 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -37,6 +37,7 @@ export enum Screens { ProofDetails = 'Proof Details', NameWallet = 'Name Wallet', RenameContact = 'Rename Contact', + ProofSelection = 'Select Proof', ScanHelp = 'Scan Help', QRCodeGen = 'Generate QR Code', } @@ -97,6 +98,7 @@ export type ContactStackParams = { [Screens.Chat]: { connectionId: string } [Screens.ContactDetails]: { connectionId: string } [Screens.RenameContact]: { connectionId: string } + [Screens.ProofSelection]: { connectionId: string } [Screens.WhatAreContacts]: undefined [Screens.QRCodeGen]: undefined [Screens.CredentialDetails]: { credentialId: string } diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 8ae25eb3..1277d1d4 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -870,6 +870,7 @@ export const createConnectionInvitation = async (agent: Agent | undefined, goalC throw new Error('Could not create new invitation') } const invitationUrl = record.outOfBandInvitation.toUrl({ domain }) + console.log(record) return { record, invitation: record.outOfBandInvitation, From 01777991e6b30e058748b3df9c125e0f95a4782d Mon Sep 17 00:00:00 2001 From: Bryce McMath <32586431+bryce-mcmath@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:42:31 -0800 Subject: [PATCH 10/77] feat: enable help actions by cred def id and schema id (#1084) Signed-off-by: Bryce McMath --- .../App/components/misc/CredentialCard.tsx | 3 + .../App/components/misc/CredentialCard11.tsx | 15 ++++- .../legacy/core/App/screens/ProofRequest.tsx | 1 + .../core/App/types/get-credential-help.ts | 1 + packages/legacy/core/App/types/proof-items.ts | 2 + packages/legacy/core/App/utils/helpers.ts | 23 ++++++- .../components/CredentialCard11.test.tsx | 27 +++++++- .../core/__tests__/utils/helpers.test.ts | 62 +++++++++++++++++++ 8 files changed, 129 insertions(+), 5 deletions(-) diff --git a/packages/legacy/core/App/components/misc/CredentialCard.tsx b/packages/legacy/core/App/components/misc/CredentialCard.tsx index 51ceed42..467b4e87 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard.tsx @@ -15,6 +15,7 @@ interface CredentialCardProps { credDefId?: string schemaId?: string proofCredDefId?: string + proofSchemaId?: string credName?: string onPress?: GenericFn style?: ViewStyle @@ -31,6 +32,7 @@ const CredentialCard: React.FC = ({ credDefId, schemaId, proofCredDefId, + proofSchemaId, proof, displayItems, credName, @@ -56,6 +58,7 @@ const CredentialCard: React.FC = ({ credDefId={credDefId} schemaId={schemaId} proofCredDefId={proofCredDefId} + proofSchemaId={proofSchemaId} credential={credential} handleAltCredChange={handleAltCredChange} hasAltCredentials={hasAltCredentials} diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index 3d141b11..43b88031 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -34,6 +34,7 @@ interface CredentialCard11Props { credDefId?: string schemaId?: string proofCredDefId?: string + proofSchemaId?: string proof?: boolean hasAltCredentials?: boolean handleAltCredChange?: () => void @@ -80,6 +81,7 @@ const CredentialCard11: React.FC = ({ credDefId, schemaId, proofCredDefId, + proofSchemaId, proof, hasAltCredentials, handleAltCredChange, @@ -291,14 +293,25 @@ const CredentialCard11: React.FC = ({ }, [credential?.revocationNotification]) useEffect(() => { + if (!error) { + return + } + getCredentialHelpDictionary?.some((entry) => { if (proofCredDefId && entry.credDefIds.includes(proofCredDefId)) { setHelpAction(() => () => { entry.action(navigation) }) + return true + } + if (proofSchemaId && entry.schemaIds.includes(proofSchemaId)) { + setHelpAction(() => () => { + entry.action(navigation) + }) + return true } }) - }, [proofCredDefId]) + }, [proofCredDefId, proofSchemaId]) const CredentialCardLogo: React.FC = () => { return ( diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 1d706957..29f64812 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -544,6 +544,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { credDefId={item.credDefId} schemaId={item.schemaId} proofCredDefId={item.proofCredDefId} + proofSchemaId={item.proofSchemaId} displayItems={[ ...(item.attributes ?? []), ...evaluatePredicates(getCredentialsFields(), item.credId)(item), diff --git a/packages/legacy/core/App/types/get-credential-help.ts b/packages/legacy/core/App/types/get-credential-help.ts index 2e87ceb4..6ee3b317 100644 --- a/packages/legacy/core/App/types/get-credential-help.ts +++ b/packages/legacy/core/App/types/get-credential-help.ts @@ -8,5 +8,6 @@ export type GetCredentialHelpAction = ( export interface GetCredentialHelpEntry { credDefIds: string[] + schemaIds: string[] action: GetCredentialHelpAction } diff --git a/packages/legacy/core/App/types/proof-items.ts b/packages/legacy/core/App/types/proof-items.ts index 79ed4122..e163d9ba 100644 --- a/packages/legacy/core/App/types/proof-items.ts +++ b/packages/legacy/core/App/types/proof-items.ts @@ -8,6 +8,7 @@ export interface ProofCredentialAttributes { credDefId?: string proofCredDefId?: string schemaId?: string + proofSchemaId?: string credName: string attributes?: Attribute[] } @@ -19,6 +20,7 @@ export interface ProofCredentialPredicates { credDefId?: string proofCredDefId?: string schemaId?: string + proofSchemaId?: string credName: string predicates?: Predicate[] } diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 127b04f5..bfe72c37 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -320,10 +320,23 @@ const credNameFromRestriction = (queries?: AnonCredsProofRequestRestriction[]): } } -const credDefIdFromRestrictions = (queries?: AnonCredsProofRequestRestriction[]): string => { +export const credDefIdFromRestrictions = (queries?: AnonCredsProofRequestRestriction[]): string => { return queries?.filter((rstr) => rstr.cred_def_id)[0]?.cred_def_id ?? '' } +export const schemaIdFromRestrictions = (queries?: AnonCredsProofRequestRestriction[]): string => { + const rstrWithSchemaId = queries?.filter( + (rstr) => rstr.schema_id || (rstr.issuer_did && rstr.schema_name && rstr.schema_version) + )[0] + + // the '2' here is the enum of the transaction type which, for schemas, is always 2 + const schemaId = rstrWithSchemaId + ? rstrWithSchemaId.schema_id || + `${rstrWithSchemaId.issuer_did}:2:${rstrWithSchemaId.schema_name}:${rstrWithSchemaId.schema_version}` + : '' + return schemaId +} + export const isDataUrl = (value: string | number | null) => { return typeof value === 'string' && value.startsWith('data:image/') } @@ -409,6 +422,7 @@ const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { const { name, names, restrictions } = attrReq const credName = credNameFromRestriction(restrictions) const proofCredDefId = credDefIdFromRestrictions(restrictions) + const proofSchemaId = schemaIdFromRestrictions(restrictions) //there is no credId in this context so use credName as a placeholder const processedAttributes: ProofCredentialAttributes = { @@ -419,6 +433,7 @@ const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { credDefId: undefined, credName: credName, proofCredDefId, + proofSchemaId, attributes: [] as Attribute[], } @@ -461,6 +476,7 @@ export const processProofAttributes = ( const { name, names, non_revoked, restrictions } = requestedProofAttributes[key] const proofCredDefId = credDefIdFromRestrictions(restrictions) + const proofSchemaId = schemaIdFromRestrictions(restrictions) if (credentialList.length <= 0) { const missingAttributes = addMissingDisplayAttributes(requestedProofAttributes[key]) @@ -500,6 +516,7 @@ export const processProofAttributes = ( schemaId: credential?.credentialInfo?.schemaId, credDefId: credential?.credentialInfo?.credentialDefinitionId, proofCredDefId, + proofSchemaId, credName, attributes: [], } @@ -550,6 +567,7 @@ const addMissingDisplayPredicates = (predReq: AnonCredsRequestedPredicate) => { const credName = credNameFromRestriction(restrictions) const proofCredDefId = credDefIdFromRestrictions(restrictions) + const proofSchemaId = schemaIdFromRestrictions(restrictions) //there is no credId in this context so use credName as a placeholder const processedPredicates: ProofCredentialPredicates = { @@ -559,6 +577,7 @@ const addMissingDisplayPredicates = (predReq: AnonCredsRequestedPredicate) => { schemaId: undefined, credDefId: undefined, proofCredDefId, + proofSchemaId, credName: credName, predicates: [] as Predicate[], } @@ -599,6 +618,7 @@ export const processProofPredicates = ( const credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) const { name, p_type: pType, p_value: pValue, non_revoked, restrictions } = requestedProofPredicates[key] const proofCredDefId = credDefIdFromRestrictions(restrictions) + const proofSchemaId = schemaIdFromRestrictions(restrictions) if (credentialList.length <= 0) { const missingPredicates = addMissingDisplayPredicates(requestedProofPredicates[key]) @@ -640,6 +660,7 @@ export const processProofPredicates = ( schemaId, credDefId: credentialDefinitionId, proofCredDefId, + proofSchemaId, credName: credName, predicates: [], } diff --git a/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx b/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx index 3eb216f2..5d229378 100644 --- a/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx +++ b/packages/legacy/core/__tests__/components/CredentialCard11.test.tsx @@ -52,7 +52,6 @@ describe('CredentialCard11 component', () => { credential={credentialRecord} credDefId={'cred_def_id'} proof - proofCredDefId={'proof_cred_def'} error={false} handleAltCredChange={handleAltCredChange} hasAltCredentials @@ -68,14 +67,14 @@ describe('CredentialCard11 component', () => { expect(handleAltCredChange).toBeCalled() }) - test('Missing credential with help action', async () => { + test('Missing credential with help action (cred def ID)', async () => { const helpAction = jest.fn() const { findByTestId } = render( @@ -89,5 +88,27 @@ describe('CredentialCard11 component', () => { fireEvent(getThisCredentialButton, 'press') expect(helpAction).toBeCalled() }) + + test('Missing credential with help action (schema ID)', async () => { + const helpAction = jest.fn() + + const { findByTestId } = render( + + + + ) + + const getThisCredentialButton = await findByTestId(testIdWithKey('GetThisCredential')) + + expect(getThisCredentialButton).toBeTruthy() + + fireEvent(getThisCredentialButton, 'press') + expect(helpAction).toBeCalled() + }) }) }) diff --git a/packages/legacy/core/__tests__/utils/helpers.test.ts b/packages/legacy/core/__tests__/utils/helpers.test.ts index 9cf0b991..07b6dbcb 100644 --- a/packages/legacy/core/__tests__/utils/helpers.test.ts +++ b/packages/legacy/core/__tests__/utils/helpers.test.ts @@ -11,6 +11,8 @@ import { getConnectionName, removeExistingInvitationIfRequired, connectFromInvitation, + credDefIdFromRestrictions, + schemaIdFromRestrictions, } from '../../App/utils/helpers' const proofCredentialPath = path.join(__dirname, '../fixtures/proof-credential.json') @@ -197,3 +199,63 @@ describe('getConnectionName', () => { expect(result).toBe('') }) }) + +describe('credDefIdFromRestrictions', () => { + test('With no cred_def_id in any restrictions', async () => { + const expected = '' + const restrictions = [{}, {}] + + const result = credDefIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) + + test('With cred_def_id in restriction', async () => { + const expected = 'cred_def_id' + const restrictions = [{ cred_def_id: expected }] + + const result = credDefIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) + + test('With cred_def_id in later restriction', async () => { + const expected = 'cred_def_id' + const restrictions = [{}, { cred_def_id: expected }] + + const result = credDefIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) +}) + +describe('schemaIdFromRestrictions', () => { + test('With no schema_id or schema ID subproperties in any restrictions', async () => { + const expected = '' + const restrictions = [{}, {}] + + const result = schemaIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) + + test('With schema_id in restriction', async () => { + const expected = 'schema_id' + const restrictions = [{}, { schema_id: expected }] + + const result = schemaIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) + + test('With all subproperties of schema ID in restriction', async () => { + const expected = 'abc123:2:Student Card:1.0' + const restrictions = [{ schema_name: 'Student Card', schema_version: '1.0', issuer_did: 'abc123' }] + + const result = schemaIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) + + test('With only some subproperties of schema ID in restriction', async () => { + const expected = '' + const restrictions = [{ schema_name: 'Student Card', issuer_did: 'abc123' }] + + const result = schemaIdFromRestrictions(restrictions) + expect(result).toBe(expected) + }) +}) From f5f79993f15ad34d258c84a162889bd191c9a13c Mon Sep 17 00:00:00 2001 From: "Jason C. Leach" Date: Mon, 12 Feb 2024 15:51:55 -0800 Subject: [PATCH 11/77] chore: add openwallet project proposal (#1089) Signed-off-by: Jason C. Leach --- docs/openwallet-project-proposal.md | 96 +++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/openwallet-project-proposal.md diff --git a/docs/openwallet-project-proposal.md b/docs/openwallet-project-proposal.md new file mode 100644 index 00000000..897efcf8 --- /dev/null +++ b/docs/openwallet-project-proposal.md @@ -0,0 +1,96 @@ +# Project Name + +Right now, we often refer to our project as "Aries Bifold," but its official name on GitHub is [Aries Mobile Agent React Native](https://github.com/hyperledger/aries-mobile-agent-react-native). If the OpenWallet Foundation (OWF) accepts our project, we're likely change its official name to either "Aries Bifold" or possibly substitute the "Aries" part altogether. This would help us differentiate our project from the "Aries Hyperledger" initiative. We plan to start discussing this name change as soon as we are part of the OWF. + +# Project Description + +Aries Bifold is an open-source project designed to enhance the way we interact with digital identities, making the process both secure and user-friendly. It is based on React Native, which lets it run smoothly on different devices and platforms, such as iOS, and Android. It is a leading example of digital wallets, with a focus on making verifiable credentials (VCs) simple and convenient for everyone. Aries Bifold is a “growth” project that has been in active development for several years, with [production deployments](https://apps.apple.com/ca/app/bc-wallet/id1587380443) in British Columbia, Canada and other jurisdictions. Our mission is to create a collaborative community that enhances the way digital credentials are handled, making them accessible and straightforward for all. + +**Key Features and Benefits:** + +- **Unified Digital Identity Management:** Emphasizing security and user-friendliness, Aries Bifold excels in consolidating and managing digital identities across various standards like AnonCreds and W3C. This capability positions Bifold as a pivotal resource for secure and private handling of digital identities, accessible to all. + +- **Seamless Multi-Platform Use:** Thanks to its React Native architecture, Bifold delivers a smooth experience on any device, enabling users to manage their digital identities whether they are using a phone or a tablet. This cross-platform flexibility means that developers can create applications once and deploy them on both iOS and Android, ensuring a consistent and accessible user experience. + +- **Community-Driven Development:** Aries Bifold is more than a tool; it's a community initiative aimed at fostering collaboration and sharing innovations. By bringing together diverse groups, from organizations to individuals, Bifold encourages the pooling of resources and knowledge to facilitate the broader adoption and understanding of verifiable credentials. + +- **Widespread Adoption and Trust:** With a growing list of users around the globe, including governmental bodies in Canada and teams in Brazil, Bifold has proven its reliability and relevance. Its international use showcases the platform's adaptability to various needs and its role in advancing digital identity management on a global scale. + +- **Adaptability to Diverse Needs:** Bifold's design caters to a wide range of project types and complexities, offering tailored solutions for managing digital identities. This adaptability ensures that users can streamline their processes related to verifiable credentials, improving efficiency and simplification in digital identity initiatives. + +# Alignment with the OpenWallet Foundation Mission + +Aries Bifold aligns seamlessly with the mission of the OpenWallet Foundation (OWF) as laid out by the Linux Foundation. By developing a secure, versatile, and user-friendly mobile client for managing digital identities, Aries Bifold embodies the OWF's goal of advancing interoperability and setting best practices in digital wallet technology. As an open-source initiative, Aries Bifold contributes to the collective effort of creating an open software stack that empowers organizations and individuals to build interoperable, privacy-protecting wallets without reinventing the wheel. Our project's focus on simplifying interactions with verifiable credentials and fostering a collaborative community echoes the OWF's mission to develop a multi-purpose engine that serves as a starting point for anyone looking to innovate in the digital wallet space. By participating in this shared vision, Aries Bifold helps pave the way for a future where digital wallets are accessible, secure, and capable of supporting a wide range of use cases, from identity verification to payments and beyond. + +# TAC Sponsor(s) + +- Tracy Kuhrt + +# Project License + +Our project is open and free to use under the [Apache 2.0 license](https://github.com/hyperledger/aries-mobile-agent-react-native/blob/main/LICENSE). This means you can use, modify, and distribute it, subject to the terms of this license. + +# Source Control + +We manage our code on GitHub, within the [Hyperledger organization](https://github.com/hyperledger). You can find our project here: + +- [Aries Mobile Agent React Native](https://github.com/hyperledger/aries-mobile-agent-react-native). + +# Issue Tracking + +We keep track of any project issues directly on GitHub using the Issues feature. If there are problems that affect multiple parts of the project, we might use GitHub's Projects feature to manage them. + +# External Dependencies + +Our project uses several smaller libraries, too many to list here. However, one key dependency we heavily rely on is [Credo-ts](https://github.com/openwallet-foundation/credo-ts/issues) from the OpenWallet Foundation, which was previously known as Aries Framework JavaScript. + +# Release Methodology + +Our release process is designed to ensure our software is secure, high-quality, and up-to-date: + +- **Automated Checks**: We employ automated tools to perform code quality and security scans, catching potential issues early. + +- **Peer Review**: All code changes are peer-reviewed, ensuring a second set of eyes evaluates every update for quality and reliability. + +- **CI/CD Pipelines**: Our automated pipelines handle integration, testing, and deployment, keeping our codebase deployable at all times. + +- **NPM Packages**: The outcome of our process is the regular release of updated NPM packages, providing users with the latest features and improvements efficiently. + +This streamlined approach allows us to maintain a high standard for our releases, ensuring our users always have access to the best version of our software. + +# Initial Maintainers + +Our initial [maintainers](https://github.com/hyperledger/aries-mobile-agent-react-native/blob/main/MAINTAINERS.md) are as follows: + +| Name | Github | +| ---------------------- | ------------- | +| Akiff Manji | amanji | +| Bryce McMath | bryce-mcmath | +| Clécio Varjão | cvarjao | +| James Ebert | JamesKEbert | +| Jean-Christophe Drouin | jcdrouin21 | +| Jason C. Leach | jleach | +| Mostafa Gamal | MosCD3 | +| Ryan Koch | ryankoch13 | +| Thiago Romano | thiagoromanos | +| Wade King | wadeking98 | + +# Proposed Project Governance + +The current governance model under Hyperledger is community-based. This means that decisions are made democratically, with the aim of community consensus. In cases where no clear consensus is established, active contributors may be granted a louder voice. + +This approach has proven effective and will be continued as the chosen governance model. + +# Links to Documented Governance Practices + +Work in progress. The intake form has been submitted. + +# Financial Sponsorship + +There are no financial obligations or sponsorship outside of what is covered by the Hyperledger Foundation for infrastructure related costs. + +# Infrastructure + +- GitHub repositories and Actions +- Atlassian Confluence for community meetings via Wiki +- npmjs.org for package publishing From 99f62c935ee7c897b4be42f62e35bcb207934095 Mon Sep 17 00:00:00 2001 From: Caroline Lucas Calheirani <97122568+CarolineLCa@users.noreply.github.com> Date: Tue, 13 Feb 2024 20:44:26 -0300 Subject: [PATCH 12/77] feat: removing statusBar (#1062) Signed-off-by: Caroline Lucas Calheirani --- .../core/App/screens/CredentialOfferAccept.tsx | 8 +------- packages/legacy/core/App/screens/PINCreate.tsx | 3 --- packages/legacy/core/App/screens/PINEnter.tsx | 4 +--- .../core/App/screens/ProofRequestAccept.tsx | 8 +------- .../legacy/core/App/screens/UseBiometry.tsx | 17 +---------------- .../components/CameraDisclosureModal.test.tsx | 5 ++--- 6 files changed, 6 insertions(+), 39 deletions(-) diff --git a/packages/legacy/core/App/screens/CredentialOfferAccept.tsx b/packages/legacy/core/App/screens/CredentialOfferAccept.tsx index 1d5bea98..6c996b82 100644 --- a/packages/legacy/core/App/screens/CredentialOfferAccept.tsx +++ b/packages/legacy/core/App/screens/CredentialOfferAccept.tsx @@ -3,7 +3,7 @@ import { useCredentialById } from '@aries-framework/react-hooks' import { useNavigation } from '@react-navigation/core' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Platform, Modal, StatusBar, StyleSheet, Text, View, ScrollView, AccessibilityInfo } from 'react-native' +import { Modal, StyleSheet, Text, View, ScrollView, AccessibilityInfo } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Button, { ButtonType } from '../components/buttons/Button' @@ -11,7 +11,6 @@ import { useAnimatedComponents } from '../contexts/animated-components' import { useConfiguration } from '../contexts/configuration' import { useTheme } from '../contexts/theme' import { Screens, TabStacks } from '../types/navigators' -import { statusBarStyleForColor, StatusBarStyles } from '../utils/luminance' import { testIdWithKey } from '../utils/testable' enum DeliveryStatus { @@ -108,11 +107,6 @@ const CredentialOfferAccept: React.FC = ({ visible, return ( - {credentialDeliveryStatus === DeliveryStatus.Pending && ( diff --git a/packages/legacy/core/App/screens/PINCreate.tsx b/packages/legacy/core/App/screens/PINCreate.tsx index f3b44c3c..123fd0f1 100644 --- a/packages/legacy/core/App/screens/PINCreate.tsx +++ b/packages/legacy/core/App/screens/PINCreate.tsx @@ -8,7 +8,6 @@ import { Keyboard, StyleSheet, Text, - StatusBar, View, TextInput, TouchableOpacity, @@ -32,7 +31,6 @@ import { useTheme } from '../contexts/theme' import { BifoldError } from '../types/error' import { AuthenticateStackParams, Screens } from '../types/navigators' import { PINCreationValidations, PINValidationsType } from '../utils/PINCreationValidation' -import { StatusBarStyles } from '../utils/luminance' import { testIdWithKey } from '../utils/testable' interface PINCreateProps extends StackScreenProps { @@ -171,7 +169,6 @@ const PINCreate: React.FC = ({ setAuthenticated, route }) => { return ( - diff --git a/packages/legacy/core/App/screens/PINEnter.tsx b/packages/legacy/core/App/screens/PINEnter.tsx index c42e9e9d..0cd59321 100644 --- a/packages/legacy/core/App/screens/PINEnter.tsx +++ b/packages/legacy/core/App/screens/PINEnter.tsx @@ -2,7 +2,7 @@ import { useNavigation } from '@react-navigation/core' import { CommonActions } from '@react-navigation/native' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { StatusBar, Keyboard, StyleSheet, Text, Image, View, DeviceEventEmitter } from 'react-native' +import { Keyboard, StyleSheet, Text, Image, View, DeviceEventEmitter } from 'react-native' import Button, { ButtonType } from '../components/buttons/Button' import PINInput from '../components/inputs/PINInput' @@ -18,7 +18,6 @@ import { useTheme } from '../contexts/theme' import { BifoldError } from '../types/error' import { Screens } from '../types/navigators' import { hashPIN } from '../utils/crypto' -import { StatusBarStyles } from '../utils/luminance' import { testIdWithKey } from '../utils/testable' interface PINEnterProps { @@ -297,7 +296,6 @@ const PINEnter: React.FC = ({ setAuthenticated, usage = PINEntryU return ( - diff --git a/packages/legacy/core/App/screens/ProofRequestAccept.tsx b/packages/legacy/core/App/screens/ProofRequestAccept.tsx index 8ed1e354..beef30f9 100644 --- a/packages/legacy/core/App/screens/ProofRequestAccept.tsx +++ b/packages/legacy/core/App/screens/ProofRequestAccept.tsx @@ -3,14 +3,13 @@ import { useProofById } from '@aries-framework/react-hooks' import { useNavigation } from '@react-navigation/core' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Platform, Modal, StatusBar, StyleSheet, Text, View, ScrollView } from 'react-native' +import { Modal, StyleSheet, Text, View, ScrollView } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Button, { ButtonType } from '../components/buttons/Button' import { useAnimatedComponents } from '../contexts/animated-components' import { useTheme } from '../contexts/theme' import { Screens, TabStacks } from '../types/navigators' -import { statusBarStyleForColor, StatusBarStyles } from '../utils/luminance' import { testIdWithKey } from '../utils/testable' export interface ProofRequestAcceptProps { @@ -73,11 +72,6 @@ const ProofRequestAccept: React.FC = ({ visible, proofI return ( - diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index 9a7cd0e4..456d2a44 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -1,17 +1,6 @@ import React, { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { - StyleSheet, - Text, - View, - Modal, - Switch, - StatusBar, - Platform, - ScrollView, - Pressable, - DeviceEventEmitter, -} from 'react-native' +import { StyleSheet, Text, View, Modal, Switch, ScrollView, Pressable, DeviceEventEmitter } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Button, { ButtonType } from '../components/buttons/Button' @@ -21,7 +10,6 @@ import { useAuth } from '../contexts/auth' import { DispatchAction } from '../contexts/reducers/store' import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' -import { statusBarStyleForColor, StatusBarStyles } from '../utils/luminance' import { testIdWithKey } from '../utils/testable' import PINEnter, { PINEntryUsage } from './PINEnter' @@ -120,9 +108,6 @@ const UseBiometry: React.FC = () => { return ( - diff --git a/packages/legacy/core/__tests__/components/CameraDisclosureModal.test.tsx b/packages/legacy/core/__tests__/components/CameraDisclosureModal.test.tsx index 0e087441..73881611 100644 --- a/packages/legacy/core/__tests__/components/CameraDisclosureModal.test.tsx +++ b/packages/legacy/core/__tests__/components/CameraDisclosureModal.test.tsx @@ -1,7 +1,6 @@ import { useNavigation } from '@react-navigation/core' import { render, fireEvent, act } from '@testing-library/react-native' import React from 'react' - import CameraDisclosureModal from '../../App/components/modals/CameraDisclosureModal' import { testIdWithKey } from '../../App/utils/testable' @@ -14,10 +13,10 @@ jest.mock('@react-navigation/native', () => { }) describe('CameraDisclosureModal Component', () => { - beforeAll(()=>{ + beforeAll(() => { jest.useFakeTimers() }) - afterAll(()=>{ + afterAll(() => { jest.useRealTimers() }) beforeEach(() => { From fd89d3ccacfc4b39b2ccd055ec797c1bda021464 Mon Sep 17 00:00:00 2001 From: Bryce McMath <32586431+bryce-mcmath@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:03:59 -0800 Subject: [PATCH 13/77] feat:implement hide lists for credentials and contacts (#1088) Signed-off-by: Bryce McMath --- .../core/App/contexts/configuration.tsx | 2 + .../legacy/core/App/screens/ListContacts.tsx | 11 +++- .../core/App/screens/ListCredentials.tsx | 16 +++++- .../__tests__/screens/ListContacts.test.tsx | 53 ++++++++++++++++++ .../screens/ListCredentials.test.tsx | 54 +++++++++++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/packages/legacy/core/App/contexts/configuration.tsx b/packages/legacy/core/App/contexts/configuration.tsx index e74a4f83..4479dcaf 100644 --- a/packages/legacy/core/App/contexts/configuration.tsx +++ b/packages/legacy/core/App/contexts/configuration.tsx @@ -59,6 +59,8 @@ export interface ConfigurationContext { globalScreenOptions?: StackNavigationOptions showDetailsInfo?: boolean getCredentialHelpDictionary?: GetCredentialHelpEntry[] + contactHideList?: string[] + credentialHideList?: string[] } export const ConfigurationContext = createContext(null as unknown as ConfigurationContext) diff --git a/packages/legacy/core/App/screens/ListContacts.tsx b/packages/legacy/core/App/screens/ListContacts.tsx index d70d3ad6..77c79a4c 100644 --- a/packages/legacy/core/App/screens/ListContacts.tsx +++ b/packages/legacy/core/App/screens/ListContacts.tsx @@ -8,6 +8,7 @@ import { FlatList, StyleSheet, View } from 'react-native' import HeaderButton, { ButtonLocation } from '../components/buttons/HeaderButton' import ContactListItem from '../components/listItems/ContactListItem' import EmptyListContacts from '../components/misc/EmptyListContacts' +import { useConfiguration } from '../contexts/configuration' import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' import { ContactStackParams, Screens, Stacks } from '../types/navigators' @@ -32,10 +33,16 @@ const ListContacts: React.FC = ({ navigation }) => { }) const { records } = useConnections() const [store] = useStore() - // Filter out mediator agents + const { contactHideList } = useConfiguration() + // Filter out mediator agents and hidden contacts when not in dev mode let connections: ConnectionRecord[] = records if (!store.preferences.developerModeEnabled) { - connections = records.filter((r) => !r.connectionTypes.includes(ConnectionType.Mediator)) + connections = records.filter((r) => { + return ( + !r.connectionTypes.includes(ConnectionType.Mediator) && + !contactHideList?.includes((r.theirLabel || r.alias) ?? '') + ) + }) } const onPressAddContact = () => { diff --git a/packages/legacy/core/App/screens/ListCredentials.tsx b/packages/legacy/core/App/screens/ListCredentials.tsx index 32f77fb3..423a4aba 100644 --- a/packages/legacy/core/App/screens/ListCredentials.tsx +++ b/packages/legacy/core/App/screens/ListCredentials.tsx @@ -1,3 +1,4 @@ +import { AnonCredsCredentialMetadataKey } from '@aries-framework/anoncreds/build/utils/metadata' import { CredentialState } from '@aries-framework/core' import { useCredentialByState } from '@aries-framework/react-hooks' import { useNavigation } from '@react-navigation/core' @@ -18,18 +19,29 @@ import { TourID } from '../types/tour' const ListCredentials: React.FC = () => { const { t } = useTranslation() + const [store, dispatch] = useStore() const { credentialListOptions: CredentialListOptions, credentialEmptyList: CredentialEmptyList, enableTours: enableToursConfig, + credentialHideList, } = useConfiguration() - const credentials = [ + + let credentials = [ ...useCredentialByState(CredentialState.CredentialReceived), ...useCredentialByState(CredentialState.Done), ] + + // Filter out hidden credentials when not in dev mode + if (!store.preferences.developerModeEnabled) { + credentials = credentials.filter((r) => { + const credDefId = r.metadata.get(AnonCredsCredentialMetadataKey)?.credentialDefinitionId + return !credentialHideList?.includes(credDefId) + }) + } + const navigation = useNavigation>() const { ColorPallet } = useTheme() - const [store, dispatch] = useStore() const { start, stop } = useTour() const screenIsFocused = useIsFocused() diff --git a/packages/legacy/core/__tests__/screens/ListContacts.test.tsx b/packages/legacy/core/__tests__/screens/ListContacts.test.tsx index 1b9fab90..0086bc70 100644 --- a/packages/legacy/core/__tests__/screens/ListContacts.test.tsx +++ b/packages/legacy/core/__tests__/screens/ListContacts.test.tsx @@ -5,6 +5,7 @@ import { act, fireEvent, render } from '@testing-library/react-native' import React from 'react' import { ConfigurationContext } from '../../App/contexts/configuration' +import { StoreProvider, defaultState } from '../../App/contexts/store' import ListContacts from '../../App/screens/ListContacts' import configurationContext from '../contexts/configuration' @@ -83,4 +84,56 @@ describe('ListContacts Component', () => { }) }) }) + + test('Hide list filters out specific contacts', async () => { + const navigation = useNavigation() + const tree = render( + + + + + + ) + await act(async () => {}) + + const faberContact = await tree.queryByText('Faber', { exact: false }) + const bobContact = await tree.queryByText('Bob', { exact: false }) + + expect(faberContact).toBe(null) + expect(bobContact).not.toBe(null) + }) + + test('Hide list does not filter out specific contacts when developer mode is enabled', async () => { + const navigation = useNavigation() + const tree = render( + + + + + + ) + await act(async () => {}) + + const faberContact = await tree.queryByText('Faber', { exact: false }) + const bobContact = await tree.queryByText('Bob', { exact: false }) + + expect(faberContact).not.toBe(null) + expect(bobContact).not.toBe(null) + }) }) diff --git a/packages/legacy/core/__tests__/screens/ListCredentials.test.tsx b/packages/legacy/core/__tests__/screens/ListCredentials.test.tsx index f9eb8ea7..584ab646 100644 --- a/packages/legacy/core/__tests__/screens/ListCredentials.test.tsx +++ b/packages/legacy/core/__tests__/screens/ListCredentials.test.tsx @@ -9,6 +9,7 @@ import { ReactTestInstance } from 'react-test-renderer' import CredentialCard from '../../App/components/misc/CredentialCard' import { ConfigurationContext } from '../../App/contexts/configuration' +import { StoreProvider, defaultState } from '../../App/contexts/store' import ListCredentials from '../../App/screens/ListCredentials' import configurationContext from '../contexts/configuration' @@ -26,6 +27,8 @@ jest.mock('@react-navigation/native', () => { // eslint-disable-next-line @typescript-eslint/no-empty-function jest.mock('react-native-localize', () => {}) +const credentialDefinitionId = 'xxxxxxxxxxxxxxxxxx:3:CL:11111:default' + describe('displays a credentials list screen', () => { const testOpenVPCredentialRecord = new CredentialExchangeRecord({ threadId: '1', @@ -35,6 +38,7 @@ describe('displays a credentials list screen', () => { }) testOpenVPCredentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { schemaId: 'Ui6HA36FvN83cEtmYYHxrn:2:unverified_person:0.1.0', + credentialDefinitionId, }) testOpenVPCredentialRecord.credentials.push({ credentialRecordType: 'anoncreds', @@ -126,4 +130,54 @@ describe('displays a credentials list screen', () => { expect(new Date(createdAtDates[2])).toEqual(new Date('2020-01-01T00:00:00')) }) }) + + test('Hide list filters out specific credentials', async () => { + const tree = render( + + + + + + ) + await act(async () => { + const credentialCards = tree.UNSAFE_getAllByType(CredentialCard) + + expect(credentialCards.length).toBe(2) + }) + }) + + test('Hide list does not filter out specific credentials when developer mode is enabled', async () => { + const tree = render( + + + + + + ) + await act(async () => { + const credentialCards = tree.UNSAFE_getAllByType(CredentialCard) + + expect(credentialCards.length).toBe(3) + }) + }) }) From bb5b3ce44964c9810a1b613e4a9734cd9b47777b Mon Sep 17 00:00:00 2001 From: Bryce McMath <32586431+bryce-mcmath@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:04:43 -0800 Subject: [PATCH 14/77] feat: add loading info box during attestation (#1090) Signed-off-by: Bryce McMath --- packages/legacy/core/App/localization/en/index.ts | 1 + packages/legacy/core/App/localization/fr/index.ts | 1 + packages/legacy/core/App/localization/pt-br/index.ts | 1 + packages/legacy/core/App/screens/ProofRequest.tsx | 6 ++++++ 4 files changed, 9 insertions(+) diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index 4b43a46e..9bb8543c 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -419,6 +419,7 @@ const translation = { "DeleteOfferDescription": "Don't recognize the organization? Check your Contacts list. You only receive notifications from Contacts you've initiated", }, "ProofRequest": { + "JustAMoment": "Just a moment while we prepare things for you...", "FromYourWallet": "From your wallet", "MissingCredentials": "Missing credentials", "PredicateGeDate": "is after", diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index 3189c8b9..6f25e0df 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -416,6 +416,7 @@ const translation = { "CustomOfferParagraph2": "Vous ne reconnaissez pas l'organisation? Vérifiez votre liste de Contacts. Vous ne recevez des notifications que des Contacts que vous avez initiés." }, "ProofRequest": { + "JustAMoment": "Just a moment while we prepare things for you... (FR)", "FromYourWallet": "From your wallet (FR)", "MissingCredentials": "Missing credentials (FR)", "PredicateGeDate": "is after (FR)", diff --git a/packages/legacy/core/App/localization/pt-br/index.ts b/packages/legacy/core/App/localization/pt-br/index.ts index 35b78851..f4a2b0ae 100644 --- a/packages/legacy/core/App/localization/pt-br/index.ts +++ b/packages/legacy/core/App/localization/pt-br/index.ts @@ -400,6 +400,7 @@ const translation = { "CustomOfferParagraph2": "Não reconhece a organização. Verifique sua lista de Contatos. Você só recebe notificões de Contatos que você tenha adicionado.", }, "ProofRequest": { + "JustAMoment": "Just a moment while we prepare things for you... (PT-BR)", "FromYourWallet": "From your wallet (PB)", "MissingCredentials": "Missing credentials (PB)", "PredicateGeDate": "é posterior a", diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 29f64812..71e1e424 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -21,6 +21,7 @@ import { CredentialCard } from '../components/misc' import ConnectionAlert from '../components/misc/ConnectionAlert' import ConnectionImage from '../components/misc/ConnectionImage' import CommonRemoveModal from '../components/modals/CommonRemoveModal' +import InfoTextBox from '../components/texts/InfoTextBox' import { EventTypes } from '../constants' import { useAnimatedComponents } from '../contexts/animated-components' import { useConfiguration } from '../contexts/configuration' @@ -393,6 +394,11 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const proofPageHeader = () => { return ( + {attestationLoading && ( + + {t('ProofRequest.JustAMoment')} + + )} {loading || attestationLoading ? ( From 04a1fe1cdda28f1f566f39c2b1f02253d343ba06 Mon Sep 17 00:00:00 2001 From: Sophia Sales <69023582+SophiaSales@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:44:49 -0300 Subject: [PATCH 15/77] fix: updating navigation to deeplink in Connection (#1086) Signed-off-by: Sophia Sales Co-authored-by: Sophia Sales --- packages/legacy/core/App/screens/Connection.tsx | 2 +- .../core/__mocks__/custom/@react-navigation/core.ts | 3 +++ .../core/__tests__/screens/Connection.test.tsx | 12 +++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/legacy/core/App/screens/Connection.tsx b/packages/legacy/core/App/screens/Connection.tsx index 6f83a430..fd231b3a 100644 --- a/packages/legacy/core/App/screens/Connection.tsx +++ b/packages/legacy/core/App/screens/Connection.tsx @@ -149,7 +149,7 @@ const Connection: React.FC = ({ navigation, route }) => { state.notificationRecord && state.notificationRecord.state === 'request-received' ) { - navigation.navigate(Screens.ProofRequest, { proofId: state.notificationRecord.id }) + navigation.replace(Screens.ProofRequest, { proofId: state.notificationRecord.id }) dispatch({ isVisible: false }) return diff --git a/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts b/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts index 5460d9e0..36243d5b 100644 --- a/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts +++ b/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts @@ -1,12 +1,15 @@ const navigate = jest.fn() const dispatch = jest.fn() +const replace = jest.fn() const navigation = { __timestamp: process.hrtime(), navigate, + replace, setOptions: jest.fn(), getParent: () => {return { navigate, dispatch, + replace, }}, getState: jest.fn(() => ({ index: jest.fn(), diff --git a/packages/legacy/core/__tests__/screens/Connection.test.tsx b/packages/legacy/core/__tests__/screens/Connection.test.tsx index c83e44dd..8acf4b86 100644 --- a/packages/legacy/core/__tests__/screens/Connection.test.tsx +++ b/packages/legacy/core/__tests__/screens/Connection.test.tsx @@ -14,6 +14,8 @@ import ConnectionModal from '../../App/screens/Connection' import { testIdWithKey } from '../../App/utils/testable' import configurationContext from '../contexts/configuration' import timeTravel from '../helpers/timetravel' +import { StackNavigationProp } from '@react-navigation/stack' +import { DeliveryStackParams, Screens } from '../../App/types/navigators' const proofNotifPath = path.join(__dirname, '../fixtures/proof-notif.json') const proofNotif = JSON.parse(fs.readFileSync(proofNotifPath, 'utf8')) @@ -42,7 +44,7 @@ jest.mock('../../App/hooks/connections', () => ({ })) describe('ConnectionModal Component', () => { - beforeEach(() => { + beforeEach(() => { // @ts-ignore-next-line useNotifications.mockReturnValue({ total: 0, notifications: [] }) // @ts-ignore-next-line @@ -185,7 +187,7 @@ describe('ConnectionModal Component', () => { }) test('No connection proof request auto navigate', async () => { - const navigation = useNavigation() + const navigation = useNavigation>() // @ts-ignore-next-line useNotifications.mockReturnValue({ total: 1, notifications: [proofNotif] }) // @ts-ignore-next-line @@ -203,9 +205,9 @@ describe('ConnectionModal Component', () => { const tree = render(element) expect(tree).toMatchSnapshot() - expect(navigation.navigate).toBeCalledTimes(1) - expect(navigation.navigate).toBeCalledWith('Proof Request', { proofId: proofNotif.id }) - }) + expect(navigation.replace).toBeCalledTimes(1) + expect(navigation.replace).toBeCalledWith('Proof Request', { proofId: proofNotif.id }) + }) test('Goal code extracted and navigation to Chat', async () => { const navigation = useNavigation() From 7c5a1f88b97b5f471f3f65f7875b996a0ac55fbc Mon Sep 17 00:00:00 2001 From: NidhishVyas Date: Wed, 14 Feb 2024 16:46:11 -0500 Subject: [PATCH 16/77] hard-code --- .../modals/DismissiblePopupModal.tsx | 2 +- .../App/components/record/RecordField.tsx | 13 +- .../legacy/core/App/localization/en/index.ts | 3 +- .../core/App/navigators/ContactStack.tsx | 14 +- .../core/App/navigators/ProofRequestStack.tsx | 6 + packages/legacy/core/App/screens/Chat.tsx | 5 +- .../legacy/core/App/screens/Connection.tsx | 1 + .../core/App/screens/ContactDetails.tsx | 12 +- .../App/screens/CredentialOfferAccept.tsx | 3 +- .../legacy/core/App/screens/ProofRequest.tsx | 2 + .../core/App/screens/ProofRequestDetails.tsx | 5 +- .../core/App/screens/ProofSelection.tsx | 7 - .../core/App/screens/SelectProofRequest.tsx | 122 ++++++++++++++++++ packages/legacy/core/App/screens/Settings.tsx | 43 +++--- packages/legacy/core/App/theme.ts | 3 +- packages/legacy/core/App/types/navigators.ts | 8 +- packages/legacy/core/App/utils/credential.ts | 6 + packages/legacy/core/App/utils/helpers.ts | 1 + packages/verifier/src/request-templates.ts | 16 +-- 19 files changed, 218 insertions(+), 54 deletions(-) delete mode 100644 packages/legacy/core/App/screens/ProofSelection.tsx create mode 100644 packages/legacy/core/App/screens/SelectProofRequest.tsx diff --git a/packages/legacy/core/App/components/modals/DismissiblePopupModal.tsx b/packages/legacy/core/App/components/modals/DismissiblePopupModal.tsx index fd6da561..0128c383 100644 --- a/packages/legacy/core/App/components/modals/DismissiblePopupModal.tsx +++ b/packages/legacy/core/App/components/modals/DismissiblePopupModal.tsx @@ -74,7 +74,7 @@ const DismissiblePopupModal: React.FC = ({ headerText: { ...TextTheme.bold, alignSelf: 'flex-start', - color: ColorPallet.notification.infoText, + color: ColorPallet.grayscale.black, }, bodyText: { ...TextTheme.normal, diff --git a/packages/legacy/core/App/components/record/RecordField.tsx b/packages/legacy/core/App/components/record/RecordField.tsx index abf013ea..3b72a640 100644 --- a/packages/legacy/core/App/components/record/RecordField.tsx +++ b/packages/legacy/core/App/components/record/RecordField.tsx @@ -7,6 +7,7 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native' import { hiddenFieldValue } from '../../constants' import { useTheme } from '../../contexts/theme' +import { DateToString } from '../../utils/credential' import { isDataUrl } from '../../utils/helpers' import { testIdWithKey } from '../../utils/testable' @@ -41,13 +42,20 @@ export const AttributeValue: React.FC = ({ field, style, s }) if ( (field.encoding == validEncoding && field.format && validFormat.test(field.format) && field.value) || - isDataUrl(field.value) + isDataUrl(field.value) || + field.name?.toLowerCase().includes('photo') ) { return } if (field.type == CaptureBaseAttributeType.DateInt || field.type == CaptureBaseAttributeType.DateTime) { return } + if (field.name?.toLowerCase().includes('date')) + return ( + + {shown ? DateToString(field.value as string) : hiddenFieldValue} + + ) return ( {shown ? field.value : hiddenFieldValue} @@ -92,6 +100,9 @@ const RecordField: React.FC = ({ }, }) + // // eslint-disable-next-line no-console + // console.log(field) + return ( diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index e0df8415..366d3ffb 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -487,6 +487,7 @@ const translation = { "Build": "Build", "WhatAreContacts": "What are Contacts?", "QRCodeGen": "Generate QR Code", + "ProofRequestDetails":"Proof Request Details", "ScanMyQR": "Scan my QR code", "Help": "Help", "MoreInformation": "More information", @@ -548,6 +549,7 @@ const translation = { "ChangePIN": "Change PIN", "CommonDecline": "Decline", "ProofRequests": "Proof requests", + "SelectProofRequest": "Select proof request", "SendProofRequest": "Send a proof request", "ChooseProofRequest": "Choose a proof request", "ProofRequestUsageHistory": "Usage History", @@ -560,7 +562,6 @@ const translation = { "ProofRequesting": 'Proof Requesting', "NameWallet": "Name your wallet", "RenameContact": "Edit Contact Name", - "ProofSelection": "Select for proof selection", }, "Loading": { "TakingTooLong": "This is taking longer than usual. You can return to home or continue waiting.", diff --git a/packages/legacy/core/App/navigators/ContactStack.tsx b/packages/legacy/core/App/navigators/ContactStack.tsx index 7ba04faf..878c3eae 100644 --- a/packages/legacy/core/App/navigators/ContactStack.tsx +++ b/packages/legacy/core/App/navigators/ContactStack.tsx @@ -11,9 +11,10 @@ import CredentialOffer from '../screens/CredentialOffer' import ListContacts from '../screens/ListContacts' import ProofDetails from '../screens/ProofDetails' import ProofRequest from '../screens/ProofRequest' -import ProofSelection from '../screens/ProofSelection' +import ProofRequestDetails from '../screens/ProofRequestDetails' import QRCodeGen from '../screens/QRCodeGen' import RenameContact from '../screens/RenameContact' +import SelectProofRequest from '../screens/SelectProofRequest' import WhatAreContacts from '../screens/WhatAreContacts' import { ContactStackParams, Screens } from '../types/navigators' @@ -41,12 +42,17 @@ const ContactStack: React.FC = () => { options={{ title: t('Screens.RenameContact') }} /> + { component={ListProofRequests} options={{ title: t('Screens.ChooseProofRequest') }} /> + = ({ route }) => { const onSendRequest = useCallback(async () => { navigation.navigate(Stacks.ProofRequestsStack as any, { - screen: Screens.ProofRequests, + // screen: Screens.ProofRequests, + screen: Screens.SelectProofRequest, params: { navigation: navigation, connectionId }, }) }, [navigation, connectionId]) const actions = useMemo(() => { - return store.preferences.useVerifierCapability + return !store.preferences.useVerifierCapability ? [ { text: t('Verifier.SendProofRequest'), diff --git a/packages/legacy/core/App/screens/Connection.tsx b/packages/legacy/core/App/screens/Connection.tsx index 6f83a430..b8da00d6 100644 --- a/packages/legacy/core/App/screens/Connection.tsx +++ b/packages/legacy/core/App/screens/Connection.tsx @@ -64,6 +64,7 @@ const Connection: React.FC = ({ navigation, route }) => { }, messageText: { fontWeight: TextTheme.normal.fontWeight, + color: ColorPallet.grayscale.black, textAlign: 'center', marginTop: 30, }, diff --git a/packages/legacy/core/App/screens/ContactDetails.tsx b/packages/legacy/core/App/screens/ContactDetails.tsx index 97f36571..b01527c0 100644 --- a/packages/legacy/core/App/screens/ContactDetails.tsx +++ b/packages/legacy/core/App/screens/ContactDetails.tsx @@ -13,7 +13,7 @@ import { ToastType } from '../components/toast/BaseToast' import { EventTypes } from '../constants' import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' -import { useConnectionByOutOfBandId } from '../hooks/connections' +// import { useConnectionByOutOfBandId } from '../hooks/connections' import { BifoldError } from '../types/error' import { ContactStackParams, Screens, TabStacks } from '../types/navigators' import { ModalUsage } from '../types/remove' @@ -94,8 +94,8 @@ const ContactDetails: React.FC = ({ route }) => { } const handleGoToSelect = () => { - // navigation.navigate(Screens.ProofSelection, { connectionId }) - useConnectionByOutOfBandId() + navigation.navigate(Screens.SelectProofRequest, { connectionId }) + // useConnectionByOutOfBandId() } const callGoToRename = useCallback(() => handleGoToRename(), []) @@ -132,12 +132,12 @@ const ContactDetails: React.FC = ({ route }) => { - {t('Screens.ProofSelection')} + {t('Screens.SelectProofRequest')} = ({ visible, const [timer, setTimer] = useState() const credential = useCredentialById(credentialId) const navigation = useNavigation() - const { ListItems } = useTheme() + const { ListItems, ColorPallet } = useTheme() const { CredentialAdded, CredentialPending } = useAnimatedComponents() const { connectionTimerDelay } = useConfiguration() const connTimerDelay = connectionTimerDelay ?? 10000 // in ms @@ -52,6 +52,7 @@ const CredentialOfferAccept: React.FC = ({ visible, messageText: { textAlign: 'center', marginTop: 30, + color: ColorPallet.grayscale.black, }, controlsContainer: { marginTop: 'auto', diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 117debc6..966ca57a 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -1,3 +1,5 @@ +/* eslint-disable import/no-named-as-default-member */ +/* eslint-disable no-unsafe-optional-chaining */ import type { StackScreenProps } from '@react-navigation/stack' import { diff --git a/packages/legacy/core/App/screens/ProofRequestDetails.tsx b/packages/legacy/core/App/screens/ProofRequestDetails.tsx index 6a3e07c4..e69141dc 100644 --- a/packages/legacy/core/App/screens/ProofRequestDetails.tsx +++ b/packages/legacy/core/App/screens/ProofRequestDetails.tsx @@ -207,7 +207,7 @@ const ProofRequestAttributesCard: React.FC = ( ) } -const ProofRequestDetails: React.FC = ({ route, navigation }) => { +const ProofRequestDetails: React.FC = ({ navigation }) => { const { ColorPallet, TextTheme } = useTheme() const [store] = useStore() const { t } = useTranslation() @@ -218,6 +218,7 @@ const ProofRequestDetails: React.FC = ({ route, naviga if (!agent) { throw new Error('Unable to fetch agent from AFJ') } + const templateId = 'Aries:5:VerifiedFullName:0.0.1:indy' const style = StyleSheet.create({ container: { @@ -245,7 +246,7 @@ const ProofRequestDetails: React.FC = ({ route, naviga }, }) - const { templateId, connectionId } = route?.params + const connectionId = '4449b08e-b3f8-4c5d-98c2-91e0f9093328' const [meta, setMeta] = useState(undefined) const [attributes, setAttributes] = useState | undefined>(undefined) diff --git a/packages/legacy/core/App/screens/ProofSelection.tsx b/packages/legacy/core/App/screens/ProofSelection.tsx deleted file mode 100644 index 20a82c22..00000000 --- a/packages/legacy/core/App/screens/ProofSelection.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' - -const ProofSelection = () => { - return <> -} - -export default ProofSelection diff --git a/packages/legacy/core/App/screens/SelectProofRequest.tsx b/packages/legacy/core/App/screens/SelectProofRequest.tsx new file mode 100644 index 00000000..8281b235 --- /dev/null +++ b/packages/legacy/core/App/screens/SelectProofRequest.tsx @@ -0,0 +1,122 @@ +import { useAgent } from '@aries-framework/react-hooks' +import { linkProofWithTemplate, sendProofRequest, useProofRequestTemplates } from '@hyperledger/aries-bifold-verifier' +import { useNavigation } from '@react-navigation/core' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { View, StyleSheet, ScrollView } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' + +import Button, { ButtonType } from '../components/buttons/Button' +import CheckBoxRow from '../components/inputs/CheckBoxRow' +import { useTheme } from '../contexts/theme' +import { Screens } from '../types/navigators' +import { testIdWithKey } from '../utils/testable' + +const SelectProofRequest = (connectionId: string) => { + const { OnboardingTheme } = useTheme() + const navigation = useNavigation() + + const [selectedFields, setSelectedFields] = useState({ + firstName: false, + lastName: false, + dob: false, + }) + const { t } = useTranslation() + + const style = StyleSheet.create({ + container: { + ...OnboardingTheme.container, + padding: 20, + }, + controlsContainer: { + marginTop: 'auto', + marginBottom: 20, + }, + marginView: { + marginTop: 10, + marginBottom: 10, + }, + }) + + const handleFieldToggle = (fieldName: string) => { + setSelectedFields((prevFields) => ({ + ...prevFields, + [fieldName]: !prevFields[fieldName], + })) + } + + // const handleRequest = () => { + // const selectedArray = [] + // if (selectedFields.firstName) { + // selectedArray.push('first_name') + // } + // if (selectedFields.lastName) { + // selectedArray.push('last_name') + // } + // // Now selectedArray contains the desired strings based on the state of firstName and lastName + // useProofRequestTemplates(true, selectedArray) + // // Proceed with further logic using selectedArray + // } + + const { agent } = useAgent() + + const useProofRequest = useCallback(async () => { + const selectedArray = [] + if (selectedFields.firstName) { + selectedArray.push('first_name') + } + if (selectedFields.lastName) { + selectedArray.push('last_name') + } + if (connectionId) { + // Send to specific contact and redirect to the chat with him + sendProofRequest(agent, useProofRequestTemplates(true, selectedArray), connectionId, []).then((result) => { + if (result?.proofRecord) { + linkProofWithTemplate(agent, result.proofRecord, templateId) + } + }) + + navigation.getParent()?.navigate(Screens.Chat, { connectionId }) + } else { + // Else redirect to the screen with connectionless request + navigation.navigate(Screens.ProofRequesting, { templateId, predicateValues: customPredicateValues }) + } + }, [agent, connectionId]) + + return ( + + + + + handleFieldToggle('firstName')} + /> + + + handleFieldToggle('lastName')} + /> + + + handleFieldToggle('dob')} /> + + + + + + )} + + ) : ( + + + + )} + + + + ) +} + +export default PushNotification diff --git a/packages/legacy/core/App/screens/Settings.tsx b/packages/legacy/core/App/screens/Settings.tsx index d295a536..12b379a0 100644 --- a/packages/legacy/core/App/screens/Settings.tsx +++ b/packages/legacy/core/App/screens/Settings.tsx @@ -34,7 +34,7 @@ const Settings: React.FC = ({ navigation }) => { const [store, dispatch] = useStore() const developerOptionCount = useRef(0) const { SettingsTheme, TextTheme, ColorPallet, Assets } = useTheme() - const { settings, enableTours } = useConfiguration() + const { settings, enableTours, enablePushNotifications } = useConfiguration() const defaultIconSize = 24 const styles = StyleSheet.create({ container: { @@ -164,6 +164,22 @@ const Settings: React.FC = ({ navigation }) => { ...(settings || []), ] + // add optional push notifications menu to settings + if (enablePushNotifications) { + settingsSections + .find((item) => item.header.title === t('Settings.AppSettings')) + ?.data.push({ + title: t('Settings.Notifications'), + value: undefined, + accessibilityLabel: t('Settings.Notifications'), + testID: testIdWithKey('Notifications'), + onPress: () => + navigation + .getParent() + ?.navigate(Stacks.SettingStack, { screen: Screens.UsePushNotifications, params: { isMenu: true } }), + }) + } + if (enableTours) { const section = settingsSections.find((item) => item.header.title === t('Settings.AppSettings')) if (section) { diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index 72ebb0bd..521a94e2 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -29,6 +29,7 @@ export enum Screens { OnTheWay = 'On The Way', Declined = 'Declined', UseBiometry = 'Use Biometry', + UsePushNotifications = 'Use Push Notifications', Developer = 'Developer', CustomNotification = 'Custom Notification', ProofChangeCredential = 'Choose a credential', @@ -84,6 +85,7 @@ export type AuthenticateStackParams = { [Screens.EnterPIN]: { setAuthenticated: (status: boolean) => void } | undefined [Screens.UseBiometry]: undefined [Screens.NameWallet]: undefined + [Screens.UsePushNotifications]: undefined } export type OnboardingStackParams = { @@ -143,6 +145,7 @@ export type SettingStackParams = { [Screens.Terms]: undefined [Screens.Onboarding]: undefined [Screens.Developer]: undefined + [Screens.UsePushNotifications]: { isMenu?: boolean } } export type NotificationStackParams = { diff --git a/packages/legacy/core/App/types/state.ts b/packages/legacy/core/App/types/state.ts index c5ab0493..f509b415 100644 --- a/packages/legacy/core/App/types/state.ts +++ b/packages/legacy/core/App/types/state.ts @@ -4,6 +4,7 @@ export interface Onboarding { didAgreeToTerms: boolean didCreatePIN: boolean didConsiderBiometry: boolean + didConsiderPushNotifications: boolean didNameWallet: boolean } @@ -13,6 +14,7 @@ export interface Migration { export interface Preferences { useBiometry: boolean + usePushNotifications: boolean biometryPreferencesUpdated: boolean developerModeEnabled: boolean useVerifierCapability?: boolean diff --git a/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts b/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts index 36243d5b..f6bc2fde 100644 --- a/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts +++ b/packages/legacy/core/__mocks__/custom/@react-navigation/core.ts @@ -29,4 +29,10 @@ const useIsFocused = () => { return true } -export { useNavigation, useIsFocused } +const CommonActions = { + navigate: jest.fn(), + reset: jest.fn(), + goBack: jest.fn(), +} + +export { useNavigation, useIsFocused, CommonActions } diff --git a/packages/legacy/core/__tests__/screens/NameWallet.test.tsx b/packages/legacy/core/__tests__/screens/NameWallet.test.tsx index 652d2048..7edc437e 100644 --- a/packages/legacy/core/__tests__/screens/NameWallet.test.tsx +++ b/packages/legacy/core/__tests__/screens/NameWallet.test.tsx @@ -52,6 +52,7 @@ describe('NameWallet Screen', () => { didConsiderBiometry: true, didNameWallet: true, didSeePreface: true, + didConsiderPushNotifications: true, }, } const tree = render( diff --git a/packages/legacy/core/__tests__/screens/PushNotification.test.tsx b/packages/legacy/core/__tests__/screens/PushNotification.test.tsx new file mode 100644 index 00000000..fa868b0b --- /dev/null +++ b/packages/legacy/core/__tests__/screens/PushNotification.test.tsx @@ -0,0 +1,82 @@ +import { fireEvent, render, waitFor } from '@testing-library/react-native' +import React from 'react' + +import { useConfiguration } from '../../App/contexts/configuration' +import { StoreProvider, defaultState } from '../../App/contexts/store' +import { testIdWithKey } from '../../App/utils/testable' +import PushNotification from '../../App/screens/PushNotification' + + +jest.mock('@react-navigation/core', () => { + return require('../../__mocks__/custom/@react-navigation/core') +}) +jest.mock('@react-navigation/native', () => { + return require('../../__mocks__/custom/@react-navigation/native') +}) +jest.mock('@react-navigation/core',) + +jest.mock('@hyperledger/anoncreds-react-native', () => ({})) +jest.mock('@hyperledger/aries-askar-react-native', () => ({})) +jest.mock('@hyperledger/indy-vdr-react-native', () => ({})) +jest.mock('../../App/contexts/configuration', () => ({ + useConfiguration: jest.fn(), +})) + +describe('displays a push notification screen', () => { + const setup = jest.fn() + const toggle = jest.fn() + const status = async () => await Promise.resolve('denied') + beforeEach(() => { + // @ts-ignore-next-line + useConfiguration.mockReturnValue({ enablePushNotifications: { setup, toggle, status } }) + jest.clearAllMocks() + }) + + test('Push notification screen renders correctly in settings', async () => { + const route = { + params: { + isMenu: true, + }, + } as any + const tree = render( + + + + ) + + expect(tree).toMatchSnapshot() + const toggleSwitch = tree.getByTestId(testIdWithKey('PushNotificationSwitch')) + expect(toggleSwitch).not.toBe(null) + await waitFor(() => { + fireEvent(toggleSwitch, 'onValueChange', true) + }) + expect(setup).toHaveBeenCalledTimes(1) + expect(toggle).toHaveBeenCalledTimes(1) + }) + test('Push notification screen renders correctly in onboarding', async () => { + const route = { + params: { + }, + } as any + const tree = render( + + + + ) + + const continueButton = tree.getByTestId(testIdWithKey('PushNotificationContinue')) + expect(continueButton).not.toBe(null) + await waitFor(() => { + fireEvent(continueButton, 'press') + }) + expect(setup).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap new file mode 100644 index 00000000..065b20ee --- /dev/null +++ b/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap @@ -0,0 +1,261 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`displays a push notification screen Push notification screen renders correctly in settings 1`] = ` + + + + + + < /> + + + PushNotifications.EnableNotifiactions + + + PushNotifications.BeNotified + + + + • + + + PushNotifications.BulletOne + + + + + • + + + PushNotifications.BulletTwo + + + + + • + + + PushNotifications.BulletThree + + + + + • + + + PushNotifications.BulletFour + + + + + + PushNotifications.ReceiveNotifications + + + + + + + + +`; From 881d993d53a73782c67e609da67652640397a552 Mon Sep 17 00:00:00 2001 From: Wade King Date: Mon, 18 Mar 2024 11:41:19 -0700 Subject: [PATCH 41/77] feat: added ability to update eula post onboarding (#1114) Signed-off-by: wadeking98 --- packages/legacy/core/App/container-api.ts | 2 +- packages/legacy/core/App/container-impl.ts | 4 +- .../core/App/contexts/reducers/store.ts | 4 +- .../core/App/navigators/OnboardingStack.tsx | 2 +- .../legacy/core/App/navigators/RootStack.tsx | 3 +- packages/legacy/core/App/screens/Splash.tsx | 44 +++++++++------ packages/legacy/core/App/screens/Terms.tsx | 56 +++++++++++-------- packages/legacy/core/App/types/state.ts | 2 +- .../core/__tests__/screens/Splash.test.tsx | 41 ++++++++------ .../core/__tests__/screens/Terms.test.tsx | 1 - 10 files changed, 95 insertions(+), 64 deletions(-) diff --git a/packages/legacy/core/App/container-api.ts b/packages/legacy/core/App/container-api.ts index 03aa24e7..bc9aad23 100644 --- a/packages/legacy/core/App/container-api.ts +++ b/packages/legacy/core/App/container-api.ts @@ -40,7 +40,7 @@ export type FN_ONBOARDING_DONE = ( export interface TokenMapping { [TOKENS.SCREEN_PREFACE]: React.FC [TOKENS.STACK_ONBOARDING]: React.FC - [TOKENS.SCREEN_TERMS]: React.FC + [TOKENS.SCREEN_TERMS]: { screen: React.FC; version: boolean | string } [TOKENS.SCREEN_DEVELOPER]: React.FC [TOKENS.SCREEN_ONBOARDING]: typeof Onboarding [TOKENS.FN_ONBOARDING_DONE]: FN_ONBOARDING_DONE diff --git a/packages/legacy/core/App/container-impl.ts b/packages/legacy/core/App/container-impl.ts index 10f2a069..0ab05997 100644 --- a/packages/legacy/core/App/container-impl.ts +++ b/packages/legacy/core/App/container-impl.ts @@ -9,7 +9,7 @@ import OnboardingStack from './navigators/OnboardingStack' import Developer from './screens/Developer' import Onboarding from './screens/Onboarding' import Preface from './screens/Preface' -import ScreenTerms from './screens/Terms' +import ScreenTerms, { TermsVersion } from './screens/Terms' import { AuthenticateStackParams, Screens } from './types/navigators' export class MainContainer implements Container { @@ -23,7 +23,7 @@ export class MainContainer implements Container { console.log(`Initializing Bifold container`) this.container.registerInstance(TOKENS.SCREEN_PREFACE, Preface) this.container.registerInstance(TOKENS.SCREEN_DEVELOPER, Developer) - this.container.registerInstance(TOKENS.SCREEN_TERMS, ScreenTerms) + this.container.registerInstance(TOKENS.SCREEN_TERMS, { screen: ScreenTerms, version: TermsVersion }) this.container.registerInstance(TOKENS.SCREEN_ONBOARDING, Onboarding) this.container.registerInstance(TOKENS.STACK_ONBOARDING, OnboardingStack) this.container.registerInstance(TOKENS.COMP_BUTTON, Button) diff --git a/packages/legacy/core/App/contexts/reducers/store.ts b/packages/legacy/core/App/contexts/reducers/store.ts index 018a539c..aa260bb7 100644 --- a/packages/legacy/core/App/contexts/reducers/store.ts +++ b/packages/legacy/core/App/contexts/reducers/store.ts @@ -480,9 +480,11 @@ export const reducer = (state: S, action: ReducerAction { const defaultStackOptions = createDefaultStackOptions(theme) const navigation = useNavigation>() const onTutorialCompleted = container.resolve(TOKENS.FN_ONBOARDING_DONE)(dispatch, navigation) - const Terms = container.resolve(TOKENS.SCREEN_TERMS) + const { screen: Terms } = container.resolve(TOKENS.SCREEN_TERMS) const Developer = container.resolve(TOKENS.SCREEN_DEVELOPER) const Preface = container.resolve(TOKENS.SCREEN_PREFACE) diff --git a/packages/legacy/core/App/navigators/RootStack.tsx b/packages/legacy/core/App/navigators/RootStack.tsx index 8f52ad55..a3c47924 100644 --- a/packages/legacy/core/App/navigators/RootStack.tsx +++ b/packages/legacy/core/App/navigators/RootStack.tsx @@ -56,6 +56,7 @@ const RootStack: React.FC = () => { } = useConfiguration() const container = useContainer() const OnboardingStack = container.resolve(TOKENS.STACK_ONBOARDING) + const { version: TermsVersion } = container.resolve(TOKENS.SCREEN_TERMS) useDeepLinks() // remove connection on mobile verifier proofs if proof is rejected regardless of if it has been opened @@ -270,7 +271,7 @@ const RootStack: React.FC = () => { if ( (!showPreface || state.onboarding.didSeePreface) && - state.onboarding.didAgreeToTerms && + state.onboarding.didAgreeToTerms === TermsVersion && state.onboarding.didCompleteTutorial && state.onboarding.didCreatePIN && (!state.preferences.enableWalletNaming || state.onboarding.didNameWallet) && diff --git a/packages/legacy/core/App/screens/Splash.tsx b/packages/legacy/core/App/screens/Splash.tsx index e81c2802..75f45817 100644 --- a/packages/legacy/core/App/screens/Splash.tsx +++ b/packages/legacy/core/App/screens/Splash.tsx @@ -11,6 +11,7 @@ import { Config } from 'react-native-config' import { SafeAreaView } from 'react-native-safe-area-context' import { EventTypes, LocalStorageKeys } from '../constants' +import { TOKENS, useContainer } from '../container-api' import { useAnimatedComponents } from '../contexts/animated-components' import { useAuth } from '../contexts/auth' import { useConfiguration } from '../contexts/configuration' @@ -30,51 +31,54 @@ import { import { getAgentModules, createLinkSecretIfRequired } from '../utils/agent' import { migrateToAskar, didMigrateToAskar } from '../utils/migration' -const onboardingComplete = (state: StoreOnboardingState): boolean => { - return state.didCompleteTutorial && state.didAgreeToTerms && state.didCreatePIN && state.didConsiderBiometry +const onboardingComplete = (state: StoreOnboardingState, params: { termsVersion?: boolean | string }): boolean => { + const termsVer = params.termsVersion ?? true + return ( + state.didCompleteTutorial && state.didAgreeToTerms === termsVer && state.didCreatePIN && state.didConsiderBiometry + ) } const resumeOnboardingAt = ( state: StoreOnboardingState, - enableWalletNaming: boolean | undefined, - showPreface: boolean | undefined + params: { enableWalletNaming?: boolean; showPreface?: boolean; termsVersion?: boolean | string } ): Screens => { + const termsVer = params.termsVersion ?? true if ( - (state.didSeePreface || !showPreface) && + (state.didSeePreface || !params.showPreface) && state.didCompleteTutorial && - state.didAgreeToTerms && + state.didAgreeToTerms === termsVer && state.didCreatePIN && - (state.didNameWallet || !enableWalletNaming) && + (state.didNameWallet || !params.enableWalletNaming) && !state.didConsiderBiometry ) { return Screens.UseBiometry } if ( - (state.didSeePreface || !showPreface) && + (state.didSeePreface || !params.showPreface) && state.didCompleteTutorial && - state.didAgreeToTerms && + state.didAgreeToTerms === termsVer && state.didCreatePIN && - enableWalletNaming && + params.enableWalletNaming && !state.didNameWallet ) { return Screens.NameWallet } if ( - (state.didSeePreface || !showPreface) && + (state.didSeePreface || !params.showPreface) && state.didCompleteTutorial && - state.didAgreeToTerms && + state.didAgreeToTerms === termsVer && !state.didCreatePIN ) { return Screens.CreatePIN } - if ((state.didSeePreface || !showPreface) && state.didCompleteTutorial && !state.didAgreeToTerms) { + if ((state.didSeePreface || !params.showPreface) && state.didCompleteTutorial && state.didAgreeToTerms !== termsVer) { return Screens.Terms } - if (state.didSeePreface || !showPreface) { + if (state.didSeePreface || !params.showPreface) { return Screens.Onboarding } @@ -95,6 +99,8 @@ const Splash: React.FC = () => { const { getWalletCredentials } = useAuth() const { ColorPallet } = useTheme() const { LoadingIndicator } = useAnimatedComponents() + const container = useContainer() + const { version: TermsVersion } = container.resolve(TOKENS.SCREEN_TERMS) const styles = StyleSheet.create({ container: { flex: 1, @@ -159,7 +165,7 @@ const Splash: React.FC = () => { if (data) { const onboardingState = JSON.parse(data) as StoreOnboardingState dispatch({ type: DispatchAction.ONBOARDING_UPDATED, payload: [onboardingState] }) - if (onboardingComplete(onboardingState)) { + if (onboardingComplete(onboardingState, { termsVersion: TermsVersion })) { // if they previously completed onboarding before wallet naming was enabled, mark complete if (!store.onboarding.didNameWallet) { dispatch({ type: DispatchAction.DID_NAME_WALLET, payload: [true] }) @@ -193,7 +199,13 @@ const Splash: React.FC = () => { CommonActions.reset({ index: 0, routes: [ - { name: resumeOnboardingAt(onboardingState, store.preferences.enableWalletNaming, showPreface) }, + { + name: resumeOnboardingAt(onboardingState, { + enableWalletNaming: store.preferences.enableWalletNaming, + showPreface, + termsVersion: TermsVersion, + }), + }, ], }) ) diff --git a/packages/legacy/core/App/screens/Terms.tsx b/packages/legacy/core/App/screens/Terms.tsx index 82bbdf92..788d4b28 100644 --- a/packages/legacy/core/App/screens/Terms.tsx +++ b/packages/legacy/core/App/screens/Terms.tsx @@ -1,6 +1,6 @@ import { useNavigation } from '@react-navigation/core' import { StackNavigationProp } from '@react-navigation/stack' -import React, { useState } from 'react' +import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { ScrollView, StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' @@ -16,21 +16,25 @@ import { useTheme } from '../contexts/theme' import { AuthenticateStackParams, Screens } from '../types/navigators' import { testIdWithKey } from '../utils/testable' +export const TermsVersion = true + const Terms: React.FC = () => { - const [, dispatch] = useStore() + const [store, dispatch] = useStore() const [checked, setChecked] = useState(false) const { t } = useTranslation() const navigation = useNavigation>() const { OnboardingTheme, TextTheme } = useTheme() const container = useContainer() const Button = container.resolve(TOKENS.COMP_BUTTON) - const onSubmitPressed = () => { + const agreedToPreviousTerms = store.onboarding.didAgreeToTerms + const onSubmitPressed = useCallback(() => { dispatch({ type: DispatchAction.DID_AGREE_TO_TERMS, + payload: [{ DidAgreeToTerms: TermsVersion }], }) navigation.navigate(Screens.CreatePIN) - } + }, []) const style = StyleSheet.create({ container: { ...OnboardingTheme.container, @@ -81,32 +85,36 @@ const Terms: React.FC = () => { est laborum. - setChecked(!checked)} - /> + {!agreedToPreviousTerms && ( + setChecked(!checked)} + /> + )} @@ -174,6 +170,7 @@ const PushNotification: React.FC diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index 456d2a44..3a9df927 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -1,3 +1,5 @@ +import { CommonActions, useNavigation } from '@react-navigation/core' +import { StackNavigationProp } from '@react-navigation/stack' import React, { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { StyleSheet, Text, View, Modal, Switch, ScrollView, Pressable, DeviceEventEmitter } from 'react-native' @@ -7,9 +9,11 @@ import Button, { ButtonType } from '../components/buttons/Button' import { EventTypes } from '../constants' import { useAnimatedComponents } from '../contexts/animated-components' import { useAuth } from '../contexts/auth' +import { useConfiguration } from '../contexts/configuration' import { DispatchAction } from '../contexts/reducers/store' import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' +import { OnboardingStackParams, Screens } from '../types/navigators' import { testIdWithKey } from '../utils/testable' import PINEnter, { PINEntryUsage } from './PINEnter' @@ -22,6 +26,7 @@ enum UseBiometryUsage { const UseBiometry: React.FC = () => { const [store, dispatch] = useStore() const { t } = useTranslation() + const { enablePushNotifications } = useConfiguration() const { isBiometricsActive, commitPIN, disableBiometrics } = useAuth() const [biometryAvailable, setBiometryAvailable] = useState(false) const [biometryEnabled, setBiometryEnabled] = useState(store.preferences.useBiometry) @@ -29,6 +34,7 @@ const UseBiometry: React.FC = () => { const [canSeeCheckPIN, setCanSeeCheckPIN] = useState(false) const { ColorPallet, TextTheme, Assets } = useTheme() const { ButtonLoading } = useAnimatedComponents() + const navigation = useNavigation>() const screenUsage = store.onboarding.didConsiderBiometry ? UseBiometryUsage.ToggleOnOff : UseBiometryUsage.InitialSetup @@ -83,6 +89,14 @@ const UseBiometry: React.FC = () => { type: DispatchAction.USE_BIOMETRY, payload: [biometryEnabled], }) + if (enablePushNotifications) { + navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: Screens.UsePushNotifications }], + }) + ) + } } const toggleSwitch = () => { diff --git a/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx b/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx index 026090ae..998ef9d6 100644 --- a/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx +++ b/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx @@ -17,11 +17,14 @@ jest.mock('react-native-fs', () => ({})) jest.mock('@hyperledger/anoncreds-react-native', () => ({})) jest.mock('@hyperledger/aries-askar-react-native', () => ({})) jest.mock('@hyperledger/indy-vdr-react-native', () => ({})) +jest.mock('../../App/contexts/configuration', () => ({ + useConfiguration: jest.fn().mockReturnValue({ enablePushNotifications: { setup: jest.fn(), toggle: jest.fn(), status: jest.fn() } }), +})) describe('UseBiometry Screen', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-empty-function - jest.spyOn(global.console, 'error').mockImplementation(() => {}) + jest.spyOn(global.console, 'error').mockImplementation(() => { }) }) test('Renders correctly when biometry available', async () => { diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap index 065b20ee..e9e42a86 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/PushNotification.test.tsx.snap @@ -230,6 +230,7 @@ exports[`displays a push notification screen Push notification screen renders co PushNotifications.ReceiveNotifications Date: Thu, 21 Mar 2024 16:43:20 -0700 Subject: [PATCH 44/77] fix: missing svg image in settings (#1120) Signed-off-by: Bryce McMath --- packages/legacy/core/App/screens/Settings.tsx | 7 +------ .../screens/__snapshots__/Settings.test.tsx.snap | 10 +++++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/legacy/core/App/screens/Settings.tsx b/packages/legacy/core/App/screens/Settings.tsx index 12b379a0..d58eaadf 100644 --- a/packages/legacy/core/App/screens/Settings.tsx +++ b/packages/legacy/core/App/screens/Settings.tsx @@ -68,11 +68,6 @@ const Settings: React.FC = ({ navigation }) => { borderBottomColor: ColorPallet.brand.primaryBackground, marginHorizontal: 25, }, - logo: { - height: 64, - width: '50%', - marginVertical: 16, - }, footer: { marginVertical: 25, alignItems: 'center', @@ -365,7 +360,7 @@ const Settings: React.FC = ({ navigation }) => { {`${t('Settings.Version')} ${getVersion()} ${t('Settings.Build')} (${getBuildNumber()})`} - + diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/Settings.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/Settings.test.tsx.snap index 3ba167b7..932f2186 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/Settings.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/Settings.test.tsx.snap @@ -1002,9 +1002,13 @@ exports[`Settings Screen Renders correctly 1`] = ` Settings.Version 1 Settings.Build (1) < - height={64} - marginVertical={16} - width="50%" + height={75} + style={ + Object { + "alignSelf": "center", + } + } + width={150} /> From 0a4903ce04cb78a5bdabaf16d1b2d5e3d61d1c34 Mon Sep 17 00:00:00 2001 From: Mostafa Gamal <46829557+MosCD3@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:39:42 -0400 Subject: [PATCH 45/77] feat: Custom screen options dictionary: Onboarding stack only (#1079) Signed-off-by: OPS Signed-off-by: Mostafa Gamal <46829557+MosCD3@users.noreply.github.com> Signed-off-by: Mostafa Gamal Co-authored-by: OPS --- packages/legacy/app/ios/Podfile.lock | 8 ++-- packages/legacy/core/App/container-api.ts | 16 ++++++- packages/legacy/core/App/container-impl.ts | 2 + .../core/App/navigators/OnboardingStack.tsx | 42 +++++------------- .../App/navigators/defaultStackOptions.tsx | 43 ++++++++++++++++++- packages/legacy/core/App/types/navigators.ts | 3 ++ 6 files changed, 76 insertions(+), 38 deletions(-) diff --git a/packages/legacy/app/ios/Podfile.lock b/packages/legacy/app/ios/Podfile.lock index e362e478..f50b1213 100644 --- a/packages/legacy/app/ios/Podfile.lock +++ b/packages/legacy/app/ios/Podfile.lock @@ -735,14 +735,14 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: anoncreds: 8e6ab626d5250ae6301c3e96d6fc739186e083f0 aries-askar: 738c677e194913ed3c256adc953db3fe0494f8f8 - boost: 57d2868c099736d80fcd648bf211b4431e51a558 + boost: a7c83b31436843459a1961bfd74b96033dc77234 CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 + DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 FBLazyVector: 71803c074f6325f10b5ec891c443b6bbabef0ca7 FBReactNativeSpec: 448e08a759d29a96e15725ae532445bf4343567c fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 5337263514dd6f09803962437687240c5dc39aa4 hermes-engine: f6cf92a471053245614d9d8097736f6337d5b86c indy-vdr: 85cd66089f151256581323440e78988891f4082e libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 @@ -806,4 +806,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 91c24b25407631a4023c96a168470f60d473e28f -COCOAPODS: 1.13.0 +COCOAPODS: 1.15.2 diff --git a/packages/legacy/core/App/container-api.ts b/packages/legacy/core/App/container-api.ts index bc9aad23..12830fd6 100644 --- a/packages/legacy/core/App/container-api.ts +++ b/packages/legacy/core/App/container-api.ts @@ -6,7 +6,7 @@ import { Button } from './components/buttons/Button-api' import { ReducerAction } from './contexts/reducers/store' import Onboarding from './screens/Onboarding' import { GenericFn } from './types/fn' -import { AuthenticateStackParams } from './types/navigators' +import { AuthenticateStackParams, ScreenOptionsType } from './types/navigators' export enum SCREEN_TOKENS { SCREEN_PREFACE = 'screen.preface', @@ -30,7 +30,18 @@ export enum SERVICE_TOKENS { SERVICE_TERMS = 'screen.terms', } -export const TOKENS = { ...SCREEN_TOKENS, ...SERVICE_TOKENS, ...STACK_TOKENS, ...FN_TOKENS, ...COMP_TOKENS } +export enum OBJECT_TOKENS { + OBJECT_ONBOARDINGCONFIG = 'object.onboarding-config', +} + +export const TOKENS = { + ...SCREEN_TOKENS, + ...SERVICE_TOKENS, + ...STACK_TOKENS, + ...FN_TOKENS, + ...COMP_TOKENS, + ...OBJECT_TOKENS, +} export type FN_ONBOARDING_DONE = ( dispatch: React.Dispatch>, @@ -45,6 +56,7 @@ export interface TokenMapping { [TOKENS.SCREEN_ONBOARDING]: typeof Onboarding [TOKENS.FN_ONBOARDING_DONE]: FN_ONBOARDING_DONE [TOKENS.COMP_BUTTON]: Button + [TOKENS.OBJECT_ONBOARDINGCONFIG]: ScreenOptionsType } export interface Container { diff --git a/packages/legacy/core/App/container-impl.ts b/packages/legacy/core/App/container-impl.ts index 0ab05997..8e08a434 100644 --- a/packages/legacy/core/App/container-impl.ts +++ b/packages/legacy/core/App/container-impl.ts @@ -6,6 +6,7 @@ import Button from './components/buttons/Button' import { TOKENS, Container, TokenMapping } from './container-api' import { DispatchAction, ReducerAction } from './contexts/reducers/store' import OnboardingStack from './navigators/OnboardingStack' +import { DefaultScreenOptionsDictionary } from './navigators/defaultStackOptions' import Developer from './screens/Developer' import Onboarding from './screens/Onboarding' import Preface from './screens/Preface' @@ -27,6 +28,7 @@ export class MainContainer implements Container { this.container.registerInstance(TOKENS.SCREEN_ONBOARDING, Onboarding) this.container.registerInstance(TOKENS.STACK_ONBOARDING, OnboardingStack) this.container.registerInstance(TOKENS.COMP_BUTTON, Button) + this.container.registerInstance(TOKENS.OBJECT_ONBOARDINGCONFIG, DefaultScreenOptionsDictionary) this.container.registerInstance( TOKENS.FN_ONBOARDING_DONE, diff --git a/packages/legacy/core/App/navigators/OnboardingStack.tsx b/packages/legacy/core/App/navigators/OnboardingStack.tsx index e6c36c28..293cd48a 100644 --- a/packages/legacy/core/App/navigators/OnboardingStack.tsx +++ b/packages/legacy/core/App/navigators/OnboardingStack.tsx @@ -20,7 +20,6 @@ import { createCarouselStyle } from '../screens/OnboardingPages' import PINCreate from '../screens/PINCreate' import PushNotification from '../screens/PushNotification' import { AuthenticateStackParams, Screens } from '../types/navigators' -import { testIdWithKey } from '../utils/testable' import { createDefaultStackOptions } from './defaultStackOptions' @@ -49,6 +48,7 @@ const OnboardingStack: React.FC = () => { const onTutorialCompleted = container.resolve(TOKENS.FN_ONBOARDING_DONE)(dispatch, navigation) const { screen: Terms } = container.resolve(TOKENS.SCREEN_TERMS) const Developer = container.resolve(TOKENS.SCREEN_DEVELOPER) + const ScreenOptionsDictionary = container.resolve(TOKENS.OBJECT_ONBOARDINGCONFIG) const Preface = container.resolve(TOKENS.SCREEN_PREFACE) const onAuthenticated = (status: boolean): void => { @@ -83,38 +83,31 @@ const OnboardingStack: React.FC = () => { component: Preface, options: () => { return { + ...ScreenOptionsDictionary[Screens.Preface], title: t('Screens.Preface'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, - headerLeft: () => false, } }, }, { name: Screens.Splash, component: splash, + options: ScreenOptionsDictionary[Screens.Splash], }, { name: Screens.Onboarding, component: OnBoardingScreen, options: () => { return { + ...ScreenOptionsDictionary[Screens.Onboarding], title: t('Screens.Onboarding'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, - gestureEnabled: false, - headerLeft: () => false, } }, }, { name: Screens.Terms, options: () => ({ + ...ScreenOptionsDictionary[Screens.Terms], title: t('Screens.Terms'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, - headerLeft: () => false, - rightLeft: () => false, }), component: Terms, }, @@ -123,42 +116,31 @@ const OnboardingStack: React.FC = () => { component: CreatePINScreen, initialParams: {}, options: () => ({ + ...ScreenOptionsDictionary[Screens.CreatePIN], title: t('Screens.CreatePIN'), - headerShown: true, - headerLeft: () => false, - rightLeft: () => false, }), }, { name: Screens.NameWallet, options: () => ({ + ...ScreenOptionsDictionary[Screens.CreatePIN], title: t('Screens.NameWallet'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, - headerLeft: () => false, - rightLeft: () => false, }), component: NameWallet, }, { name: Screens.UseBiometry, options: () => ({ + ...ScreenOptionsDictionary[Screens.CreatePIN], title: t('Screens.Biometry'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, - headerLeft: () => false, - rightLeft: () => false, }), component: useBiometry, }, { name: Screens.UsePushNotifications, options: () => ({ + ...ScreenOptionsDictionary[Screens.CreatePIN], title: t('Screens.UsePushNotifications'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, - headerLeft: () => false, - rightLeft: () => false, }), component: PushNotification, }, @@ -167,18 +149,16 @@ const OnboardingStack: React.FC = () => { component: Developer, options: () => { return { + ...ScreenOptionsDictionary[Screens.Developer], title: t('Screens.Developer'), - headerTintColor: OnboardingTheme.headerTintColor, - headerShown: true, headerBackAccessibilityLabel: t('Global.Back'), - headerBackTestID: testIdWithKey('Back'), } }, }, ] return ( - + {screens.map((item) => { return })} diff --git a/packages/legacy/core/App/navigators/defaultStackOptions.tsx b/packages/legacy/core/App/navigators/defaultStackOptions.tsx index f6550e44..10a366ab 100644 --- a/packages/legacy/core/App/navigators/defaultStackOptions.tsx +++ b/packages/legacy/core/App/navigators/defaultStackOptions.tsx @@ -4,7 +4,48 @@ import { useTranslation } from 'react-i18next' import HeaderTitle from '../components/texts/HeaderTitle' import { useConfiguration } from '../contexts/configuration' -import { ITheme } from '../theme' +import { ITheme, OnboardingTheme } from '../theme' +import { ScreenOptionsType, Screens } from '../types/navigators' +import { testIdWithKey } from '../utils/testable' + +export const DefaultScreenOptionsDictionary: ScreenOptionsType = { + [Screens.Preface]: { + headerTintColor: OnboardingTheme.headerTintColor, + headerLeft: () => false, + }, + [Screens.Splash]: { + headerShown: false, + }, + [Screens.Onboarding]: { + headerTintColor: OnboardingTheme.headerTintColor, + gestureEnabled: false, + headerLeft: () => false, + }, + [Screens.Terms]: { + headerTintColor: OnboardingTheme.headerTintColor, + headerLeft: () => false, + }, + [Screens.CreatePIN]: { + headerLeft: () => false, + }, + [Screens.NameWallet]: { + headerTintColor: OnboardingTheme.headerTintColor, + headerLeft: () => false, + }, + [Screens.UseBiometry]: { + headerTintColor: OnboardingTheme.headerTintColor, + headerLeft: () => false, + }, + [Screens.Developer]: { + headerTintColor: OnboardingTheme.headerTintColor, + headerShown: false, + headerBackTestID: testIdWithKey('Back'), + }, + [Screens.UsePushNotifications]: { + headerTintColor: OnboardingTheme.headerTintColor, + headerLeft: () => false, + }, +} export function createDefaultStackOptions({ ColorPallet }: ITheme): StackNavigationOptions { const { t } = useTranslation() diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index 521a94e2..3ebbfc65 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -1,5 +1,6 @@ import { CredentialExchangeRecord } from '@aries-framework/core' import { NavigatorScreenParams } from '@react-navigation/core' +import { StackNavigationOptions } from '@react-navigation/stack' export enum Screens { AttemptLockout = 'Temporarily Locked', @@ -164,3 +165,5 @@ export type DeliveryStackParams = { [Screens.Declined]: { credentialId: string } [Screens.Chat]: { connectionId: string } } + +export type ScreenOptionsType = Partial> From 29c94f41f53f252687eab6a6cfed6094817cbc6f Mon Sep 17 00:00:00 2001 From: Bryce McMath <32586431+bryce-mcmath@users.noreply.github.com> Date: Tue, 26 Mar 2024 09:32:09 -0700 Subject: [PATCH 46/77] feat: change proof req buttons and add new modal (#1122) Signed-off-by: Bryce McMath --- .../App/assets/img/no_information_shared.svg | 36 ++++ .../components/modals/ProofCancelModal.tsx | 79 +++++++++ .../legacy/core/App/localization/en/index.ts | 2 + .../legacy/core/App/localization/fr/index.ts | 2 + .../core/App/localization/pt-br/index.ts | 2 + .../App/navigators/defaultStackOptions.tsx | 1 - .../legacy/core/App/screens/ProofRequest.tsx | 87 +++++++--- packages/legacy/core/App/theme.ts | 3 + .../components/ProofCancelModal.test.tsx | 24 +++ .../ProofCancelModal.test.tsx.snap | 158 ++++++++++++++++++ .../__tests__/screens/ProofRequest.test.tsx | 27 ++- 11 files changed, 378 insertions(+), 43 deletions(-) create mode 100644 packages/legacy/core/App/assets/img/no_information_shared.svg create mode 100644 packages/legacy/core/App/components/modals/ProofCancelModal.tsx create mode 100644 packages/legacy/core/__tests__/components/ProofCancelModal.test.tsx create mode 100644 packages/legacy/core/__tests__/components/__snapshots__/ProofCancelModal.test.tsx.snap diff --git a/packages/legacy/core/App/assets/img/no_information_shared.svg b/packages/legacy/core/App/assets/img/no_information_shared.svg new file mode 100644 index 00000000..517ea508 --- /dev/null +++ b/packages/legacy/core/App/assets/img/no_information_shared.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/legacy/core/App/components/modals/ProofCancelModal.tsx b/packages/legacy/core/App/components/modals/ProofCancelModal.tsx new file mode 100644 index 00000000..3f3c1c70 --- /dev/null +++ b/packages/legacy/core/App/components/modals/ProofCancelModal.tsx @@ -0,0 +1,79 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { Modal, ScrollView, StyleSheet, View, Text } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' + +import { useTheme } from '../../contexts/theme' +import { GenericFn } from '../../types/fn' +import { testIdWithKey } from '../../utils/testable' +import Button, { ButtonType } from '../buttons/Button' + +interface ProofCancelModalProps { + visible?: boolean + onDone?: GenericFn +} + +const ProofCancelModal: React.FC = ({ visible, onDone }) => { + const { t } = useTranslation() + const { ColorPallet, TextTheme, Assets } = useTheme() + + const styles = StyleSheet.create({ + safeAreaView: { + backgroundColor: ColorPallet.brand.modalPrimaryBackground, + flex: 1, + }, + container: { + flexGrow: 1, + padding: 20, + }, + controlsContainer: { + marginHorizontal: 20, + marginBottom: 10, + }, + content: { + justifyContent: 'space-around', + }, + heading: { + ...TextTheme.modalHeadingOne, + marginTop: 60, + marginBottom: 30, + textAlign: 'center', + }, + image: { + alignSelf: 'center', + marginBottom: 50, + }, + subtext: { + ...TextTheme.modalNormal, + fontSize: 22, + textAlign: 'center', + }, + }) + + return ( + + + + + {t('ProofRequest.NoInfoShared')} + + {t('ProofRequest.YourInfo')} + + + + + + {/* {/*