diff --git a/app/components/UI/Carousel/index.tsx b/app/components/UI/Carousel/index.tsx index b2385cad8f14..00629ad13535 100644 --- a/app/components/UI/Carousel/index.tsx +++ b/app/components/UI/Carousel/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, FC, useMemo, useEffect } from 'react'; +import React, { useState, useCallback, FC, useMemo } from 'react'; import { View, TouchableOpacity, @@ -6,9 +6,12 @@ import { Linking, Image, FlatList, + NativeSyntheticEvent, + NativeScrollEvent, } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; +import debounce from 'lodash/debounce'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { styleSheet } from './styles'; import { CarouselProps, CarouselSlide, NavigationAction } from './types'; @@ -23,6 +26,7 @@ import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletV import { PREDEFINED_SLIDES, BANNER_IMAGES } from './constants'; import { useStyles } from '../../../component-library/hooks'; import { selectDismissedBanners } from '../../../selectors/banner'; +import { MetaMetricsEvents } from '../../../core/Analytics'; ///: BEGIN:ONLY_INCLUDE_IF(solana) import { selectSelectedInternalAccount, @@ -118,13 +122,11 @@ export const Carousel: FC = ({ style }) => { ///: END:ONLY_INCLUDE_IF trackEvent( - createEventBuilder({ - category: 'Banner Select', - properties: { - name: slideId, - ...extraProperties, - }, - }).build(), + createEventBuilder(MetaMetricsEvents.CAROUSEL_BANNER_CLICKED) + .addProperties({ + bannerName: slideId, + }) + .build(), ); ///: BEGIN:ONLY_INCLUDE_IF(solana) @@ -157,9 +159,51 @@ export const Carousel: FC = ({ style }) => { const handleClose = useCallback( (slideId: string) => { + const isLastVisibleBanner = visibleSlides.length === 1; dispatch(dismissBanner(slideId)); + + if (isLastVisibleBanner) { + trackEvent( + createEventBuilder( + MetaMetricsEvents.CAROUSEL_BANNER_CLOSE_ALL, + ).build(), + ); + } + }, + [dispatch, trackEvent, createEventBuilder, visibleSlides.length], + ); + + const handleNavigationChange = useCallback( + (newIndex: number) => { + if (newIndex !== selectedIndex && newIndex < visibleSlides.length) { + trackEvent( + createEventBuilder(MetaMetricsEvents.CAROUSEL_BANNER_NAVIGATE) + .addProperties({ + from_banner: visibleSlides[selectedIndex]?.id, + to_banner: visibleSlides[newIndex]?.id, + navigation_method: 'swipe', + }) + .build(), + ); + setSelectedIndex(newIndex); + } + }, + [selectedIndex, visibleSlides, trackEvent, createEventBuilder], + ); + + const debouncedHandleNavigationChange = useMemo( + () => debounce(handleNavigationChange, 50), + [handleNavigationChange], + ); + + const handleMomentumScrollEnd = useCallback( + (event: NativeSyntheticEvent) => { + const offset = event.nativeEvent.contentOffset.x; + const width = event.nativeEvent.layoutMeasurement.width; + const newIndex = Math.round(offset / width); + debouncedHandleNavigationChange(newIndex); }, - [dispatch], + [debouncedHandleNavigationChange], ); const renderBannerSlides = useCallback( @@ -218,20 +262,6 @@ export const Carousel: FC = ({ style }) => { ], ); - // Track banner display events when visible slides change - useEffect(() => { - visibleSlides.forEach((slide) => { - trackEvent( - createEventBuilder({ - category: 'Banner Display', - properties: { - name: slide.id, - }, - }).build(), - ); - }); - }, [visibleSlides, trackEvent, createEventBuilder]); - const renderProgressDots = useMemo( () => ( = ({ style }) => { horizontal pagingEnabled showsHorizontalScrollIndicator={false} - onMomentumScrollEnd={(event) => { - const newIndex = Math.round( - event.nativeEvent.contentOffset.x / - event.nativeEvent.layoutMeasurement.width, - ); - setSelectedIndex(newIndex); - }} + onMomentumScrollEnd={handleMomentumScrollEnd} /> {!isSingleSlide && renderProgressDots} diff --git a/app/core/Analytics/MetaMetrics.events.ts b/app/core/Analytics/MetaMetrics.events.ts index 1b0dfc1b4a0f..ca35cbbff6a7 100644 --- a/app/core/Analytics/MetaMetrics.events.ts +++ b/app/core/Analytics/MetaMetrics.events.ts @@ -431,6 +431,11 @@ enum EVENT_NAME { // RPC Failover RPC_SERVICE_UNAVAILABLE = 'RPC Service Unavailable', RPC_SERVICE_DEGRADED = 'RPC Service Degraded', + + // Carousel + CAROUSEL_BANNER_CLICKED = 'Carousel Banner Clicked', + CAROUSEL_BANNER_CLOSE_ALL = 'Carousel Banner Close All', + CAROUSEL_BANNER_NAVIGATE = 'Carousel Banner Navigate', } enum ACTIONS { @@ -1030,6 +1035,11 @@ const events = { RPC_SERVICE_UNAVAILABLE: generateOpt(EVENT_NAME.RPC_SERVICE_UNAVAILABLE), RPC_SERVICE_DEGRADED: generateOpt(EVENT_NAME.RPC_SERVICE_DEGRADED), + // Carousel + CAROUSEL_BANNER_CLICKED: generateOpt(EVENT_NAME.CAROUSEL_BANNER_CLICKED), + CAROUSEL_BANNER_CLOSE_ALL: generateOpt(EVENT_NAME.CAROUSEL_BANNER_CLOSE_ALL), + CAROUSEL_BANNER_NAVIGATE: generateOpt(EVENT_NAME.CAROUSEL_BANNER_NAVIGATE), + // Bridge BRIDGE_PAGE_VIEWED: generateOpt(EVENT_NAME.BRIDGE_PAGE_VIEWED), SWAP_PAGE_VIEWED: generateOpt(EVENT_NAME.SWAP_PAGE_VIEWED), // Temporary event until unified swap/bridge is done