From 1e27466181fd7b0cc5fbc2905e3b89d51d1971fb Mon Sep 17 00:00:00 2001 From: vinnyhoward Date: Thu, 24 Apr 2025 22:12:42 -0600 Subject: [PATCH] feat: add more rich track events for carousel --- app/components/UI/Carousel/index.tsx | 168 +++++++++++++---------- app/core/Analytics/MetaMetrics.events.ts | 10 ++ 2 files changed, 107 insertions(+), 71 deletions(-) diff --git a/app/components/UI/Carousel/index.tsx b/app/components/UI/Carousel/index.tsx index 2239a554618a..057dab5dbfb0 100644 --- a/app/components/UI/Carousel/index.tsx +++ b/app/components/UI/Carousel/index.tsx @@ -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'; export const Carousel: FC = ({ style }) => { const [selectedIndex, setSelectedIndex] = useState(0); @@ -77,12 +81,11 @@ export const Carousel: FC = ({ style }) => { const handleSlideClick = useCallback( (slideId: string, navigation: NavigationAction) => { trackEvent( - createEventBuilder({ - category: 'Banner Select', - properties: { - name: slideId, - }, - }).build(), + createEventBuilder(MetaMetricsEvents.CAROUSEL_BANNER_CLICKED) + .addProperties({ + bannerName: slideId, + }) + .build(), ); if (navigation.type === 'url') { @@ -102,77 +105,106 @@ 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], + [dispatch, trackEvent, createEventBuilder, visibleSlides.length], ); - const renderBannerSlides = useCallback( - ({ item: slide }: { item: CarouselSlide }) => { - trackEvent( - createEventBuilder({ - category: 'Banner Display', - properties: { - name: slide.id, - }, - }).build(), - ); + 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], + ); - return ( - handleSlideClick(slide.id, slide.navigation)} - onPressIn={() => setPressedSlideId(slide.id)} - onPressOut={() => setPressedSlideId(null)} - > - - - - - - - - {slide.title} - - - {slide.description} - - - - {!slide.undismissable && ( - handleClose(slide.id)} + 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); + }, + [debouncedHandleNavigationChange], + ); + + const renderBannerSlides = useCallback( + ({ item: slide }: { item: CarouselSlide }) => ( + handleSlideClick(slide.id, slide.navigation)} + onPressIn={() => setPressedSlideId(slide.id)} + onPressOut={() => setPressedSlideId(null)} + > + + + + + + + - - - )} + {slide.title} + + + {slide.description} + + - - ); - }, + {!slide.undismissable && ( + handleClose(slide.id)} + > + + + )} + + + ), [ styles, handleSlideClick, handleClose, colors.icon.default, pressedSlideId, - trackEvent, - createEventBuilder, ], ); @@ -218,13 +250,7 @@ export const Carousel: FC = ({ 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 d6b3fd4a00cc..077149946c20 100644 --- a/app/core/Analytics/MetaMetrics.events.ts +++ b/app/core/Analytics/MetaMetrics.events.ts @@ -429,6 +429,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 { @@ -1027,6 +1032,11 @@ const events = { // RPC Failover 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), }; /**