Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljscript committed Oct 8, 2024
1 parent 5ca0641 commit f06725d
Show file tree
Hide file tree
Showing 16 changed files with 234 additions and 164 deletions.
4 changes: 2 additions & 2 deletions apps/wallet-mobile/src/YoroiApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {AsyncStorageProvider} from '@yoroi/common'
import {LinksProvider} from '@yoroi/links'
import {NotificationProvider} from '@yoroi/notifications'
import {SetupWalletProvider} from '@yoroi/setup-wallet'
import {ThemeProvider} from '@yoroi/theme'
import React from 'react'
Expand All @@ -13,6 +14,7 @@ import {LoadingBoundary} from './components/Boundary/Boundary'
import {ErrorBoundary} from './components/ErrorBoundary/ErrorBoundary'
import {AuthProvider} from './features/Auth/AuthProvider'
import {BrowserProvider} from './features/Discover/common/BrowserProvider'
import {notificationManager} from './features/Notifications/useCases/common/notification-manager'
import {PortfolioTokenActivityProvider} from './features/Portfolio/common/PortfolioTokenActivityProvider'
import {CurrencyProvider} from './features/Settings/Currency/CurrencyContext'
import {AutomaticWalletOpenerProvider} from './features/WalletManager/context/AutomaticWalletOpeningProvider'
Expand All @@ -28,8 +30,6 @@ import {useMigrations} from './kernel/storage/migrations/useMigrations'
import {rootStorage} from './kernel/storage/rootStorage'
import {PoolTransitionProvider} from './legacy/Staking/PoolTransition/PoolTransitionProvider'
import {useThemeStorageMaker} from './yoroi-wallets/hooks'
import {NotificationProvider} from '@yoroi/notifications'
import {notificationManager} from './features/Notifications/useCases/common/notification-manager'

