diff --git a/packages/legacy/core/App/components/listItems/ContactListItem.tsx b/packages/legacy/core/App/components/listItems/ContactListItem.tsx index 4e6216d452..d385be4492 100644 --- a/packages/legacy/core/App/components/listItems/ContactListItem.tsx +++ b/packages/legacy/core/App/components/listItems/ContactListItem.tsx @@ -11,6 +11,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { View, StyleSheet, TouchableOpacity, Image, Text } from 'react-native' +import { useStore } from '../../contexts/store' import { useTheme } from '../../contexts/theme' import { useCredentialsByConnectionId } from '../../hooks/credentials' import { useProofsByConnectionId } from '../../hooks/proofs' @@ -18,6 +19,7 @@ import { Role } from '../../types/chat' import { ContactStackParams, Screens, Stacks } from '../../types/navigators' import { formatTime, + getConnectionName, getCredentialEventLabel, getCredentialEventRole, getProofEventLabel, @@ -41,6 +43,7 @@ const ContactListItem: React.FC = ({ contact, navigation }) => { const credentials = useCredentialsByConnectionId(contact.id) const proofs = useProofsByConnectionId(contact.id) const [message, setMessage] = useState({ text: '', createdAt: contact.createdAt }) + const [store] = useStore() const styles = StyleSheet.create({ container: { @@ -132,8 +135,14 @@ const ContactListItem: React.FC = ({ contact, navigation }) => { ?.navigate(Stacks.ContactStack, { screen: Screens.Chat, params: { connectionId: contact.id } }) }, [contact]) - const contactLabel = useMemo(() => contact.alias || contact.theirLabel, [contact]) - const contactLabelAbbr = useMemo(() => contactLabel?.charAt(0).toUpperCase(), [contact]) + const contactLabel = useMemo( + () => getConnectionName(contact, store.preferences.alternateContactNames), + [contact, store.preferences.alternateContactNames] + ) + const contactLabelAbbr = useMemo( + () => contactLabel?.charAt(0).toUpperCase(), + [contact, store.preferences.alternateContactNames] + ) return ( { const NotificationListItem: React.FC = ({ notificationType, notification }) => { const navigation = useNavigation>() const { customNotification } = useConfiguration() - const [, dispatch] = useStore() + const [store, dispatch] = useStore() const { t } = useTranslation() const { ColorPallet, TextTheme } = useTheme() const { agent } = useAgent() @@ -224,14 +224,14 @@ const NotificationListItem: React.FC = ({ notificatio const detailsForNotificationType = async (notificationType: NotificationType): Promise => { return new Promise((resolve) => { + const theirLabel = getConnectionName(connection, store.preferences.alternateContactNames) + switch (notificationType) { case NotificationType.BasicMessage: resolve({ type: InfoBoxType.Info, title: t('Home.NewMessage'), - body: connection?.theirLabel - ? `${connection.theirLabel} ${t('Home.SentMessage')}` - : t('Home.ReceivedMessage'), + body: theirLabel ? `${theirLabel} ${t('Home.SentMessage')}` : t('Home.ReceivedMessage'), buttonTitle: t('Home.ViewMessage'), }) break diff --git a/packages/legacy/core/App/contexts/reducers/store.ts b/packages/legacy/core/App/contexts/reducers/store.ts index 7169232b9b..a36f6c2466 100644 --- a/packages/legacy/core/App/contexts/reducers/store.ts +++ b/packages/legacy/core/App/contexts/reducers/store.ts @@ -46,6 +46,7 @@ enum PreferencesDispatchAction { ACCEPT_DEV_CREDENTIALS = 'preferences/acceptDevCredentials', USE_DATA_RETENTION = 'preferences/useDataRetention', PREVENT_AUTO_LOCK = 'preferences/preventAutoLock', + UPDATE_ALTERNATE_CONTACT_NAMES = 'preferences/updateAlternateContactNames', } enum ToursDispatchAction { @@ -212,6 +213,10 @@ export const reducer = (state: S, action: ReducerAction(state: S, action: ReducerAction { title: t('Screens.ContactDetails'), }} /> + = ({ route }) => { const basicMessages = useBasicMessagesByConnectionId(connectionId) const credentials = useCredentialsByConnectionId(connectionId) const proofs = useProofsByConnectionId(connectionId) - const theirLabel = useMemo(() => connection?.theirLabel || connection?.id || '', [connection]) + const isFocused = useIsFocused() const { assertConnectedNetwork, silentAssertConnectedNetwork } = useNetwork() const [messages, setMessages] = useState>([]) const [showActionSlider, setShowActionSlider] = useState(false) const { ChatTheme: theme, Assets } = useTheme() const { ColorPallet } = useTheme() + const [theirLabel, setTheirLabel] = useState(getConnectionName(connection, store.preferences.alternateContactNames)) + + // This useEffect is for properly rendering changes to the alt contact name, useMemo did not pick them up + useEffect(() => { + setTheirLabel(getConnectionName(connection, store.preferences.alternateContactNames)) + }, [isFocused, connection, store.preferences.alternateContactNames]) useMemo(() => { assertConnectedNetwork() @@ -70,7 +77,7 @@ const Chat: React.FC = ({ route }) => { title: theirLabel, headerRight: () => , }) - }, [connection]) + }, [connection, theirLabel]) // when chat is open, mark messages as seen useEffect(() => { @@ -259,7 +266,7 @@ const Chat: React.FC = ({ route }) => { ? [...transformedMessages.sort((a: any, b: any) => b.createdAt - a.createdAt), connectedMessage] : transformedMessages.sort((a: any, b: any) => b.createdAt - a.createdAt) ) - }, [basicMessages, credentials, proofs]) + }, [basicMessages, credentials, proofs, theirLabel]) const onSend = useCallback( async (messages: IMessage[]) => { diff --git a/packages/legacy/core/App/screens/ContactDetails.tsx b/packages/legacy/core/App/screens/ContactDetails.tsx index b40fd869d4..b58848b6dc 100644 --- a/packages/legacy/core/App/screens/ContactDetails.tsx +++ b/packages/legacy/core/App/screens/ContactDetails.tsx @@ -1,23 +1,23 @@ import { CredentialState } from '@aries-framework/core' import { useAgent, useConnectionById, useCredentialByState } from '@aries-framework/react-hooks' -import { Attribute } from '@hyperledger/aries-oca/build/legacy' import { useNavigation } from '@react-navigation/core' import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack' -import React, { useCallback, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { DeviceEventEmitter } from 'react-native' +import { DeviceEventEmitter, View, Text, TouchableOpacity, StyleSheet } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Toast from 'react-native-toast-message' import CommonRemoveModal from '../components/modals/CommonRemoveModal' -import RecordRemove from '../components/record/RecordRemove' import { ToastType } from '../components/toast/BaseToast' import { EventTypes } from '../constants' -import { useConfiguration } from '../contexts/configuration' +import { useStore } from '../contexts/store' +import { useTheme } from '../contexts/theme' import { BifoldError } from '../types/error' import { ContactStackParams, Screens, TabStacks } from '../types/navigators' import { ModalUsage } from '../types/remove' -import { formatTime } from '../utils/helpers' +import { formatTime, getConnectionName } from '../utils/helpers' +import { testIdWithKey } from '../utils/testable' type ContactDetailsProps = StackScreenProps @@ -34,7 +34,15 @@ const ContactDetails: React.FC = ({ route }) => { ...useCredentialByState(CredentialState.CredentialReceived), ...useCredentialByState(CredentialState.Done), ].filter((credential) => credential.connectionId === connection?.id) - const { record } = useConfiguration() + const { ColorPallet, TextTheme } = useTheme() + const [store] = useStore() + + const styles = StyleSheet.create({ + contentContainer: { + padding: 20, + backgroundColor: ColorPallet.brand.secondaryBackground, + }, + }) const handleOnRemove = () => { if (connectionCredentials?.length) { @@ -79,25 +87,52 @@ const ContactDetails: React.FC = ({ route }) => { setIsCredentialsRemoveModalDisplayed(false) } + const handleGoToRename = () => { + navigation.navigate(Screens.RenameContact, { connectionId }) + } + + const callGoToRename = useCallback(() => handleGoToRename(), []) const callOnRemove = useCallback(() => handleOnRemove(), []) const callSubmitRemove = useCallback(() => handleSubmitRemove(), []) const callCancelRemove = useCallback(() => handleCancelRemove(), []) const callGoToCredentials = useCallback(() => handleGoToCredentials(), []) const callCancelUnableToRemove = useCallback(() => handleCancelUnableRemove(), []) + const contactLabel = useMemo( + () => getConnectionName(connection, store.preferences.alternateContactNames), + [connection, store.preferences.alternateContactNames] + ) + return ( - {record({ - fields: [ - { - name: connection?.alias || connection?.theirLabel, - value: t('ContactDetails.DateOfConnection', { - date: connection?.createdAt ? formatTime(connection.createdAt, { includeHour: true }) : '', - }), - }, - ] as Attribute[], - footer: () => , - })} + + {contactLabel} + + {t('ContactDetails.DateOfConnection', { + date: connection?.createdAt ? formatTime(connection.createdAt, { includeHour: true }) : '', + })} + + + + {t('Screens.RenameContact')} + + + + {t('ContactDetails.RemoveContact')} + + @@ -41,6 +42,7 @@ const VerifiedProof: React.FC = ({ }: VerifiedProofProps) => { const { t } = useTranslation() const { ColorPallet, TextTheme } = useTheme() + const [store] = useStore() const styles = StyleSheet.create({ container: { @@ -98,8 +100,11 @@ const VerifiedProof: React.FC = ({ const connection = useConnectionById(record.connectionId || '') const connectionLabel = useMemo( - () => (connection ? connection?.alias || connection?.theirLabel : t('Verifier.ConnectionLessLabel')), - [connection] + () => + connection + ? getConnectionName(connection, store.preferences.alternateContactNames) + : t('Verifier.ConnectionLessLabel'), + [connection, store.preferences.alternateContactNames] ) const [sharedProofDataItems, setSharedProofDataItems] = useState([]) diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 3d37a8625c..d2601a309c 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -43,7 +43,12 @@ import { ModalUsage } from '../types/remove' import { TourID } from '../types/tour' import { useAppAgent } from '../utils/agent' import { getCredentialIdentifiers } from '../utils/credential' -import { mergeAttributesAndPredicates, processProofAttributes, processProofPredicates } from '../utils/helpers' +import { + getConnectionName, + mergeAttributesAndPredicates, + processProofAttributes, + processProofPredicates, +} from '../utils/helpers' import { testIdWithKey } from '../utils/testable' import ProofRequestAccept from './ProofRequestAccept' @@ -63,7 +68,6 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const fullCredentials = useCredentials().records const proof = useProofById(proofId) const connection = proof?.connectionId ? useConnectionById(proof.connectionId) : undefined - const proofConnectionLabel = connection?.theirLabel ?? proof?.connectionId ?? '' const [pendingModalVisible, setPendingModalVisible] = useState(false) const [revocationOffense, setRevocationOffense] = useState(false) const [retrievedCredentials, setRetrievedCredentials] = useState() @@ -76,6 +80,10 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const { enableTours: enableToursConfig, OCABundleResolver } = useConfiguration() const [containsPI, setContainsPI] = useState(false) const [store, dispatch] = useStore() + const proofConnectionLabel = useMemo( + () => getConnectionName(connection, store.preferences.alternateContactNames), + [connection, store.preferences.alternateContactNames] + ) const { start } = useTour() const screenIsFocused = useIsFocused() diff --git a/packages/legacy/core/App/screens/ProofRequestUsageHistory.tsx b/packages/legacy/core/App/screens/ProofRequestUsageHistory.tsx index e55bc55313..1fe39f4e7a 100644 --- a/packages/legacy/core/App/screens/ProofRequestUsageHistory.tsx +++ b/packages/legacy/core/App/screens/ProofRequestUsageHistory.tsx @@ -9,9 +9,10 @@ import Icon from 'react-native-vector-icons/MaterialIcons' import { useProofsByTemplateId, isPresentationReceived } from '../../verifier' import EmptyList from '../components/misc/EmptyList' +import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' import { ProofRequestsStackParams, Screens } from '../types/navigators' -import { formatTime } from '../utils/helpers' +import { formatTime, getConnectionName } from '../utils/helpers' import { testIdWithKey } from '../utils/testable' type ProofRequestUsageHistoryProps = StackScreenProps @@ -40,8 +41,12 @@ const getPresentationStateLabel = (record: ProofExchangeRecord) => { const ProofRequestUsageHistoryRecord: React.FC = ({ record, navigation }) => { const { t } = useTranslation() const { ListItems, ColorPallet } = useTheme() - + const [store] = useStore() const connection = record.connectionId ? useConnectionById(record.connectionId) : undefined + const theirLabel = useMemo( + () => getConnectionName(connection, store.preferences.alternateContactNames), + [connection, store.preferences.alternateContactNames] + ) const style = StyleSheet.create({ card: { @@ -101,7 +106,7 @@ const ProofRequestUsageHistoryRecord: React.FC {t('Verifier.PresentationFrom')}: - {connection?.theirLabel || t('Verifier.ConnectionlessPresentation')} + {theirLabel || t('Verifier.ConnectionlessPresentation')} {t('Verifier.PresentationState')}: diff --git a/packages/legacy/core/App/screens/RenameContact.tsx b/packages/legacy/core/App/screens/RenameContact.tsx new file mode 100644 index 0000000000..3f6793996d --- /dev/null +++ b/packages/legacy/core/App/screens/RenameContact.tsx @@ -0,0 +1,156 @@ +import { useConnectionById } from '@aries-framework/react-hooks' +import { useNavigation } from '@react-navigation/core' +import { StackScreenProps } from '@react-navigation/stack' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { StyleSheet, Text, View } from 'react-native' + +import ButtonLoading from '../components/animated/ButtonLoading' +import Button, { ButtonType } from '../components/buttons/Button' +import LimitedTextInput from '../components/inputs/LimitedTextInput' +import { InfoBoxType } from '../components/misc/InfoBox' +import PopupModal from '../components/modals/PopupModal' +import KeyboardView from '../components/views/KeyboardView' +import { DispatchAction } from '../contexts/reducers/store' +import { useStore } from '../contexts/store' +import { useTheme } from '../contexts/theme' +import { ContactStackParams, Screens } from '../types/navigators' +import { getConnectionName } from '../utils/helpers' +import { testIdWithKey } from '../utils/testable' + +type ErrorState = { + visible: boolean + title: string + description: string +} + +type RenameContactProps = StackScreenProps + +const RenameContact: React.FC = ({ route }) => { + const { connectionId } = route.params + const connection = useConnectionById(connectionId) + const { t } = useTranslation() + const { ColorPallet, TextTheme } = useTheme() + const navigation = useNavigation() + const [store, dispatch] = useStore() + const [contactName, setContactName] = useState(getConnectionName(connection, store.preferences.alternateContactNames)) + const [loading, setLoading] = useState(false) + const [errorState, setErrorState] = useState({ + visible: false, + title: '', + description: '', + }) + + const styles = StyleSheet.create({ + screenContainer: { + height: '100%', + backgroundColor: ColorPallet.brand.primaryBackground, + padding: 20, + justifyContent: 'space-between', + }, + + contentContainer: { + justifyContent: 'center', + alignItems: 'center', + width: '100%', + }, + // below used as helpful label for view, no properties needed atp + controlsContainer: {}, + + buttonContainer: { + width: '100%', + }, + }) + + const handleChangeText = (text: string) => { + setContactName(text) + } + + const handleCancelPressed = () => { + navigation.goBack() + } + + const handleContinuePressed = () => { + if (contactName.length < 1) { + setErrorState({ + title: t('RenameContact.EmptyNameTitle'), + description: t('RenameContact.EmptyNameDescription'), + visible: true, + }) + } else if (contactName.length > 50) { + setErrorState({ + title: t('RenameContact.CharCountTitle'), + description: t('RenameContact.CharCountDescription'), + visible: true, + }) + } else { + setLoading(true) + dispatch({ + type: DispatchAction.UPDATE_ALTERNATE_CONTACT_NAMES, + payload: [{ [connectionId]: contactName }], + }) + setLoading(false) + navigation.goBack() + } + } + + const handleDismissError = () => { + setErrorState((prev) => ({ ...prev, visible: false })) + } + + return ( + + + + + {t('RenameContact.ThisContactName')} + + + + + + + + + +