Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: option to rename contacts #980

Merged
merged 3 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ 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'
import { Role } from '../../types/chat'
import { ContactStackParams, Screens, Stacks } from '../../types/navigators'
import {
formatTime,
getConnectionName,
getCredentialEventLabel,
getCredentialEventRole,
getProofEventLabel,
Expand All @@ -41,6 +43,7 @@ const ContactListItem: React.FC<Props> = ({ contact, navigation }) => {
const credentials = useCredentialsByConnectionId(contact.id)
const proofs = useProofsByConnectionId(contact.id)
const [message, setMessage] = useState<CondensedMessage>({ text: '', createdAt: contact.createdAt })
const [store] = useStore()

const styles = StyleSheet.create({
container: {
Expand Down Expand Up @@ -132,8 +135,14 @@ const ContactListItem: React.FC<Props> = ({ 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 (
<TouchableOpacity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { GenericFn } from '../../types/fn'
import { BasicMessageMetadata, basicMessageCustomMetadata } from '../../types/metadata'
import { HomeStackParams, Screens, Stacks } from '../../types/navigators'
import { ModalUsage } from '../../types/remove'
import { parsedSchema } from '../../utils/helpers'
import { getConnectionName, parsedSchema } from '../../utils/helpers'
import { testIdWithKey } from '../../utils/testable'
import Button, { ButtonType } from '../buttons/Button'
import { InfoBoxType } from '../misc/InfoBox'
Expand Down Expand Up @@ -71,7 +71,7 @@ const markMessageAsSeen = async (agent: Agent, record: BasicMessageRecord) => {
const NotificationListItem: React.FC<NotificationListItemProps> = ({ notificationType, notification }) => {
const navigation = useNavigation<StackNavigationProp<HomeStackParams>>()
const { customNotification } = useConfiguration()
const [, dispatch] = useStore()
const [store, dispatch] = useStore()
const { t } = useTranslation()
const { ColorPallet, TextTheme } = useTheme()
const { agent } = useAgent()
Expand Down Expand Up @@ -224,14 +224,14 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({ notificatio

const detailsForNotificationType = async (notificationType: NotificationType): Promise<DisplayDetails> => {
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
Expand Down
23 changes: 23 additions & 0 deletions packages/legacy/core/App/contexts/reducers/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -212,6 +213,10 @@ export const reducer = <S extends State>(state: S, action: ReducerAction<Dispatc
preferences.useDataRetention = true
}

if (!preferences.alternateContactNames) {
preferences.alternateContactNames = {}
}

return {
...state,
preferences,
Expand Down Expand Up @@ -255,6 +260,24 @@ export const reducer = <S extends State>(state: S, action: ReducerAction<Dispatc
preferences,
}
}
case PreferencesDispatchAction.UPDATE_ALTERNATE_CONTACT_NAMES: {
const idNamePair = (action?.payload ?? []).pop() ?? {}
const preferences = {
...state.preferences,
alternateContactNames: {
...state.preferences.alternateContactNames,
...idNamePair,
},
}
const newState = {
...state,
preferences,
}

AsyncStorage.setItem(LocalStorageKeys.Preferences, JSON.stringify(preferences))

return newState
}
case ToursDispatchAction.UPDATE_SEEN_TOUR_PROMPT: {
const seenToursPrompt: ToursState = (action?.payload ?? []).pop() ?? false
const tours = {
Expand Down
1 change: 1 addition & 0 deletions packages/legacy/core/App/contexts/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const defaultState: State = {
enableWalletNaming: false,
walletName: generateRandomWalletName(),
preventAutoLock: false,
alternateContactNames: {},
},
tours: {
seenToursPrompt: false,
Expand Down
9 changes: 9 additions & 0 deletions packages/legacy/core/App/localization/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ const translation = {
"CustomNotification": 'Custom Notification',
"ProofRequesting": 'Proof Requesting',
"NameWallet": "Name your wallet",
"RenameContact": "Edit Contact Name",
},
"Loading": {
"TakingTooLong": "This is taking longer than usual. You can return to home or continue waiting.",
Expand All @@ -554,6 +555,14 @@ const translation = {
"EmptyNameTitle": "Wallet name can't be empty",
"EmptyNameDescription": "This is the name people see when connecting with you.\n\nPlease enter a wallet name.",
},
"RenameContact": {
"ThisContactName": "This contact name is only displayed in your Contacts List.",
"ContactName": "Contact name",
"CharCountTitle": "Character count exceeded",
"CharCountDescription": "You've exceeded the maximum character count of 50 characters. Please reduce your character count.",
"EmptyNameTitle": "Contact name can't be empty",
"EmptyNameDescription": "This is the name you will see for this Contact in your Contacts list.\n\nPlease enter a contact name.",
},
"NetInfo": {
"NoInternetConnectionTitle": "No internet connection",
"NoInternetConnectionMessage": "You're unable to access services using Bifold or receive credentials until you're back online.\n\nPlease check your internet connection.",
Expand Down
7 changes: 6 additions & 1 deletion packages/legacy/core/App/localization/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,8 @@ const translation = {
"UseBiometry": 'Utiliser la biométrie',
"CustomNotification": 'Notification personnalisée',
"ProofRequesting": 'Demande de preuve',
"NameWallet": "Name your wallet (FR)"
"NameWallet": "Name your wallet (FR)",
"RenameContact": "Edit Contact Name (FR)",
},
"Loading": {
"TakingTooLong": "Cela prend plus de temps que d'habitude. Vous pouvez retourner à l'accueil ou continuer à attendre.",
Expand All @@ -541,6 +542,10 @@ const translation = {
"EmptyNameTitle": "Wallet name can't be empty (FR)",
"EmptyNameDescription": "This is the name people see when connecting with you.\n\nPlease enter a wallet name. (FR)",
},
"RenameContact": {
"ThisContactName": "This contact name is only displayed in your Contacts List. (FR)",
"ContactName": "Contact name (FR)",
},
"NetInfo": {
"NoInternetConnectionTitle": "Aucune connexion Internet",
"NoInternetConnectionMessage": "Vous ne pouvez pas accéder aux services à l'aide de Bifold ou recevoir des informations d'identification tant que vous n'êtes pas de nouveau en ligne.\n\nS'il vous plait, vérifiez votre connexion internet.",
Expand Down
5 changes: 5 additions & 0 deletions packages/legacy/core/App/localization/pt-br/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ const translation = {
"ProofRequestUsageHistory": "Histórico de uso",
"CreateConnectionInvitation": "Criar um convite de conexão",
"NameWallet": "Nomear sua carteira",
"RenameContact": "Edit Contact Name (PT-BR)",
},
"Loading": {
"TakingTooLong": "Isso esta demorando mais que o normal. Você pode voltar para a home ou continuar esperando.",
Expand All @@ -517,6 +518,10 @@ const translation = {
"EmptyNameTitle": "O nome de sua carteira não pode ser vazio",
"EmptyNameDescription": "Este é o nome que as pessoas verão quando se conerctar a você. Por favor, digite o nome da carteira.",
},
"RenameContact": {
"ThisContactName": "This contact name is only displayed in your Contacts List. (PT-BR)",
"ContactName": "Contact name (PT-BR)",
},
"NetInfo": {
"NoInternetConnectionTitle": "Sem conexão com a internet",
"NoInternetConnectionMessage": "Não é possivel acessar serviços utilizando a Bifold ou receber credenciais até você voltar a estar online.\n\nFavor checkar sua conexão com a internet.",
Expand Down
6 changes: 6 additions & 0 deletions packages/legacy/core/App/navigators/ContactStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 RenameContact from '../screens/RenameContact'
import WhatAreContacts from '../screens/WhatAreContacts'
import { ContactStackParams, Screens } from '../types/navigators'

Expand All @@ -32,6 +33,11 @@ const ContactStack: React.FC = () => {
title: t('Screens.ContactDetails'),
}}
/>
<Stack.Screen
name={Screens.RenameContact}
component={RenameContact}
options={{ title: t('Screens.RenameContact') }}
/>
<Stack.Screen name={Screens.Chat} component={Chat} />
<Stack.Screen name={Screens.WhatAreContacts} component={WhatAreContacts} options={{ title: '' }} />
<Stack.Screen
Expand Down
15 changes: 11 additions & 4 deletions packages/legacy/core/App/screens/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ProofState,
} from '@aries-framework/core'
import { useAgent, useBasicMessagesByConnectionId, useConnectionById } from '@aries-framework/react-hooks'
import { useNavigation } from '@react-navigation/core'
import { useIsFocused, useNavigation } from '@react-navigation/core'
import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand All @@ -31,6 +31,7 @@ import { Role } from '../types/chat'
import { BasicMessageMetadata, basicMessageCustomMetadata } from '../types/metadata'
import { RootStackParams, ContactStackParams, Screens, Stacks } from '../types/navigators'
import {
getConnectionName,
getCredentialEventLabel,
getCredentialEventRole,
getMessageEventRole,
Expand All @@ -54,12 +55,18 @@ const Chat: React.FC<ChatProps> = ({ 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<Array<ExtendedChatMessage>>([])
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()
Expand All @@ -70,7 +77,7 @@ const Chat: React.FC<ChatProps> = ({ route }) => {
title: theirLabel,
headerRight: () => <InfoIcon connectionId={connection?.id as string} />,
})
}, [connection])
}, [connection, theirLabel])

// when chat is open, mark messages as seen
useEffect(() => {
Expand Down Expand Up @@ -259,7 +266,7 @@ const Chat: React.FC<ChatProps> = ({ 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[]) => {
Expand Down
71 changes: 53 additions & 18 deletions packages/legacy/core/App/screens/ContactDetails.tsx
Original file line number Diff line number Diff line change
@@ -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<ContactStackParams, Screens.ContactDetails>

Expand All @@ -34,7 +34,15 @@ const ContactDetails: React.FC<ContactDetailsProps> = ({ 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) {
Expand Down Expand Up @@ -79,25 +87,52 @@ const ContactDetails: React.FC<ContactDetailsProps> = ({ 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 (
<SafeAreaView style={{ flexGrow: 1 }} edges={['bottom', 'left', 'right']}>
{record({
fields: [
{
name: connection?.alias || connection?.theirLabel,
value: t('ContactDetails.DateOfConnection', {
date: connection?.createdAt ? formatTime(connection.createdAt, { includeHour: true }) : '',
}),
},
] as Attribute[],
footer: () => <RecordRemove onRemove={callOnRemove} />,
})}
<View style={styles.contentContainer}>
<Text style={{ ...TextTheme.headingThree }}>{contactLabel}</Text>
<Text style={{ ...TextTheme.normal, marginTop: 20 }}>
{t('ContactDetails.DateOfConnection', {
date: connection?.createdAt ? formatTime(connection.createdAt, { includeHour: true }) : '',
})}
</Text>
</View>
<TouchableOpacity
bryce-mcmath marked this conversation as resolved.
Show resolved Hide resolved
onPress={callGoToRename}
accessibilityLabel={t('Screens.RenameContact')}
accessibilityRole={'button'}
testID={testIdWithKey('RenameContact')}
style={[styles.contentContainer, { marginTop: 10 }]}
>
<Text style={{ ...TextTheme.normal }}>{t('Screens.RenameContact')}</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={callOnRemove}
accessibilityLabel={t('ContactDetails.RemoveContact')}
accessibilityRole={'button'}
testID={testIdWithKey('RemoveFromWallet')}
style={[styles.contentContainer, { marginTop: 10 }]}
>
<Text style={{ ...TextTheme.normal, color: ColorPallet.semantic.error }}>
{t('ContactDetails.RemoveContact')}
</Text>
</TouchableOpacity>
<CommonRemoveModal
usage={ModalUsage.ContactRemove}
visible={isRemoveModalDisplayed}
Expand Down
Loading