diff --git a/apps/wallet-mobile/src/features/Notifications/useCases/NotificationsDevScreen.tsx b/apps/wallet-mobile/src/features/Notifications/useCases/NotificationsDevScreen.tsx index 1f80d5cd1c..004b94f495 100644 --- a/apps/wallet-mobile/src/features/Notifications/useCases/NotificationsDevScreen.tsx +++ b/apps/wallet-mobile/src/features/Notifications/useCases/NotificationsDevScreen.tsx @@ -1,3 +1,11 @@ +import { + NotificationProvider, + useNotificationManager, + useNotificationsConfig, + useReceivedNotificationEvents, + useResetNotificationsConfig, + useUpdateNotificationsConfig, +} from '@yoroi/notifications' import {useTheme} from '@yoroi/theme' import {Notifications as NotificationTypes} from '@yoroi/types' import * as React from 'react' @@ -5,34 +13,36 @@ import {StyleSheet, Switch as RNSwitch, Text, View} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import {Button} from '../../../components/Button/Button' -import {ScrollableView} from '../../../components/ScrollableView' -import { - useHandleNotification, - useMockedNotifications, - useNotificationsConfig, - useReceivedNotificationEvents, - useRequestPermissions, - useResetNotificationsConfig, - useUpdateNotificationsConfig, -} from './common/hooks' +import {ScrollView} from '../../../components/ScrollView/ScrollView' +import {useMockedNotifications} from './common/mocks' +import {createTransactionReceivedNotification} from './common/transaction-received-notification' export const NotificationsDevScreen = () => { - useRequestPermissions() - useHandleNotification() - const {triggerTransactionReceived} = useMockedNotifications() + const {manager} = useMockedNotifications() + return ( + + + + ) +} + +const Screen = () => { + const manager = useNotificationManager() const handleOnTriggerTransactionReceived = () => { - triggerTransactionReceived({ - previousTxsCounter: 0, - nextTxsCounter: 1, - txId: '123', - isSentByUser: false, - }) + manager.notification$.next( + createTransactionReceivedNotification({ + previousTxsCounter: 0, + nextTxsCounter: 1, + txId: '123', + isSentByUser: false, + }), + ) } return ( - + Notifications Playground @@ -48,7 +58,7 @@ export const NotificationsDevScreen = () => { - + ) } diff --git a/apps/wallet-mobile/src/features/Notifications/useCases/common/hooks.ts b/apps/wallet-mobile/src/features/Notifications/useCases/common/hooks.ts index 3ee68b44de..31f8d522e1 100644 --- a/apps/wallet-mobile/src/features/Notifications/useCases/common/hooks.ts +++ b/apps/wallet-mobile/src/features/Notifications/useCases/common/hooks.ts @@ -1,87 +1,25 @@ import {Notification, Notifications} from '@jamsinclair/react-native-notifications' -import {useAsyncStorage, useMutationWithInvalidations} from '@yoroi/common' +import {useAsyncStorage} from '@yoroi/common' import {notificationManagerMaker} from '@yoroi/notifications' import {Notifications as NotificationTypes} from '@yoroi/types' import * as React from 'react' -import {UseMutationOptions, useQuery, UseQueryOptions} from 'react-query' -import {Subject} from 'rxjs' -import {useWalletManager} from '../../../WalletManager/context/WalletManagerProvider' -import { - checkForNewTransactions, - createTransactionReceivedNotification, - registerBackgroundFetchAsync, - unregisterBackgroundFetchAsync, -} from './transacrtion-received' -import {useSendNotification} from './notifications' +import {useEffect} from 'react' -export const useRequestPermissions = () => { - React.useEffect(() => { - Notifications.registerRemoteNotifications() - }, []) -} - -export const useHandleNotification = () => { - React.useEffect(() => { - const s = Notifications.events().registerNotificationReceivedForeground( - (notification: Notification, completion) => { - console.log(`Notification received in foreground: ${notification.title} : ${notification.body}`) - completion({alert: true, sound: true, badge: true}) - }, - ) - - // TODO: Can we remove this? - const s2 = Notifications.events().registerNotificationReceivedBackground((notification: Notification) => { - console.log(`Notification received in background: ${notification.title} : ${notification.body}`) - }) +import {displayNotificationEvent} from './notifications' +import {useTransactionReceivedNotificationSubject} from './transaction-received-notification' - return () => { - s.remove() - s2.remove() - } - }, []) -} - -export const useNotificationsConfig = () => { - const manager = useNotificationsManager() - return useQuery(['notificationsConfig'], () => manager.config.read()) -} +let initialized = false -export const useUpdateNotificationsConfig = () => { - const manager = useNotificationsManager() - - const mutationFn = async (newConfig: NotificationTypes.Config) => { - await manager.config.save(newConfig) - } - - return useMutationWithInvalidations({ - mutationFn, - invalidateQueries: [['notificationsConfig']], - }) -} - -export const useResetNotificationsConfig = (options: UseMutationOptions = {}) => { - const manager = useNotificationsManager() - const mutationFn = async () => { - await manager.config.reset() - return manager.config.read() - } - - return useMutationWithInvalidations({ - mutationFn, - invalidateQueries: [['notificationsConfig']], - ...options, +const init = () => { + if (initialized) return + initialized = true + Notifications.registerRemoteNotifications() + Notifications.events().registerNotificationReceivedForeground((_notification: Notification, completion) => { + completion({alert: true, sound: true, badge: true}) }) -} - -export const useReceivedNotificationEvents = ( - options: UseQueryOptions, Error> = {}, -) => { - const manager = useNotificationsManager() - const queryFn = () => manager.events.read() - return useQuery({ - queryKey: ['receivedNotificationEvents'], - queryFn, - ...options, + // TODO: Can we remove this? + Notifications.events().registerNotificationReceivedBackground((notification: Notification) => { + console.log(`Notification received in background: ${notification.title} : ${notification.body}`) }) } @@ -112,34 +50,11 @@ export const useNotificationsManager = (options?: { return manager } -export const useMockedNotifications = () => { - const {send} = useSendNotification() - const [transactionReceivedSubject] = React.useState(new Subject()) - const manager = useNotificationsManager({ - subscriptions: {[NotificationTypes.Trigger.TransactionReceived]: transactionReceivedSubject}, - }) - React.useEffect(() => { - const subscription = manager.notification$.subscribe((notificationEvent) => { - if (notificationEvent.trigger === NotificationTypes.Trigger.TransactionReceived) { - send('Transaction received', 'You have received a new transaction') - } - }) - return () => { - subscription.unsubscribe() - } - }, [manager, send]) - - const triggerTransactionReceived = (metadata: NotificationTypes.TransactionReceivedEvent['metadata']) => { - transactionReceivedSubject.next(createTransactionReceivedNotification(metadata)) - } - - return {triggerTransactionReceived} -} - export const useNotifications = () => { - useRequestPermissions() - useHandleNotification() - const {send} = useSendNotification() + useEffect(() => { + init() + }, []) + const transactionReceivedSubject = useTransactionReceivedNotificationSubject() const manager = useNotificationsManager({ @@ -148,43 +63,9 @@ export const useNotifications = () => { }, }) React.useEffect(() => { - const subscription = manager.notification$.subscribe((notificationEvent) => { - if (notificationEvent.trigger === NotificationTypes.Trigger.TransactionReceived) { - console.log('Transaction received', notificationEvent.metadata) - send('Transaction received', 'You have received a new transaction') - } - }) + const subscription = manager.notification$.subscribe(displayNotificationEvent) return () => { subscription.unsubscribe() } - }, [manager, send]) -} - -const useTransactionReceivedNotificationSubject = () => { - const {walletManager} = useWalletManager() - const asyncStorage = useAsyncStorage() - const [transactionReceivedSubject] = React.useState(new Subject()) - - React.useEffect(() => { - registerBackgroundFetchAsync() - return () => { - unregisterBackgroundFetchAsync() - } - }, []) - - React.useEffect(() => { - const s1 = walletManager.syncWalletInfos$.subscribe(async (status) => { - const walletInfos = Array.from(status.values()) - const walletsDoneSyncing = walletInfos.filter((info) => info.status === 'done') - const areAllDone = walletsDoneSyncing.length === walletInfos.length - if (!areAllDone) return - - await checkForNewTransactions(walletManager, asyncStorage) - }) - - return () => { - s1.unsubscribe() - } - }, [walletManager, asyncStorage, transactionReceivedSubject]) - return transactionReceivedSubject + }, [manager]) } diff --git a/apps/wallet-mobile/src/features/Notifications/useCases/common/mocks.ts b/apps/wallet-mobile/src/features/Notifications/useCases/common/mocks.ts new file mode 100644 index 0000000000..6c94b7f026 --- /dev/null +++ b/apps/wallet-mobile/src/features/Notifications/useCases/common/mocks.ts @@ -0,0 +1,28 @@ +import {Notifications as NotificationTypes} from '@yoroi/types' +import * as React from 'react' +import {Subject} from 'rxjs' + +import {useNotificationsManager} from './hooks' +import {displayNotificationEvent} from './notifications' +import {createTransactionReceivedNotification} from './transaction-received-notification' + +export const useMockedNotifications = () => { + const [transactionReceivedSubject] = React.useState(new Subject()) + const manager = useNotificationsManager({ + subscriptions: { + [NotificationTypes.Trigger.TransactionReceived]: transactionReceivedSubject, + }, + }) + React.useEffect(() => { + const subscription = manager.notification$.subscribe(displayNotificationEvent) + return () => { + subscription.unsubscribe() + } + }, [manager]) + + const triggerTransactionReceived = (metadata: NotificationTypes.TransactionReceivedEvent['metadata']) => { + transactionReceivedSubject.next(createTransactionReceivedNotification(metadata)) + } + + return {triggerTransactionReceived, manager} +} diff --git a/apps/wallet-mobile/src/features/Notifications/useCases/common/notifications.ts b/apps/wallet-mobile/src/features/Notifications/useCases/common/notifications.ts index 513a81b4df..4677d3581b 100644 --- a/apps/wallet-mobile/src/features/Notifications/useCases/common/notifications.ts +++ b/apps/wallet-mobile/src/features/Notifications/useCases/common/notifications.ts @@ -1,5 +1,12 @@ import {Notification, Notifications} from '@jamsinclair/react-native-notifications' -import * as React from 'react' +import {Notifications as NotificationTypes} from '@yoroi/types' + +export const displayNotificationEvent = (notificationEvent: NotificationTypes.Event) => { + if (notificationEvent.trigger === NotificationTypes.Trigger.TransactionReceived) { + console.log('Transaction received', notificationEvent.metadata) + sendNotification('Transaction received', 'You have received a new transaction') + } +} export const sendNotification = (title: string, body: string) => { const notification = new Notification({ @@ -9,8 +16,3 @@ export const sendNotification = (title: string, body: string) => { }) Notifications.postLocalNotification(notification.payload) } - -export const useSendNotification = () => { - const send = React.useCallback(sendNotification, []) - return {send} -} diff --git a/apps/wallet-mobile/src/features/Notifications/useCases/common/transacrtion-received.ts b/apps/wallet-mobile/src/features/Notifications/useCases/common/transaction-received-notification.ts similarity index 75% rename from apps/wallet-mobile/src/features/Notifications/useCases/common/transacrtion-received.ts rename to apps/wallet-mobile/src/features/Notifications/useCases/common/transaction-received-notification.ts index 433338fea8..0289b028d8 100644 --- a/apps/wallet-mobile/src/features/Notifications/useCases/common/transacrtion-received.ts +++ b/apps/wallet-mobile/src/features/Notifications/useCases/common/transaction-received-notification.ts @@ -1,12 +1,17 @@ +import {useAsyncStorage} from '@yoroi/common' +import {mountAsyncStorage} from '@yoroi/common/src' +import {App, Notifications as NotificationTypes} from '@yoroi/types' +import * as BackgroundFetch from 'expo-background-fetch' import * as TaskManager from 'expo-task-manager' +import * as React from 'react' +import {Subject} from 'rxjs' +import uuid from 'uuid' + +import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types' +import {useWalletManager} from '../../../WalletManager/context/WalletManagerProvider' import {WalletManager, walletManager} from '../../../WalletManager/wallet-manager' -import * as BackgroundFetch from 'expo-background-fetch' -import {App, Notifications as NotificationTypes} from '@yoroi/types' import {notificationManager} from './notification-manager' -import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types' -import uuid from 'uuid' -import {sendNotification} from './notifications' -import {mountAsyncStorage} from '@yoroi/common/src' +import {displayNotificationEvent} from './notifications' const BACKGROUND_FETCH_TASK = 'yoroi-notifications-background-fetch' if (!TaskManager.isTaskDefined(BACKGROUND_FETCH_TASK)) { @@ -47,7 +52,7 @@ const syncAllWallets = async (walletManager: WalletManager) => { const ids = [...walletManager.walletMetas.keys()] const promises = ids.map((id) => { const wallet = walletManager.getWalletById(id) - if (!wallet) return + if (!wallet) return Promise.resolve() return wallet.sync({isForced: true}) }) await Promise.all(promises) @@ -85,7 +90,7 @@ export const checkForNewTransactions = async (walletManager: WalletManager, appS } const notification = createTransactionReceivedNotification(metadata) notificationManager.events.save(notification) - sendNotification('Transaction received from background', 'You have received a new transaction') + displayNotificationEvent(notification) }) } } @@ -106,3 +111,32 @@ export const createTransactionReceivedNotification = ( metadata, } as const } + +export const useTransactionReceivedNotificationSubject = () => { + const {walletManager} = useWalletManager() + const asyncStorage = useAsyncStorage() + const [transactionReceivedSubject] = React.useState(new Subject()) + + React.useEffect(() => { + registerBackgroundFetchAsync() + return () => { + unregisterBackgroundFetchAsync() + } + }, []) + + React.useEffect(() => { + const s1 = walletManager.syncWalletInfos$.subscribe(async (status) => { + const walletInfos = Array.from(status.values()) + const walletsDoneSyncing = walletInfos.filter((info) => info.status === 'done') + const areAllDone = walletsDoneSyncing.length === walletInfos.length + if (!areAllDone) return + + await checkForNewTransactions(walletManager, asyncStorage) + }) + + return () => { + s1.unsubscribe() + } + }, [walletManager, asyncStorage, transactionReceivedSubject]) + return transactionReceivedSubject +} diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/cardano-wallet.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/cardano-wallet.ts index 1a2babeb60..e4bf088e85 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/cardano-wallet.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/cardano-wallet.ts @@ -12,7 +12,7 @@ import {Buffer} from 'buffer' import {freeze} from 'immer' import _ from 'lodash' import {defaultMemoize} from 'reselect' -import {Observable, Subject} from 'rxjs' +import {Observable} from 'rxjs' import {buildPortfolioBalanceManager} from '../../features/Portfolio/common/helpers/build-balance-manager' import {toBalanceManagerSyncArgs} from '../../features/Portfolio/common/transformers/toBalanceManagerSyncArgs' diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts index 01e71b9202..d80576eb94 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts @@ -12,7 +12,6 @@ import { } from '@emurgo/yoroi-lib' import {App, HW, Network, Portfolio, Wallet} from '@yoroi/types' import {BigNumber} from 'bignumber.js' -import {Subject} from 'rxjs' import {WalletEncryptedStorage} from '../../kernel/storage/EncryptedStorage' import type { diff --git a/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts b/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts index d06dbba32d..92cb84b8f6 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts @@ -7,14 +7,14 @@ import {createPrimaryTokenInfo} from '@yoroi/portfolio' import {Balance, Portfolio, Wallet} from '@yoroi/types' import BigNumber from 'bignumber.js' import {noop} from 'lodash' -import {Observable, Subject} from 'rxjs' +import {Observable} from 'rxjs' import {buildPortfolioTokenManagers} from '../../features/Portfolio/common/helpers/build-token-managers' import {cardanoConfig} from '../../features/WalletManager/common/adapters/cardano/cardano-config' import {buildNetworkManagers} from '../../features/WalletManager/network-manager/network-manager' import {toTokenInfo, utf8ToHex} from '../cardano/api/utils' import {CardanoTypes, YoroiWallet} from '../cardano/types' -import {RawUtxo, TransactionInfo} from '../types/other' +import {TransactionInfo} from '../types/other' import {RemotePoolMetaSuccess, StakePoolInfosAndHistories, StakingInfo, StakingStatus} from '../types/staking' import {YoroiNftModerationStatus, YoroiSignedTx, YoroiUnsignedTx} from '../types/yoroi' import {getTokenFingerprint} from '../utils/format' diff --git a/packages/notifications/src/index.ts b/packages/notifications/src/index.ts index 41279074d4..f5a82a0b0b 100644 --- a/packages/notifications/src/index.ts +++ b/packages/notifications/src/index.ts @@ -1 +1,10 @@ export {notificationManagerMaker} from './notification-manager' +export {useResetNotificationsConfig} from './translators/reactjs/useResetNotificationsConfig' +export { + NotificationProvider, + useNotificationManager, +} from './translators/reactjs/NotificationProvider' + +export {useNotificationsConfig} from './translators/reactjs/useNotificationsConfig' +export {useReceivedNotificationEvents} from './translators/reactjs/useReceivedNotificationEvents' +export {useUpdateNotificationsConfig} from './translators/reactjs/useUpdateNotificationsConfig' diff --git a/packages/notifications/src/translators/reactjs/NotificationProvider.test.tsx b/packages/notifications/src/translators/reactjs/NotificationProvider.test.tsx new file mode 100644 index 0000000000..f54cc74bb3 --- /dev/null +++ b/packages/notifications/src/translators/reactjs/NotificationProvider.test.tsx @@ -0,0 +1,48 @@ +import * as React from 'react' +import { + NotificationProvider, + useNotificationManager, +} from './NotificationProvider' +import {notificationManagerMaker} from '../../notification-manager' +import AsyncStorage from '@react-native-async-storage/async-storage' +import {mountAsyncStorage} from '@yoroi/common' +import {render, renderHook} from '@testing-library/react-native' + +describe('NotificationProvider', () => { + beforeEach(() => AsyncStorage.clear()) + + const eventsStorage = mountAsyncStorage({path: 'events/'}) + const configStorage = mountAsyncStorage({path: 'config/'}) + + it('should render', () => { + const manager = notificationManagerMaker({ + eventsStorage, + configStorage, + }) + + expect( + render( + + <> + , + ), + ).toBeDefined() + }) + + it('should render hook without crashing', () => { + const manager = notificationManagerMaker({ + eventsStorage, + configStorage, + }) + + const wrapper = ({children}: {children: React.ReactNode}) => ( + {children} + ) + + expect(renderHook(() => useNotificationManager(), {wrapper})).toBeDefined() + }) + + it('should crash hook if it is not wrapped in NotificationProvider', () => { + expect(() => renderHook(() => useNotificationManager())).toThrow() + }) +}) diff --git a/packages/notifications/src/translators/reactjs/NotificationProvider.tsx b/packages/notifications/src/translators/reactjs/NotificationProvider.tsx new file mode 100644 index 0000000000..be5d2670a2 --- /dev/null +++ b/packages/notifications/src/translators/reactjs/NotificationProvider.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' +import {Notifications} from '@yoroi/types' + +type NotificationContextType = { + manager: Notifications.Manager +} + +const Context = React.createContext(null) + +type Props = { + manager: Notifications.Manager + children: React.ReactNode +} + +export const NotificationProvider = ({manager, children}: Props) => { + const value = React.useMemo(() => ({manager}), [manager]) + return {children} +} + +export const useNotificationManager = () => { + const context = React.useContext(Context) + + if (context === null) { + throw new Error( + 'useNotificationManager must be used within a NotificationProvider', + ) + } + return context.manager +} diff --git a/packages/notifications/src/translators/reactjs/useNotificationsConfig.test.tsx b/packages/notifications/src/translators/reactjs/useNotificationsConfig.test.tsx new file mode 100644 index 0000000000..f86634e5a4 --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useNotificationsConfig.test.tsx @@ -0,0 +1,35 @@ +import * as React from 'react' +import {renderHook, waitFor} from '@testing-library/react-native' +import {useNotificationsConfig} from './useNotificationsConfig' +import AsyncStorage from '@react-native-async-storage/async-storage' +import {mountAsyncStorage, queryClientFixture} from '@yoroi/common' +import {notificationManagerMaker} from '../../notification-manager' +import {NotificationProvider} from './NotificationProvider' +import {QueryClientProvider} from 'react-query' + +describe('useNotificationsConfig', () => { + beforeEach(() => AsyncStorage.clear()) + + const eventsStorage = mountAsyncStorage({path: 'events/'}) + const configStorage = mountAsyncStorage({path: 'config/'}) + + it('should return notifications config', async () => { + const client = queryClientFixture() + const manager = notificationManagerMaker({ + eventsStorage, + configStorage, + }) + + const wrapper = ({children}: {children: React.ReactNode}) => ( + + + {children} + + + ) + const {result} = renderHook(() => useNotificationsConfig(), {wrapper}) + await waitFor(async () => + expect(result.current.data).toEqual(await manager.config.read()), + ) + }) +}) diff --git a/packages/notifications/src/translators/reactjs/useNotificationsConfig.ts b/packages/notifications/src/translators/reactjs/useNotificationsConfig.ts new file mode 100644 index 0000000000..5164e6b5fc --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useNotificationsConfig.ts @@ -0,0 +1,7 @@ +import {useQuery} from 'react-query' +import {useNotificationManager} from './NotificationProvider' + +export const useNotificationsConfig = () => { + const manager = useNotificationManager() + return useQuery(['notificationsConfig'], () => manager.config.read()) +} diff --git a/packages/notifications/src/translators/reactjs/useReceivedNotificationEvents.test.tsx b/packages/notifications/src/translators/reactjs/useReceivedNotificationEvents.test.tsx new file mode 100644 index 0000000000..06ad3e1460 --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useReceivedNotificationEvents.test.tsx @@ -0,0 +1,37 @@ +import * as React from 'react' +import {renderHook, waitFor} from '@testing-library/react-native' +import AsyncStorage from '@react-native-async-storage/async-storage' +import {mountAsyncStorage, queryClientFixture} from '@yoroi/common' +import {notificationManagerMaker} from '../../notification-manager' +import {NotificationProvider} from './NotificationProvider' +import {QueryClientProvider} from 'react-query' +import {useReceivedNotificationEvents} from './useReceivedNotificationEvents' + +describe('useReceivedNotificationEvents', () => { + beforeEach(() => AsyncStorage.clear()) + + const eventsStorage = mountAsyncStorage({path: 'events/'}) + const configStorage = mountAsyncStorage({path: 'config/'}) + + it('should return notification events', async () => { + const client = queryClientFixture() + const manager = notificationManagerMaker({ + eventsStorage, + configStorage, + }) + + const wrapper = ({children}: {children: React.ReactNode}) => ( + + + {children} + + + ) + const {result} = renderHook(() => useReceivedNotificationEvents(), { + wrapper, + }) + await waitFor(async () => + expect(result.current.data).toEqual(await manager.events.read()), + ) + }) +}) diff --git a/packages/notifications/src/translators/reactjs/useReceivedNotificationEvents.ts b/packages/notifications/src/translators/reactjs/useReceivedNotificationEvents.ts new file mode 100644 index 0000000000..53280041a5 --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useReceivedNotificationEvents.ts @@ -0,0 +1,15 @@ +import {useQuery, UseQueryOptions} from 'react-query' +import {Notifications as NotificationTypes} from '@yoroi/types' +import {useNotificationManager} from './NotificationProvider' + +export const useReceivedNotificationEvents = ( + options: UseQueryOptions, Error> = {}, +) => { + const manager = useNotificationManager() + const queryFn = () => manager.events.read() + return useQuery({ + queryKey: ['receivedNotificationEvents'], + queryFn, + ...options, + }) +} diff --git a/packages/notifications/src/translators/reactjs/useResetNotificationsConfig.test.tsx b/packages/notifications/src/translators/reactjs/useResetNotificationsConfig.test.tsx new file mode 100644 index 0000000000..df64748b80 --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useResetNotificationsConfig.test.tsx @@ -0,0 +1,48 @@ +import * as React from 'react' +import {act, renderHook, waitFor} from '@testing-library/react-native' +import AsyncStorage from '@react-native-async-storage/async-storage' +import {mountAsyncStorage, queryClientFixture} from '@yoroi/common' +import {notificationManagerMaker} from '../../notification-manager' +import {NotificationProvider} from './NotificationProvider' +import {QueryClientProvider} from 'react-query' +import {useResetNotificationsConfig} from './useResetNotificationsConfig' +import {Notifications} from '@yoroi/types' + +describe('useResetNotificationsConfig', () => { + beforeEach(() => AsyncStorage.clear()) + + const eventsStorage = mountAsyncStorage({path: 'events/'}) + const configStorage = mountAsyncStorage({path: 'config/'}) + + it('should allow to reset config', async () => { + const client = queryClientFixture() + const manager = notificationManagerMaker({ + eventsStorage, + configStorage, + }) + + const wrapper = ({children}: {children: React.ReactNode}) => ( + + + {children} + + + ) + const {result} = renderHook(() => useResetNotificationsConfig(), { + wrapper, + }) + await manager.config.save({ + ...(await manager.config.read()), + [Notifications.Trigger.TransactionReceived]: { + notify: false, + }, + }) + act(() => { + result.current.mutate() + }) + + await waitFor(async () => + expect(result.current.data).toEqual(await manager.config.read()), + ) + }) +}) diff --git a/packages/notifications/src/translators/reactjs/useResetNotificationsConfig.ts b/packages/notifications/src/translators/reactjs/useResetNotificationsConfig.ts new file mode 100644 index 0000000000..18e57da40b --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useResetNotificationsConfig.ts @@ -0,0 +1,20 @@ +import {UseMutationOptions} from 'react-query' +import {Notifications as NotificationTypes} from '@yoroi/types' +import {useMutationWithInvalidations} from '@yoroi/common' +import {useNotificationManager} from './NotificationProvider' + +export const useResetNotificationsConfig = ( + options: UseMutationOptions = {}, +) => { + const manager = useNotificationManager() + const mutationFn = async () => { + await manager.config.reset() + return manager.config.read() + } + + return useMutationWithInvalidations({ + mutationFn, + invalidateQueries: [['notificationsConfig']], + ...options, + }) +} diff --git a/packages/notifications/src/translators/reactjs/useUpdateNotificationsConfig.test.tsx b/packages/notifications/src/translators/reactjs/useUpdateNotificationsConfig.test.tsx new file mode 100644 index 0000000000..fbd9dd828d --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useUpdateNotificationsConfig.test.tsx @@ -0,0 +1,50 @@ +import * as React from 'react' +import {act, renderHook, waitFor} from '@testing-library/react-native' +import AsyncStorage from '@react-native-async-storage/async-storage' +import {mountAsyncStorage, queryClientFixture} from '@yoroi/common' +import {notificationManagerMaker} from '../../notification-manager' +import {NotificationProvider} from './NotificationProvider' +import {QueryClientProvider} from 'react-query' +import {Notifications} from '@yoroi/types' +import {useUpdateNotificationsConfig} from './useUpdateNotificationsConfig' + +describe('useUpdateNotificationsConfig', () => { + beforeEach(() => AsyncStorage.clear()) + + const eventsStorage = mountAsyncStorage({path: 'events/'}) + const configStorage = mountAsyncStorage({path: 'config/'}) + + it('should allow to update config', async () => { + const client = queryClientFixture() + const manager = notificationManagerMaker({ + eventsStorage, + configStorage, + }) + + const wrapper = ({children}: {children: React.ReactNode}) => ( + + + {children} + + + ) + const {result} = renderHook(() => useUpdateNotificationsConfig(), { + wrapper, + }) + + const initialConfig = await manager.config.read() + + await act(async () => { + result.current.mutate({ + ...(await manager.config.read()), + [Notifications.Trigger.TransactionReceived]: { + notify: false, + }, + }) + }) + + await waitFor(async () => + expect(await manager.config.read()).not.toEqual(initialConfig), + ) + }) +}) diff --git a/packages/notifications/src/translators/reactjs/useUpdateNotificationsConfig.ts b/packages/notifications/src/translators/reactjs/useUpdateNotificationsConfig.ts new file mode 100644 index 0000000000..83e5daabfd --- /dev/null +++ b/packages/notifications/src/translators/reactjs/useUpdateNotificationsConfig.ts @@ -0,0 +1,16 @@ +import {Notifications as NotificationTypes} from '@yoroi/types' +import {useMutationWithInvalidations} from '@yoroi/common' +import {useNotificationManager} from './NotificationProvider' + +export const useUpdateNotificationsConfig = () => { + const manager = useNotificationManager() + + const mutationFn = async (newConfig: NotificationTypes.Config) => { + await manager.config.save(newConfig) + } + + return useMutationWithInvalidations({ + mutationFn, + invalidateQueries: [['notificationsConfig']], + }) +}