diff --git a/packages/core-mobile/app/new/common/hooks/useTokenDetails.ts b/packages/core-mobile/app/new/common/hooks/useTokenDetails.ts index 5ea6d4e693..68b3da0923 100644 --- a/packages/core-mobile/app/new/common/hooks/useTokenDetails.ts +++ b/packages/core-mobile/app/new/common/hooks/useTokenDetails.ts @@ -1,21 +1,23 @@ -import { useCallback, useEffect, useState, useMemo } from 'react' import { VsCurrencyType } from '@avalabs/core-coingecko-sdk' +import { getTokenAddress, getTokenChainId } from 'features/track/utils/utils' +import { useGetTrendingToken } from 'hooks/watchlist/useGetTrendingTokens' +import { useWatchlist } from 'hooks/watchlist/useWatchlist' +import { useCoreBrowser } from 'new/common/hooks/useCoreBrowser' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { InteractionManager } from 'react-native' import { useDispatch, useSelector } from 'react-redux' +import AnalyticsService from 'services/analytics/AnalyticsService' +import { ChannelId } from 'services/notifications/channels' +import TokenService from 'services/token/TokenService' +import { Ranges, TrendingToken } from 'services/token/types' +import { selectSelectedCurrency } from 'store/settings/currency' import { MarketToken, MarketType, selectIsWatchlistFavorite, toggleWatchListFavorite } from 'store/watchlist' -import { InteractionManager } from 'react-native' -import TokenService from 'services/token/TokenService' -import { useWatchlist } from 'hooks/watchlist/useWatchlist' -import { useGetTrendingToken } from 'hooks/watchlist/useGetTrendingTokens' import { getSocialHandle } from 'utils/getSocialHandle/getSocialHandle' -import { Ranges, TrendingToken } from 'services/token/types' -import { selectSelectedCurrency } from 'store/settings/currency' -import { useCoreBrowser } from 'new/common/hooks/useCoreBrowser' -import { getTokenAddress, getTokenChainId } from 'features/track/utils/utils' import { usePrevious } from './usePrevious' const isTrendingToken = (token: MarketToken | undefined): boolean => @@ -221,8 +223,17 @@ export const useTokenDetails = ({ ]) const handleFavorite = useCallback(() => { + AnalyticsService.capture( + isFavorite + ? 'PushNotificationUnsubscribed' + : 'PushNotificationSubscribed', + { + channelId: ChannelId.FAV_TOKEN_PRICE_ALERTS, + tokenId + } + ) dispatch(toggleWatchListFavorite(tokenId)) - }, [tokenId, dispatch]) + }, [isFavorite, dispatch, tokenId]) const prevChartDays = usePrevious(chartDays) diff --git a/packages/core-mobile/app/services/fcm/FCMService.ts b/packages/core-mobile/app/services/fcm/FCMService.ts index 6f7129c256..893aa10fc0 100644 --- a/packages/core-mobile/app/services/fcm/FCMService.ts +++ b/packages/core-mobile/app/services/fcm/FCMService.ts @@ -1,11 +1,14 @@ import messaging from '@react-native-firebase/messaging' -import Logger from 'utils/Logger' -import NotificationsService from 'services/notifications/NotificationsService' import { ACTIONS, DeepLinkOrigin, PROTOCOLS } from 'contexts/DeeplinkContext/types' +import { handleDeeplink } from 'contexts/DeeplinkContext/utils/handleDeeplink' +import { router } from 'expo-router' +import { Platform } from 'react-native' +import { CORE_UNIVERSAL_LINK_HOSTS } from 'resources/Constants' +import AnalyticsService from 'services/analytics/AnalyticsService' import { BalanceChangeData, BalanceChangeEvents, @@ -15,15 +18,13 @@ import { NotificationPayloadSchema, NotificationTypes } from 'services/fcm/types' -import { Platform } from 'react-native' -import { DisplayNotificationParams } from 'services/notifications/types' import { ChannelId, DEFAULT_ANDROID_CHANNEL } from 'services/notifications/channels' -import { handleDeeplink } from 'contexts/DeeplinkContext/utils/handleDeeplink' -import { CORE_UNIVERSAL_LINK_HOSTS } from 'resources/Constants' -import { router } from 'expo-router' +import NotificationsService from 'services/notifications/NotificationsService' +import { DisplayNotificationParams } from 'services/notifications/types' +import Logger from 'utils/Logger' type UnsubscribeFunc = () => void @@ -113,8 +114,10 @@ class FCMService { ): DisplayNotificationParams => { if (!fcm.notification) throw Error('No notification payload') const data = this.#extractDeepLinkData(fcm.data) + return { - channelId: fcm.notification.android?.channelId, + channelId: + fcm.notification.android?.channelId ?? EVENT_TO_CH_ID[fcm.data.event], title: fcm.notification.title, body: fcm.notification.body, sound: fcm.notification.sound, @@ -131,7 +134,7 @@ class FCMService { transactionHash: string url: string } - | { url: string } + | { url: string; channelId: string } | undefined => { if (fcmData.type === NotificationTypes.BALANCE_CHANGES) { return { @@ -143,7 +146,8 @@ class FCMService { } else if (fcmData.type === NotificationTypes.NEWS) { return { // TODO: remove urlV2 after backend is updated to send just url for NEWS notifications - url: fcmData.urlV2 ?? fcmData.url ?? '' + url: fcmData.urlV2 ?? fcmData.url ?? '', + channelId: EVENT_TO_CH_ID[fcmData.event] as string } } } @@ -176,6 +180,7 @@ class FCMService { ) return } + const notificationData = this.#prepareNotificationData(result.data) if ( @@ -204,6 +209,15 @@ class FCMService { params: { deeplinkUrl: link.url } }) }) + if ( + typeof notificationData.data?.channelId === 'string' && + notificationData.data?.channelId.length !== 0 + ) { + AnalyticsService.capture('PushNotificationPressed', { + channelId: notificationData.data.channelId, + deeplinkUrl: notificationData.data.url + }) + } }) } @@ -218,6 +232,7 @@ class FCMService { ) return } + if (result.data.notification) { //skip, FCM sdk handles this already return diff --git a/packages/core-mobile/app/services/notifications/NotificationsService.ts b/packages/core-mobile/app/services/notifications/NotificationsService.ts index 78a7ceca01..830d659b7f 100644 --- a/packages/core-mobile/app/services/notifications/NotificationsService.ts +++ b/packages/core-mobile/app/services/notifications/NotificationsService.ts @@ -9,19 +9,20 @@ import notifee, { TriggerNotification, TriggerType } from '@notifee/react-native' +import messaging from '@react-native-firebase/messaging' +import { HandleNotificationCallback } from 'contexts/DeeplinkContext/types' import { fromUnixTime, isPast } from 'date-fns' import { Linking, Platform } from 'react-native' +import AnalyticsService from 'services/analytics/AnalyticsService' import { ChannelId, NewsChannelId, notificationChannels } from 'services/notifications/channels' -import { StakeCompleteNotification } from 'store/notifications' -import Logger from 'utils/Logger' -import { HandleNotificationCallback } from 'contexts/DeeplinkContext/types' import { DisplayNotificationParams } from 'services/notifications/types' +import { StakeCompleteNotification } from 'store/notifications' import { audioFiles } from 'utils/AudioFeedback' -import messaging from '@react-native-firebase/messaging' +import Logger from 'utils/Logger' import { LAUNCH_ACTIVITY, PressActionId, @@ -252,6 +253,14 @@ class NotificationsService { if (detail?.notification?.id) { await this.cancelTriggerNotification(detail.notification.id) } + + if (detail?.notification?.data) { + AnalyticsService.capture('PushNotificationPressed', { + channelId: detail.notification?.data?.channelId as string, + deeplinkUrl: detail.notification?.data?.url as string + }) + } + callback(detail?.notification?.data) } @@ -311,7 +320,7 @@ class NotificationsService { } /** - * @param channelId For Android only + * @param channelId * @param title * @param body * @param sound For iOS only diff --git a/packages/core-mobile/app/store/notifications/listeners/handleAfterLoginFlows.ts b/packages/core-mobile/app/store/notifications/listeners/handleAfterLoginFlows.ts index 77efd3f89c..4baaa28365 100644 --- a/packages/core-mobile/app/store/notifications/listeners/handleAfterLoginFlows.ts +++ b/packages/core-mobile/app/store/notifications/listeners/handleAfterLoginFlows.ts @@ -1,6 +1,9 @@ +import { showAlert } from '@avalabs/k2-alpine' import { AuthorizationStatus } from '@notifee/react-native' import { AnyAction } from '@reduxjs/toolkit' import { navigateWithPromise } from 'common/utils/navigateWithPromise' +import { waitForInteractions } from 'common/utils/waitForInteractions' +import AnalyticsService from 'services/analytics/AnalyticsService' import { AppUpdateService } from 'services/AppUpdateService/AppUpdateService' import NotificationsService from 'services/notifications/NotificationsService' import { @@ -14,8 +17,6 @@ import { setViewOnce, ViewOnceKey } from 'store/viewOnce' -import { showAlert } from '@avalabs/k2-alpine' -import { waitForInteractions } from 'common/utils/waitForInteractions' import Config from 'react-native-config' import { turnOnAllNotifications } from '../slice' @@ -99,6 +100,7 @@ const promptEnableNotificationsIfNeeded = async ( { text: 'Not now', onPress: () => { + AnalyticsService.capture('PushNotificationRejected') resolve() } }, @@ -113,6 +115,7 @@ const promptEnableNotificationsIfNeeded = async ( return } dispatch(turnOnAllNotifications()) + AnalyticsService.capture('PushNotificationAccepted') resolve() } } diff --git a/packages/core-mobile/app/store/notifications/listeners/handleTurnOffNotificationsFor.ts b/packages/core-mobile/app/store/notifications/listeners/handleTurnOffNotificationsFor.ts index ae8c887d72..2b9d9cd2bc 100644 --- a/packages/core-mobile/app/store/notifications/listeners/handleTurnOffNotificationsFor.ts +++ b/packages/core-mobile/app/store/notifications/listeners/handleTurnOffNotificationsFor.ts @@ -1,5 +1,6 @@ -import { AppListenerEffectAPI } from 'store/types' +import AnalyticsService from 'services/analytics/AnalyticsService' import { ChannelId } from 'services/notifications/channels' +import { AppListenerEffectAPI } from 'store/types' import { setNotificationSubscriptions } from '../slice' import { handleStakeCompleteNotificationCleanup } from './handleNotificationCleanup' @@ -12,4 +13,8 @@ export const handleTurnOffNotificationsFor = async ( if (channelId === ChannelId.STAKING_COMPLETE) { handleStakeCompleteNotificationCleanup(listenerApi) } + + AnalyticsService.capture('PushNotificationUnsubscribed', { + channelId + }) } diff --git a/packages/core-mobile/app/store/notifications/listeners/handleTurnOnNotificationsFor.ts b/packages/core-mobile/app/store/notifications/listeners/handleTurnOnNotificationsFor.ts index 5a2d8ec17f..7d82d52f4b 100644 --- a/packages/core-mobile/app/store/notifications/listeners/handleTurnOnNotificationsFor.ts +++ b/packages/core-mobile/app/store/notifications/listeners/handleTurnOnNotificationsFor.ts @@ -1,9 +1,10 @@ -import { AppListenerEffectAPI } from 'store/types' +import AnalyticsService from 'services/analytics/AnalyticsService' import { ChannelId, notificationChannels } from 'services/notifications/channels' import NotificationsService from 'services/notifications/NotificationsService' +import { AppListenerEffectAPI } from 'store/types' import { setNotificationSubscriptions } from '../slice' export const handleTurnOnNotificationsFor = async ( @@ -20,4 +21,8 @@ export const handleTurnOnNotificationsFor = async ( if (blockedNotifications.has(channelId)) { NotificationsService.openSystemSettings() } + + AnalyticsService.capture('PushNotificationSubscribed', { + channelId + }) } diff --git a/packages/core-mobile/app/types/analytics.ts b/packages/core-mobile/app/types/analytics.ts index 7c647718a5..d1f3b2b4ae 100644 --- a/packages/core-mobile/app/types/analytics.ts +++ b/packages/core-mobile/app/types/analytics.ts @@ -213,4 +213,19 @@ export type AnalyticsEvents = { //SOLANA SolanaSwapFeeAccountNotInitialized: { mint: string } + + // PUSH NOTIFICATIONS + PushNotificationAccepted: undefined + PushNotificationRejected: undefined + PushNotificationPressed: { + channelId: string + deeplinkUrl?: string + } + PushNotificationUnsubscribed: { + channelId: string + } + PushNotificationSubscribed: { + channelId: string + tokenId?: string + } }