enableScreens(true)
enableFreeze(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import {SafeAreaView} from 'react-native-safe-area-context'

import {Button} from '../../../components/Button/Button'
import {ScrollView} from '../../../components/ScrollView/ScrollView'
import {createTransactionReceivedNotification} from './common/transaction-received-notification'
import {notificationManager} from './common/notification-manager'
import {createTransactionReceivedNotification} from './common/transaction-received-notification'

export const NotificationsDevScreen = () => {
const manager = notificationManager
return (
<NotificationProvider manager={manager}>
<NotificationProvider manager={notificationManager}>
<Screen />
</NotificationProvider>
)
Expand Down Expand Up @@ -64,7 +63,11 @@ const Screen = () => {
}

const ReceivedNotificationsList = () => {
const {data: receivedNotifications} = useReceivedNotificationEvents()
const {data: receivedNotifications = []} = useReceivedNotificationEvents()
const sortedNotifications = React.useMemo(
() => [...receivedNotifications].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()),
[receivedNotifications],
)
return (
<View>
<Text
Expand All @@ -76,7 +79,7 @@ const ReceivedNotificationsList = () => {
Received Notifications
</Text>

{receivedNotifications?.map((notification) => (
{sortedNotifications.map((notification) => (
<ReceivedNotification key={notification.id} notification={notification} />
))}
</View>
Expand All @@ -87,7 +90,10 @@ const ReceivedNotification = ({notification}: {notification: NotificationTypes.E
const readStatus = notification.isRead ? 'Read' : 'Unread'

if (notification.trigger === NotificationTypes.Trigger.TransactionReceived) {
const title = `You received a transaction at ${new Date(notification.date).toLocaleString()}`
const date = new Date(notification.date)
const title = `[${notification.id}] You received a transaction at ${date.toLocaleDateString(
'en-US',
)} ${date.toLocaleTimeString('en-US')}`

return (
<View>
Expand All @@ -114,6 +120,7 @@ const ReceivedNotification = ({notification}: {notification: NotificationTypes.E
const NotificationSettings = () => {
const {data: notificationsConfig} = useNotificationsConfig()
const {mutate: saveConfig} = useUpdateNotificationsConfig()
const {events} = useNotificationManager()
const {mutate: resetConfig} = useResetNotificationsConfig({
onSuccess: (config) => setLocalConfig(config),
})
Expand Down Expand Up @@ -151,7 +158,10 @@ const NotificationSettings = () => {
})
}

const handleOnReset = () => resetConfig()
const handleOnReset = () => {
resetConfig()
events.clear()
}

return (
<View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {Notification, Notifications} from '@jamsinclair/react-native-notifications'
import {Notifications} from '@jamsinclair/react-native-notifications'
import {NotificationBackgroundFetchResult} from '@jamsinclair/react-native-notifications'
import {useEffect} from 'react'

import {notificationManager} from './notification-manager'
import {parseNotificationId} from './notifications'
import {useTransactionReceivedNotifications} from './transaction-received-notification'

let initialized = false
Expand All @@ -9,13 +12,20 @@ const init = () => {
if (initialized) return
initialized = true
Notifications.registerRemoteNotifications()
Notifications.events().registerNotificationReceivedForeground((_notification: Notification, completion) => {
Notifications.events().registerNotificationReceivedForeground((_notification, completion) => {
completion({alert: true, sound: true, badge: true})
})
// TODO: Can we remove this?
Notifications.events().registerNotificationReceivedBackground((notification: Notification) => {
console.log(`Notification received in background: ${notification.title} : ${notification.body}`)

Notifications.events().registerNotificationReceivedBackground((_notification, completion) => {
completion(NotificationBackgroundFetchResult.NEW_DATA)
})

Notifications.events().registerNotificationOpened((notification, completion) => {
const id = parseNotificationId(notification.identifier)
notificationManager.events.markAsRead(id)
completion()
})

notificationManager.hydrate()

return () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {mountAsyncStorage} from '@yoroi/common'
import {notificationManagerMaker} from '@yoroi/notifications'
import {displayNotificationEvent} from './notifications'
import {Notifications} from '@yoroi/types'

import {displayNotificationEvent} from './notifications'
import {transactionReceivedSubject} from './transaction-received-notification'

const appStorage = mountAsyncStorage({path: '/'})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import {Notification, Notifications} from '@jamsinclair/react-native-notifications'
import {Notifications as NotificationTypes} from '@yoroi/types'

export const generateNotificationId = (): number => {
return generateRandomInteger(0, Number.MAX_SAFE_INTEGER)
}

export const parseNotificationId = (id: string): number => {
return parseInt(id, 10)
}

const generateRandomInteger = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1)) + min
}

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')
sendNotification({
title: 'Transaction received',
body: 'You have received a new transaction',
id: notificationEvent.id,
})
}
}

const sendNotification = (title: string, body: string) => {
const sendNotification = (options: {title: string; body: string; id: number}) => {
const notification = new Notification({
title,
body,
title: options.title,
body: options.body,
sound: 'default',
})
Notifications.postLocalNotification(notification.payload)
Notifications.postLocalNotification(notification.payload, options.id)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ 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 {TRANSACTION_DIRECTION} from '../../../../yoroi-wallets/types/other'
import {useWalletManager} from '../../../WalletManager/context/WalletManagerProvider'
import {WalletManager, walletManager} from '../../../WalletManager/wallet-manager'
import {notificationManager} from './notification-manager'
import {generateNotificationId} from './notifications'

const BACKGROUND_FETCH_TASK = 'yoroi-notifications-background-fetch'

// Check is needed for hot reloading, as task can not be defined twice
if (!TaskManager.isTaskDefined(BACKGROUND_FETCH_TASK)) {
const appStorage = mountAsyncStorage({path: '/'})
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
Expand All @@ -24,19 +27,15 @@ if (!TaskManager.isTaskDefined(BACKGROUND_FETCH_TASK)) {
})
}

export async function registerBackgroundFetchAsync() {
const registerBackgroundFetchAsync = () => {
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 10, // 10 minutes
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
minimumInterval: 60 * 10,
stopOnTerminate: false,
startOnBoot: true,
})
}

// 3. (Optional) Unregister tasks by specifying the task name
// This will cancel any future background fetch calls that match the given name
// Note: This does NOT need to be in the global scope and CAN be used in your React components!
export async function unregisterBackgroundFetchAsync() {
console.log('unregistering background fetch')
const unregisterBackgroundFetchAsync = () => {
return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK)
}

Expand All @@ -50,38 +49,36 @@ const syncAllWallets = async (walletManager: WalletManager) => {
await Promise.all(promises)
}

export const checkForNewTransactions = async (walletManager: WalletManager, appStorage: App.Storage) => {
const checkForNewTransactions = async (walletManager: WalletManager, appStorage: App.Storage) => {
const walletIds = [...walletManager.walletMetas.keys()]
const notificationsAsyncStorage = appStorage.join('notifications-transaction-received-subject/')
const notifications: NotificationTypes.TransactionReceivedEvent[] = []

for (const id of walletIds) {
const wallet = walletManager.getWalletById(id)
for (const walletId of walletIds) {
const wallet = walletManager.getWalletById(walletId)
if (!wallet) continue
const processed = (await notificationsAsyncStorage.getItem<string[]>(id)) || []
const txIds = getTxIds(wallet)
const storage = buildStorage(appStorage, walletId)
const processed = await storage.getProcessedTransactions()
const allTxIds = getTxIds(wallet)

if (processed.length === 0) {
console.log(`Wallet ${id} has no processed tx ids`)
await notificationsAsyncStorage.setItem(id, txIds)
await storage.addProcessedTransactions(allTxIds)
continue
}

const newTxIds = txIds.filter((txId) => !processed.includes(txId))
const newTxIds = allTxIds.filter((txId) => !processed.includes(txId))

if (newTxIds.length === 0) {
console.log(`Wallet ${id} has no new tx ids on network ${wallet.networkManager.network}`)
continue
}
console.log('new tx ids', newTxIds)
await notificationsAsyncStorage.setItem(id, [...processed, ...newTxIds])

await storage.addProcessedTransactions(newTxIds)

newTxIds.forEach((id) => {
const metadata: NotificationTypes.TransactionReceivedEvent['metadata'] = {
txId: id,
isSentByUser: false,
nextTxsCounter: 1,
previousTxsCounter: 0,
isSentByUser: wallet.transactions[id]?.direction === TRANSACTION_DIRECTION.SENT,
nextTxsCounter: newTxIds.length + processed.length,
previousTxsCounter: processed.length,
}
notifications.push(createTransactionReceivedNotification(metadata))
})
Expand All @@ -99,7 +96,7 @@ export const createTransactionReceivedNotification = (
metadata: NotificationTypes.TransactionReceivedEvent['metadata'],
) => {
return {
id: uuid.v4(),
id: generateNotificationId(),
date: new Date().toISOString(),
isRead: false,
trigger: NotificationTypes.Trigger.TransactionReceived,
Expand All @@ -121,7 +118,7 @@ export const useTransactionReceivedNotifications = () => {
}, [])

React.useEffect(() => {
const s1 = walletManager.syncWalletInfos$.subscribe(async (status) => {
const subscription = 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
Expand All @@ -132,8 +129,26 @@ export const useTransactionReceivedNotifications = () => {
})

return () => {
s1.unsubscribe()
subscription.unsubscribe()
}
}, [walletManager, asyncStorage, transactionReceivedSubject])
return transactionReceivedSubject
}, [walletManager, asyncStorage])
}

const buildStorage = (appStorage: App.Storage, walletId: string) => {
const storage = appStorage.join(`wallet/${walletId}/transaction-received-notification-history/`)

const getProcessedTransactions = async () => {
return (await storage.getItem<string[]>('processed')) || []
}

const addProcessedTransactions = async (txIds: string[]) => {
const processed = await getProcessedTransactions()
const newProcessed = [...processed, ...txIds]
await storage.setItem('processed', newProcessed)
}

return {
getProcessedTransactions,
addProcessedTransactions,
}
}
Loading

0 comments on commit f06725d

Please sign in to comment.