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

CP-9664: Add Support for New Notification Types #2180

Merged
merged 13 commits into from
Jan 8, 2025
Merged
7 changes: 4 additions & 3 deletions packages/core-mobile/app/navigation/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { navigate } from 'utils/Navigation'
import AppNavigation from 'navigation/AppNavigation'
import { NetworkTokensTabs } from 'screens/portfolio/network/NetworkTokens'

const DELAY_NAVIGATION = 1000
const DELAY_NAVIGATION_CLAIM_REWARDS = 1000
const DELAY_NAVIGATION = 400

export const navigateToWatchlist = (coingeckoId: string | undefined): void => {
setTimeout(async () => {
Expand Down Expand Up @@ -46,7 +47,7 @@ export const navigateToChainPortfolio = (): void => {
// @ts-ignore
params: { tabIndex: NetworkTokensTabs.Tokens }
})
}, 300)
}, DELAY_NAVIGATION)
}

export const navigateToClaimRewards = (): void => {
Expand All @@ -68,5 +69,5 @@ export const navigateToClaimRewards = (): void => {
}
}
})
}, DELAY_NAVIGATION)
}, DELAY_NAVIGATION_CLAIM_REWARDS)
}
7 changes: 5 additions & 2 deletions packages/core-mobile/app/screens/drawer/DrawerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import AdvancedItem from 'screens/drawer/components/AdvancedItem'
import DrawerLogo from 'screens/drawer/components/DrawerLogo'
import NotificationsItem from 'screens/drawer/components/NotificationsItem'
import { useSelector } from 'react-redux'
import { selectIsNotificationBlocked, selectUseDarkMode } from 'store/posthog'
import {
selectIsAllNotificationsBlocked,
selectUseDarkMode
} from 'store/posthog'
import FeedbackItem from 'screens/drawer/components/FeedbackItem'
import SeedlessService from 'seedless/services/SeedlessService'
import Logger from 'utils/Logger'
Expand Down Expand Up @@ -57,7 +60,7 @@ const DrawerView = (): JSX.Element => {
}

const Main = (): JSX.Element => {
const isNotificationBlocked = useSelector(selectIsNotificationBlocked)
const isNotificationBlocked = useSelector(selectIsAllNotificationsBlocked)

const [hasRecoveryMethodsFetched, setHasRecoveryMethodsFetched] =
useState(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
notificationChannels
} from 'services/notifications/channels'
import {
selectIsBalanceChangeNotificationsBlocked,
selectIsAllNotificationsBlocked,
selectIsEarnBlocked
} from 'store/posthog'
import Logger from 'utils/Logger'
Expand All @@ -38,19 +38,18 @@ const Notifications = (): JSX.Element => {
)
const appState = useSelector(selectAppState)
const isEarnBlocked = useSelector(selectIsEarnBlocked)
const isBalanceChangeNotificationsBlocked = useSelector(
selectIsBalanceChangeNotificationsBlocked
)
const isAllNotificationsBlocked = useSelector(selectIsAllNotificationsBlocked)

const disabledChannels = useMemo(() => {
return {
[ChannelId.BALANCE_CHANGES]: isBalanceChangeNotificationsBlocked,
[ChannelId.STAKING_COMPLETE]: isEarnBlocked,
[ChannelId.PRODUCT_ANNOUNCEMENTS]: false,
[ChannelId.OFFERS_AND_PROMOTIONS]: false,
[ChannelId.MARKET_NEWS]: false
[ChannelId.BALANCE_CHANGES]: isAllNotificationsBlocked,
[ChannelId.STAKING_COMPLETE]: isAllNotificationsBlocked || isEarnBlocked,
[ChannelId.PRODUCT_ANNOUNCEMENTS]: isAllNotificationsBlocked,
[ChannelId.OFFERS_AND_PROMOTIONS]: isAllNotificationsBlocked,
[ChannelId.MARKET_NEWS]: isAllNotificationsBlocked,
[ChannelId.PRICE_ALERTS]: isAllNotificationsBlocked
}
}, [isBalanceChangeNotificationsBlocked, isEarnBlocked])
}, [isAllNotificationsBlocked, isEarnBlocked])

useEffect(() => {
if (appState === 'active') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ const EnableNotificationsModal = (): JSX.Element => {
}
}, [canGoBack, goBack])

