-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: refactor chat msg fetching and order contacts by most recent msg (
#1126) Signed-off-by: Bryce McMath <[email protected]>
- Loading branch information
1 parent
915c951
commit f14b18a
Showing
12 changed files
with
456 additions
and
386 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
import { | ||
BasicMessageRecord, | ||
ConnectionRecord, | ||
CredentialExchangeRecord, | ||
CredentialState, | ||
ProofExchangeRecord, | ||
ProofState, | ||
} from '@credo-ts/core' | ||
import { useBasicMessagesByConnectionId } from '@credo-ts/react-hooks' | ||
import { isPresentationReceived } from '@hyperledger/aries-bifold-verifier' | ||
import { useNavigation } from '@react-navigation/core' | ||
import { StackNavigationProp } from '@react-navigation/stack' | ||
import React, { Fragment, useEffect, useState } from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import { Linking, Text } from 'react-native' | ||
|
||
import { ChatEvent } from '../components/chat/ChatEvent' | ||
import { ExtendedChatMessage, CallbackType } from '../components/chat/ChatMessage' | ||
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 { RootStackParams, ContactStackParams, Screens, Stacks } from '../types/navigators' | ||
import { | ||
getConnectionName, | ||
getCredentialEventLabel, | ||
getCredentialEventRole, | ||
getMessageEventRole, | ||
getProofEventLabel, | ||
getProofEventRole, | ||
} from '../utils/helpers' | ||
|
||
/** | ||
* Determines the callback to be called when the button below a given chat message is pressed, if it exists. | ||
* | ||
* eg. 'View offer' -> opens the credential offer screen | ||
* | ||
* @param {CredentialExchangeRecord | ProofExchangeRecord} record - The record to determine the callback type for. | ||
* @returns {CallbackType} The callback type for the given record. | ||
*/ | ||
const callbackTypeForMessage = (record: CredentialExchangeRecord | ProofExchangeRecord) => { | ||
if ( | ||
record instanceof CredentialExchangeRecord && | ||
(record.state === CredentialState.Done || record.state === CredentialState.OfferReceived) | ||
) { | ||
return CallbackType.CredentialOffer | ||
} | ||
|
||
if ( | ||
(record instanceof ProofExchangeRecord && isPresentationReceived(record) && record.isVerified !== undefined) || | ||
record.state === ProofState.RequestReceived || | ||
(record.state === ProofState.Done && record.isVerified === undefined) | ||
) { | ||
return CallbackType.ProofRequest | ||
} | ||
|
||
if ( | ||
record instanceof ProofExchangeRecord && | ||
(record.state === ProofState.PresentationSent || record.state === ProofState.Done) | ||
) { | ||
return CallbackType.PresentationSent | ||
} | ||
} | ||
|
||
/** | ||
* Custom hook for retrieving chat messages for a given connection. This hook includes some of | ||
* the JSX for rendering the chat messages, including the logic for handling links in messages. | ||
* | ||
* @param {ConnectionRecord} connection - The connection to retrieve chat messages for. | ||
* @returns {ExtendedChatMessage[]} The chat messages for the given connection. | ||
*/ | ||
export const useChatMessagesByConnection = (connection: ConnectionRecord): ExtendedChatMessage[] => { | ||
const [messages, setMessages] = useState<Array<ExtendedChatMessage>>([]) | ||
const [store] = useStore() | ||
const { t } = useTranslation() | ||
const { ChatTheme: theme, ColorPallet } = useTheme() | ||
const navigation = useNavigation<StackNavigationProp<RootStackParams | ContactStackParams>>() | ||
const basicMessages = useBasicMessagesByConnectionId(connection.id) | ||
const credentials = useCredentialsByConnectionId(connection.id) | ||
const proofs = useProofsByConnectionId(connection.id) | ||
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)) | ||
}, [connection, store.preferences.alternateContactNames]) | ||
|
||
useEffect(() => { | ||
const transformedMessages: Array<ExtendedChatMessage> = basicMessages.map((record: BasicMessageRecord) => { | ||
const role = getMessageEventRole(record) | ||
// eslint-disable-next-line | ||
const linkRegex = /(?:https?\:\/\/\w+(?:\.\w+)+\S*)|(?:[\w\d\.\_\-]+@\w+(?:\.\w+)+)/gm | ||
// eslint-disable-next-line | ||
const mailRegex = /^[\w\d\.\_\-]+@\w+(?:\.\w+)+$/gm | ||
const links = record.content.match(linkRegex) ?? [] | ||
const handleLinkPress = (link: string) => { | ||
if (link.match(mailRegex)) { | ||
link = 'mailto:' + link | ||
} | ||
Linking.openURL(link) | ||
} | ||
const msgText = ( | ||
<Text style={role === Role.me ? theme.rightText : theme.leftText}> | ||
{record.content.split(linkRegex).map((split, i) => { | ||
if (i < links.length) { | ||
const link = links[i] | ||
return ( | ||
<Fragment key={`${record.id}-${i}`}> | ||
<Text>{split}</Text> | ||
<Text | ||
onPress={() => handleLinkPress(link)} | ||
style={{ color: ColorPallet.brand.link, textDecorationLine: 'underline' }} | ||
accessibilityRole={'link'} | ||
> | ||
{link} | ||
</Text> | ||
</Fragment> | ||
) | ||
} | ||
return <Text key={`${record.id}-${i}`}>{split}</Text> | ||
})} | ||
</Text> | ||
) | ||
return { | ||
_id: record.id, | ||
text: record.content, | ||
renderEvent: () => msgText, | ||
createdAt: record.updatedAt || record.createdAt, | ||
type: record.type, | ||
user: { _id: role }, | ||
} | ||
}) | ||
|
||
transformedMessages.push( | ||
...credentials.map((record: CredentialExchangeRecord) => { | ||
const role = getCredentialEventRole(record) | ||
const userLabel = role === Role.me ? t('Chat.UserYou') : theirLabel | ||
const actionLabel = t(getCredentialEventLabel(record) as any) | ||
|
||
return { | ||
_id: record.id, | ||
text: actionLabel, | ||
renderEvent: () => <ChatEvent role={role} userLabel={userLabel} actionLabel={actionLabel} />, | ||
createdAt: record.updatedAt || record.createdAt, | ||
type: record.type, | ||
user: { _id: role }, | ||
messageOpensCallbackType: callbackTypeForMessage(record), | ||
onDetails: () => { | ||
const navMap: { [key in CredentialState]?: () => void } = { | ||
[CredentialState.Done]: () => { | ||
navigation.navigate(Stacks.ContactStack as any, { | ||
screen: Screens.CredentialDetails, | ||
params: { credential: record }, | ||
}) | ||
}, | ||
[CredentialState.OfferReceived]: () => { | ||
navigation.navigate(Stacks.ContactStack as any, { | ||
screen: Screens.CredentialOffer, | ||
params: { credentialId: record.id }, | ||
}) | ||
}, | ||
} | ||
const nav = navMap[record.state] | ||
if (nav) { | ||
nav() | ||
} | ||
}, | ||
} | ||
}) | ||
) | ||
|
||
transformedMessages.push( | ||
...proofs.map((record: ProofExchangeRecord) => { | ||
const role = getProofEventRole(record) | ||
const userLabel = role === Role.me ? t('Chat.UserYou') : theirLabel | ||
const actionLabel = t(getProofEventLabel(record) as any) | ||
|
||
return { | ||
_id: record.id, | ||
text: actionLabel, | ||
renderEvent: () => <ChatEvent role={role} userLabel={userLabel} actionLabel={actionLabel} />, | ||
createdAt: record.updatedAt || record.createdAt, | ||
type: record.type, | ||
user: { _id: role }, | ||
messageOpensCallbackType: callbackTypeForMessage(record), | ||
onDetails: () => { | ||
const toProofDetails = () => { | ||
navigation.navigate(Stacks.ContactStack as any, { | ||
screen: Screens.ProofDetails, | ||
params: { | ||
recordId: record.id, | ||
isHistory: true, | ||
senderReview: | ||
record.state === ProofState.PresentationSent || | ||
(record.state === ProofState.Done && record.isVerified === undefined), | ||
}, | ||
}) | ||
} | ||
const navMap: { [key in ProofState]?: () => void } = { | ||
[ProofState.Done]: toProofDetails, | ||
[ProofState.PresentationSent]: toProofDetails, | ||
[ProofState.PresentationReceived]: toProofDetails, | ||
[ProofState.RequestReceived]: () => { | ||
navigation.navigate(Stacks.ContactStack as any, { | ||
screen: Screens.ProofRequest, | ||
params: { proofId: record.id }, | ||
}) | ||
}, | ||
} | ||
const nav = navMap[record.state] | ||
if (nav) { | ||
nav() | ||
} | ||
}, | ||
} | ||
}) | ||
) | ||
|
||
const connectedMessage = connection | ||
? { | ||
_id: 'connected', | ||
text: `${t('Chat.YouConnected')} ${theirLabel}`, | ||
renderEvent: () => ( | ||
<Text style={theme.rightText}> | ||
{t('Chat.YouConnected')} | ||
<Text style={[theme.rightText, theme.rightTextHighlighted]}> {theirLabel}</Text> | ||
</Text> | ||
), | ||
createdAt: connection.createdAt, | ||
user: { _id: Role.me }, | ||
} | ||
: undefined | ||
|
||
setMessages( | ||
connectedMessage | ||
? [...transformedMessages.sort((a: any, b: any) => b.createdAt - a.createdAt), connectedMessage] | ||
: transformedMessages.sort((a: any, b: any) => b.createdAt - a.createdAt) | ||
) | ||
}, [basicMessages, credentials, proofs, theirLabel]) | ||
|
||
return messages | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.