Skip to content

Commit

Permalink
feat: refactored notificaiton functionality (#1224)
Browse files Browse the repository at this point in the history
Signed-off-by: wadeking98 <[email protected]>
  • Loading branch information
wadeking98 authored Aug 2, 2024
1 parent 1d3d992 commit b95c752
Show file tree
Hide file tree
Showing 18 changed files with 230 additions and 129 deletions.
2 changes: 2 additions & 0 deletions packages/legacy/core/App/__mocks__/container-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const useContainer = jest.fn().mockReturnValue({
return resolver
case T.CRED_HELP_ACTION_OVERRIDES:
return []
case T.NOTIFICATIONS:
return { useNotifications: jest.fn(), customNotificationConfig: undefined }
default:
return undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
const { ColorPallet, TextTheme } = useTheme()
const { agent } = useAgent()
const [declineModalVisible, setDeclineModalVisible] = useState(false)
const [action, setAction] = useState<any>()
const [closeAction, setCloseAction] = useState<any>()
const connection = useConnectionById(notification.connectionId ?? '')
const [details, setDetails] = useState<DisplayDetails>({
type: InfoBoxType.Info,
Expand Down Expand Up @@ -136,19 +138,8 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
},
})

let onPress: GenericFn = () => {
return
}
let onClose: GenericFn = () => {
return
}

const toggleDeclineModalVisible = () => setDeclineModalVisible(!declineModalVisible)

const isReceivedProof =
notificationType === NotificationType.ProofRequest &&
(notification as ProofExchangeRecord).state === ProofState.Done

const declineProofRequest = async () => {
try {
const proofId = (notification as ProofExchangeRecord).id
Expand Down Expand Up @@ -290,7 +281,12 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
})
}

const setActionForNotificationType = (notificationType: NotificationType): void => {
const getActionForNotificationType = (
notification: BasicMessageRecord | CredentialExchangeRecord | ProofExchangeRecord,
notificationType: NotificationType
) => {
let onPress
let onClose
switch (notificationType) {
case NotificationType.BasicMessage:
onPress = () => {
Expand All @@ -311,7 +307,10 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
onClose = toggleDeclineModalVisible
break
case NotificationType.ProofRequest:
if (isReceivedProof) {
if (
(notification as ProofExchangeRecord).state === ProofState.Done ||
(notification as ProofExchangeRecord).state === ProofState.PresentationReceived
) {
onPress = () => {
navigation.getParent()?.navigate(Stacks.ContactStack, {
screen: Screens.ProofDetails,
Expand Down Expand Up @@ -352,9 +351,14 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
default:
throw new Error('NotificationType was not set correctly.')
}
return { onPress, onClose }
}

setActionForNotificationType(notificationType)
useEffect(() => {
const { onPress, onClose } = getActionForNotificationType(notification, notificationType)
setAction(() => onPress)
setCloseAction(() => onClose)
}, [notification])

useEffect(() => {
const detailsPromise = async () => {
Expand Down Expand Up @@ -421,6 +425,10 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
}
}, [details])

const isReceivedProof =
notificationType === NotificationType.ProofRequest &&
((notification as ProofExchangeRecord).state === ProofState.Done ||
(notification as ProofExchangeRecord).state === ProofState.PresentationSent)
return (
<View style={[styles.container, styleConfig.containerStyle]} testID={testIdWithKey('NotificationListItem')}>
<View style={styles.headerContainer}>
Expand All @@ -441,7 +449,7 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
accessibilityLabel={t('Global.Dismiss')}
accessibilityRole={'button'}
testID={testIdWithKey(`Dismiss${notificationType}`)}
onPress={onClose}
onPress={closeAction}
hitSlop={hitSlop}
>
<Icon name={'close'} size={iconSize} color={styleConfig.iconColor} />
Expand All @@ -458,7 +466,7 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
accessibilityLabel={details.buttonTitle ?? t('Global.View')}
testID={testIdWithKey(`View${notificationType}${isReceivedProof ? 'Received' : ''}`)}
buttonType={ButtonType.Primary}
onPress={onPress}
onPress={action}
/>
</View>
{commonRemoveModal()}
Expand Down
7 changes: 5 additions & 2 deletions packages/legacy/core/App/components/views/HomeFooterView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'

import { TOKENS, useContainer } from '../../container-api'
import { useTheme } from '../../contexts/theme'
import { useNotifications } from '../../hooks/notifications'

const offset = 25

Expand All @@ -18,6 +18,9 @@ const HomeFooterView: React.FC<HomeFooterViewProps> = ({ children }) => {
...useCredentialByState(CredentialState.CredentialReceived),
...useCredentialByState(CredentialState.Done),
]
const container = useContainer()
const notificationObj = container?.resolve(TOKENS.NOTIFICATIONS)
const useNotifications = notificationObj?.useNotifications ?? (() => [])
const notifications = useNotifications()
const { HomeTheme, TextTheme } = useTheme()
const { t } = useTranslation()
Expand Down Expand Up @@ -62,7 +65,7 @@ const HomeFooterView: React.FC<HomeFooterViewProps> = ({ children }) => {

return (
<>
{notifications.total === 0 && (
{notifications.length === 0 && (
<View style={[styles.messageContainer]}>
<Text adjustsFontSizeToFit style={[HomeTheme.welcomeHeader, { marginTop: offset, marginBottom: 20 }]}>
{t('Home.Welcome')}
Expand Down
15 changes: 12 additions & 3 deletions packages/legacy/core/App/container-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Agent, BaseLogger } from '@credo-ts/core'
import {
Agent,
BaseLogger,
BasicMessageRecord,
ProofExchangeRecord,
CredentialExchangeRecord as CredentialRecord,
} from '@credo-ts/core'
import { IndyVdrPoolConfig } from '@credo-ts/indy-vdr'
import { ProofRequestTemplate } from '@hyperledger/aries-bifold-verifier'
import { OCABundleResolverType } from '@hyperledger/aries-oca/build/legacy'
Expand Down Expand Up @@ -38,7 +44,7 @@ export const SCREEN_TOKENS = {
} as const

export const NOTIFICATION_TOKENS = {
CUSTOM_NOTIFICATION: 'custom.notification',
NOTIFICATIONS: 'notification.list',
} as const

export const STACK_TOKENS = {
Expand Down Expand Up @@ -110,7 +116,10 @@ export type TokenMapping = {
[TOKENS.FN_ONBOARDING_DONE]: FN_ONBOARDING_DONE
[TOKENS.LOAD_STATE]: LoadStateFn
[TOKENS.COMP_BUTTON]: Button
[TOKENS.CUSTOM_NOTIFICATION]: CustomNotification | undefined
[TOKENS.NOTIFICATIONS]: {
useNotifications: () => Array<BasicMessageRecord | CredentialRecord | ProofExchangeRecord | CustomNotification>
customNotificationConfig?: CustomNotification
}
[TOKENS.OBJECT_ONBOARDING_CONFIG]: ScreenOptionsType
[TOKENS.CACHE_CRED_DEFS]: { did: string; id: string }[]
[TOKENS.CACHE_SCHEMAS]: { did: string; id: string }[]
Expand Down
2 changes: 2 additions & 0 deletions packages/legacy/core/App/container-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LocalStorageKeys } from './constants'
import { TOKENS, Container, TokenMapping } from './container-api'
import { DispatchAction, ReducerAction } from './contexts/reducers/store'
import { defaultState } from './contexts/store'
import { useNotifications } from './hooks/notifications'
import { IHistoryManager } from './modules/history'
import HistoryManager from './modules/history/context/historyManager'
import OnboardingStack from './navigators/OnboardingStack'
Expand Down Expand Up @@ -62,6 +63,7 @@ export class MainContainer implements Container {
this._container.registerInstance(TOKENS.UTIL_LEDGERS, defaultIndyLedgers)
this._container.registerInstance(TOKENS.UTIL_PROOF_TEMPLATE, useProofRequestTemplates)
this._container.registerInstance(TOKENS.UTIL_ATTESTATION_MONITOR, { useValue: undefined })
this._container.registerInstance(TOKENS.NOTIFICATIONS, { useNotifications })
this._container.registerInstance(TOKENS.CACHE_CRED_DEFS, [])
this._container.registerInstance(TOKENS.CACHE_SCHEMAS, [])
this._container.registerInstance(
Expand Down
1 change: 0 additions & 1 deletion packages/legacy/core/App/contexts/configuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export interface ConfigurationContext {
disableOnboardingSkip?: boolean
useBiometry: React.FC
enablePushNotifications?: PushNotificationConfiguration
useCustomNotifications: () => { total: number; notifications: any }
whereToUseWalletUrl: string
showScanHelp?: boolean
showScanButton?: boolean
Expand Down
2 changes: 0 additions & 2 deletions packages/legacy/core/App/defaultConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import HomeFooterView from './components/views/HomeFooterView'
import HomeHeaderView from './components/views/HomeHeaderView'
import { PINRules } from './constants'
import { ConfigurationContext } from './contexts/configuration'
import { useNotifications } from './hooks/notifications'
import { Locales, translationResources } from './localization'
import Developer from './screens/Developer'
import OnboardingPages from './screens/OnboardingPages'
Expand Down Expand Up @@ -33,7 +32,6 @@ export const defaultConfiguration: ConfigurationContext = {
supportedLanguages: Object.keys(translationResources) as Locales[],
showPreface: false,
disableOnboardingSkip: false,
useCustomNotifications: useNotifications,
useBiometry: UseBiometry,
whereToUseWalletUrl: 'https://example.com',
showScanHelp: true,
Expand Down
79 changes: 43 additions & 36 deletions packages/legacy/core/App/hooks/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@credo-ts/core'
import { useBasicMessages, useCredentialByState, useProofByState } from '@credo-ts/react-hooks'
import { ProofCustomMetadata, ProofMetadata } from '@hyperledger/aries-bifold-verifier'
import { useEffect, useState } from 'react'

import {
BasicMessageMetadata,
Expand All @@ -15,47 +16,53 @@ import {
credentialCustomMetadata,
} from '../types/metadata'

interface Notifications {
total: number
notifications: Array<BasicMessageRecord | CredentialRecord | ProofExchangeRecord>
}

export const useNotifications = (): Notifications => {
export const useNotifications = (): Array<BasicMessageRecord | CredentialRecord | ProofExchangeRecord> => {
const { records: basicMessages } = useBasicMessages()
// get all unseen messages
const unseenMessages: BasicMessageRecord[] = basicMessages.filter((msg) => {
const meta = msg.metadata.get(BasicMessageMetadata.customMetadata) as basicMessageCustomMetadata
return !meta?.seen
})
// add one unseen message per contact to notifications
const contactsWithUnseenMessages: string[] = []
const messagesToShow: BasicMessageRecord[] = []
unseenMessages.forEach((msg) => {
if (!contactsWithUnseenMessages.includes(msg.connectionId)) {
contactsWithUnseenMessages.push(msg.connectionId)
messagesToShow.push(msg)
}
})
const [notifications, setNotifications] = useState<(BasicMessageRecord | CredentialRecord | ProofExchangeRecord)[]>(
[]
)

const credsReceived = useCredentialByState(CredentialState.CredentialReceived)
const credsDone = useCredentialByState(CredentialState.Done)
const proofsDone = useProofByState([ProofState.Done, ProofState.PresentationReceived])
const offers = useCredentialByState(CredentialState.OfferReceived)
const proofsRequested = useProofByState(ProofState.RequestReceived)
const proofsDone = useProofByState([ProofState.Done, ProofState.PresentationReceived]).filter(
(proof: ProofExchangeRecord) => {

useEffect(() => {
// get all unseen messages
const unseenMessages: BasicMessageRecord[] = basicMessages.filter((msg) => {
const meta = msg.metadata.get(BasicMessageMetadata.customMetadata) as basicMessageCustomMetadata
return !meta?.seen
})

// add one unseen message per contact to notifications
const contactsWithUnseenMessages: string[] = []
const messagesToShow: BasicMessageRecord[] = []
unseenMessages.forEach((msg) => {
if (!contactsWithUnseenMessages.includes(msg.connectionId)) {
contactsWithUnseenMessages.push(msg.connectionId)
messagesToShow.push(msg)
}
})

const validProofsDone = proofsDone.filter((proof: ProofExchangeRecord) => {
if (proof.isVerified === undefined) return false
const metadata = proof.metadata.get(ProofMetadata.customMetadata) as ProofCustomMetadata
return !metadata?.details_seen
}
)
const revoked = useCredentialByState(CredentialState.Done).filter((cred: CredentialRecord) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const metadata = cred!.metadata.get(CredentialMetadata.customMetadata) as credentialCustomMetadata
if (cred?.revocationNotification && metadata?.revoked_seen == undefined) {
return cred
}
})

const notifications = [...messagesToShow, ...offers, ...proofsRequested, ...proofsDone, ...revoked].sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
})
const revoked = credsDone.filter((cred: CredentialRecord) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const metadata = cred!.metadata.get(CredentialMetadata.customMetadata) as credentialCustomMetadata
if (cred?.revocationNotification && metadata?.revoked_seen == undefined) {
return cred
}
})

const notif = [...messagesToShow, ...offers, ...proofsRequested, ...validProofsDone, ...revoked].sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
setNotifications(notif)
}, [basicMessages, credsReceived, proofsDone, proofsRequested, offers, credsDone])

return { total: notifications.length, notifications }
return notifications
}
2 changes: 1 addition & 1 deletion packages/legacy/core/App/navigators/NotificationStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const NotificationStack: React.FC = () => {
const { t } = useTranslation()
const defaultStackOptions = createDefaultStackOptions(theme)
const container = useContainer()
const customNotification = container.resolve(TOKENS.CUSTOM_NOTIFICATION)
const { customNotificationConfig: customNotification } = container.resolve(TOKENS.NOTIFICATIONS)

return (
<Stack.Navigator screenOptions={{ ...defaultStackOptions }}>
Expand Down
11 changes: 6 additions & 5 deletions packages/legacy/core/App/navigators/TabStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'

import { AttachTourStep } from '../components/tour/AttachTourStep'
import { useConfiguration } from '../contexts/configuration'
import { TOKENS, useContainer } from '../container-api'
import { useNetwork } from '../contexts/network'
import { useTheme } from '../contexts/theme'
import { Screens, Stacks, TabStackParams, TabStacks } from '../types/navigators'
Expand All @@ -20,8 +20,9 @@ import HomeStack from './HomeStack'

const TabStack: React.FC = () => {
const { fontScale } = useWindowDimensions()
const { useCustomNotifications } = useConfiguration()
const { total } = useCustomNotifications()
const container = useContainer()
const { useNotifications } = container.resolve(TOKENS.NOTIFICATIONS)
const notifications = useNotifications()
const { t } = useTranslation()
const Tab = createBottomTabNavigator<TabStackParams>()
const { assertConnectedNetwork } = useNetwork()
Expand Down Expand Up @@ -85,9 +86,9 @@ const TabStack: React.FC = () => {
</AttachTourStep>
),
tabBarShowLabel: false,
tabBarAccessibilityLabel: `${t('TabStack.Home')} (${total ?? 0})`,
tabBarAccessibilityLabel: `${t('TabStack.Home')} (${notifications.length ?? 0})`,
tabBarTestID: testIdWithKey(t('TabStack.Home')),
tabBarBadge: total || undefined,
tabBarBadge: notifications.length || undefined,
tabBarBadgeStyle: {
marginLeft: leftMarginForDevice(),
backgroundColor: ColorPallet.semantic.error,
Expand Down
Loading

0 comments on commit b95c752

Please sign in to comment.