// TODO: add title and message
return (
<WarningModal
testID="turn_on_notifications_modal"
title={'TBD title'}
message={'TBD'}
title={'Enable Push Notification?'}
message={
'Get notified about market updates, special offers, airdrops, balance changes, and more.'
}
actionText={'Turn on Notifications'}
dismissText={'Not Now'}
onAction={onTurnOnNotifications}
Expand Down
7 changes: 4 additions & 3 deletions packages/core-mobile/app/services/fcm/FCMService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
NotificationPayload,
BalanceChangeData,
NewsData,
NotificationPayloadSchema
NotificationPayloadSchema,
NotificationTypes
} from 'services/fcm/types'
import { Platform } from 'react-native'
import { DisplayNotificationParams } from 'services/notifications/types'
Expand Down Expand Up @@ -130,14 +131,14 @@ class FCMService {
}
| { url: string }
| undefined => {
if (fcmData.type === 'balance') {
if (fcmData.type === NotificationTypes.BALANCE_CHANGES) {
return {
accountAddress: fcmData.accountAddress,
chainId: fcmData.chainId,
transactionHash: fcmData.transactionHash,
url: `${PROTOCOLS.CORE}://${ACTIONS.Portfolio}`
}
} else if (fcmData.type === 'news') {
} else if (fcmData.type === NotificationTypes.NEWS) {
return {
url: fcmData.url
}
Expand Down
22 changes: 14 additions & 8 deletions packages/core-mobile/app/services/fcm/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import z, { object, string, nativeEnum, literal } from 'zod'
import { ChannelId } from 'services/notifications/channels'

export enum NotificationTypes {
BALANCE_CHANGES = 'BALANCE_CHANGES',
NEWS = 'NEWS'
}

export enum NewsEvents {
PRODUCT_ANNOUNCEMENTS = 'PRODUCT_ANNOUNCEMENTS',
OFFERS_AND_PROMOTIONS = 'OFFERS_AND_PROMOTIONS',
MARKET_NEWS = 'MARKET_NEWS'
MARKET_NEWS = 'MARKET_NEWS',
PRICE_ALERTS = 'PRICE_ALERTS'
}

export enum BalanceChangeEvents {
Expand All @@ -14,20 +20,20 @@ export enum BalanceChangeEvents {
}

export const BalanceChangeDataSchema = object({
type: literal('balance').optional(), // TODO use correct type from backend
type: literal(NotificationTypes.BALANCE_CHANGES),
event: nativeEnum(BalanceChangeEvents),
title: string().optional(),
body: string().optional(),
title: string(),
body: string(),
accountAddress: string().startsWith('0x'),
chainId: string(),
transactionHash: string().startsWith('0x')
})

export const NewsDataSchema = object({
type: literal('news').optional(), // TODO use correct type from backend
event: nativeEnum(NewsEvents), // TODO use correct events from backend
title: string().optional(),
body: string().optional(),
type: literal(NotificationTypes.NEWS),
event: nativeEnum(NewsEvents),
title: string(),
body: string(),
url: string()
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fromUnixTime, isPast } from 'date-fns'
import { Linking, Platform } from 'react-native'
import {
ChannelId,
NewsChannelId,
notificationChannels
} from 'services/notifications/channels'
import { StakeCompleteNotification } from 'store/notifications'
Expand All @@ -26,16 +27,21 @@ import {
} from './constants'

class NotificationsService {
async getNotificationSettings(): Promise<AuthorizationStatus> {
const settings = await notifee.getNotificationSettings()
return settings.authorizationStatus
}

/**
* Returns all notification channels that are blocked on system level.
* If notifications are blocked for whole app then it returns all channels.
* Map is used for optimization purposes.
*/
async getBlockedNotifications(): Promise<Map<ChannelId, boolean>> {
const settings = await notifee.getNotificationSettings()
const authorizationStatus = await this.getNotificationSettings()
const channels = await notifee.getChannels()

switch (settings.authorizationStatus) {
switch (authorizationStatus) {
case AuthorizationStatus.NOT_DETERMINED:
case AuthorizationStatus.DENIED:
return notificationChannels.reduce((map, next) => {
Expand All @@ -52,6 +58,13 @@ class NotificationsService {
}, new Map<ChannelId, boolean>())
}

async getBlockedNewsNotifications(): Promise<Map<NewsChannelId, boolean>> {
const blockedNotifications = await this.getBlockedNotifications()
blockedNotifications.delete(ChannelId.BALANCE_CHANGES)
blockedNotifications.delete(ChannelId.STAKING_COMPLETE)
return blockedNotifications as unknown as Map<NewsChannelId, boolean>
}

/**
* Tries to pull up system prompt for allowing notifications, if that doesn't
* work opens system settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function subscribeForBalanceChange({
addresses
})
).catch(error => {
Logger.error(`[subscribe.ts][subscribe]${error}`)
Logger.error(`[subscribeForBalanceChange.ts][subscribe]${error}`)
throw new Error(error)
})
if (response.ok) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Logger from 'utils/Logger'
import Config from 'react-native-config'
import messaging from '@react-native-firebase/messaging'
import fetchWithAppCheck from 'utils/httpClient'

export async function unSubscribeForBalanceChange({
Expand All @@ -14,8 +13,6 @@ export async function unSubscribeForBalanceChange({
deviceArn
})
).catch(error => {
//as fallback invalidate token so user doesn't get notifications
messaging().deleteToken()
Logger.error(`[unsubscribeForBalanceChange.ts][unsubscribe]${error}`)
throw error
})
Expand Down
31 changes: 23 additions & 8 deletions packages/core-mobile/app/services/notifications/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ export enum ChannelId {
BALANCE_CHANGES = 'balanceChanges',
PRODUCT_ANNOUNCEMENTS = 'productAnnouncements',
OFFERS_AND_PROMOTIONS = 'offersAndPromotions',
MARKET_NEWS = 'marketNews'
MARKET_NEWS = 'marketNews',
PRICE_ALERTS = 'priceAlerts'
}

export enum NewsChannelId {
PRODUCT_ANNOUNCEMENTS = 'productAnnouncements',
OFFERS_AND_PROMOTIONS = 'offersAndPromotions',
MARKET_NEWS = 'marketNews',
PRICE_ALERTS = 'priceAlerts'
}

export interface AvaxAndroidChannel extends AndroidChannel {
Expand All @@ -26,7 +34,7 @@ export const notificationChannels = [
vibration: false,
importance: AndroidImportance.DEFAULT,
title: 'Stake',
subtitle: 'Staking Complete'
subtitle: 'Staking complete alerts'
} as AvaxAndroidChannel,
{
id: ChannelId.BALANCE_CHANGES,
Expand All @@ -35,19 +43,17 @@ export const notificationChannels = [
vibration: false,
importance: AndroidImportance.DEFAULT,
title: 'Balance',
subtitle: 'Notifications when your balance changes',
subtitle: 'Wallet balance change alerts',
sound: 'core_receive'
} as AvaxAndroidChannel,

// TODO: Add titles and subtitles
{
id: ChannelId.PRODUCT_ANNOUNCEMENTS,
name: 'Product Announcements',
lights: false,
vibration: false,
importance: AndroidImportance.DEFAULT,
title: 'Product Announcements',
subtitle: 'TBD'
subtitle: 'Learn about new features and changes'
} as AvaxAndroidChannel,
{
id: ChannelId.OFFERS_AND_PROMOTIONS,
Expand All @@ -56,7 +62,7 @@ export const notificationChannels = [
vibration: false,
importance: AndroidImportance.DEFAULT,
title: 'Special Offers and Promotions',
subtitle: 'TBD'
subtitle: 'Airdrops and promotional offers'
} as AvaxAndroidChannel,
{
id: ChannelId.MARKET_NEWS,
Expand All @@ -65,6 +71,15 @@ export const notificationChannels = [
vibration: false,
importance: AndroidImportance.DEFAULT,
title: 'Market News',
subtitle: 'TBD'
subtitle: 'News and market information alerts'
} as AvaxAndroidChannel,
{
id: ChannelId.PRICE_ALERTS,
name: 'Price Alerts',
lights: false,
vibration: false,
importance: AndroidImportance.DEFAULT,
title: 'Price Alerts',
subtitle: 'Token price movement alerts'
} as AvaxAndroidChannel
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NewsEvents } from 'services/fcm/types'
import { ChannelId } from '../channels'

export const channelIdToNewsEventMap = {
[ChannelId.PRODUCT_ANNOUNCEMENTS]: NewsEvents.PRODUCT_ANNOUNCEMENTS,
[ChannelId.OFFERS_AND_PROMOTIONS]: NewsEvents.OFFERS_AND_PROMOTIONS,
[ChannelId.MARKET_NEWS]: NewsEvents.MARKET_NEWS,
[ChannelId.PRICE_ALERTS]: NewsEvents.PRICE_ALERTS
}
Loading
Loading