diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index 02f7a4abe07f..67458ec491ab 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -1,6 +1,6 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native'; -import React, { useCallback, useMemo, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { BackHandler, StyleSheet, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useSelector } from 'react-redux'; import { WalletViewSelectorsIDs } from '../Wallet/WalletView.testIds'; @@ -23,11 +23,13 @@ import { KnownCaipNamespace } from '@metamask/utils'; import { selectIsEvmNetworkSelected } from '../../../selectors/multichainNetworkController'; import { selectChainId } from '../../../selectors/networkController'; import { selectNetworkName } from '../../../selectors/networkInfos'; +import Routes from '../../../constants/navigation/Routes'; import { useParams } from '../../../util/navigation/navUtils'; import { getNetworkImageSource } from '../../../util/networks'; import { useTheme } from '../../../util/theme'; import { TabsList } from '../../../component-library/components-temp/Tabs'; import { createNetworkManagerNavDetails } from '../../UI/NetworkManager'; +import { selectMoneyHomeScreenEnabledFlag } from '../../UI/Money/selectors/featureFlags'; import { selectPerpsEnabledFlag } from '../../UI/Perps'; import { selectPredictEnabledFlag } from '../../UI/Predict/selectors/featureFlags'; import PredictTransactionsView from '../../UI/Predict/views/PredictTransactionsView/PredictTransactionsView'; @@ -106,6 +108,10 @@ const ActivityView = () => { const currentNetworkName = getNetworkInfo(0)?.networkName; + const isMoneyHomeScreenEnabled = useSelector( + selectMoneyHomeScreenEnabledFlag, + ); + const params = useParams(); const perpsEnabledFlag = useSelector(selectPerpsEnabledFlag); const isPerpsEnabled = useMemo( @@ -123,13 +129,34 @@ const ActivityView = () => { navigation.navigate(...createNetworkManagerNavDetails({})); }; + // Prevent back button returning to confirmation screen in case that users are redirected after a successful transaction. + const handleNavigateHome = useCallback(() => { + navigation.navigate(Routes.HOME_TABS); + }, [navigation]); + const handleBackPress = useCallback(() => { - if (navigation.canGoBack()) { + if (isMoneyHomeScreenEnabled) { + handleNavigateHome(); + } else if (navigation.canGoBack()) { navigation.goBack(); } - }, [navigation]); + }, [isMoneyHomeScreenEnabled, navigation, handleNavigateHome]); + + useEffect(() => { + if (!isMoneyHomeScreenEnabled) return; + + const subscription = BackHandler.addEventListener( + 'hardwareBackPress', + () => { + handleNavigateHome(); + return true; + }, + ); + + return () => subscription.remove(); + }, [navigation, isMoneyHomeScreenEnabled, handleNavigateHome]); - const showBackButton = params.showBackButton || false; + const showBackButton = params.showBackButton || isMoneyHomeScreenEnabled; // Calculate dynamic tab indices based on which tabs are enabled // Tab order: Transactions (0), Orders (1), Perps (conditional), Predict (conditional) diff --git a/app/components/Views/ActivityView/index.test.tsx b/app/components/Views/ActivityView/index.test.tsx index c1dc00d2289d..31e0ff0d4feb 100644 --- a/app/components/Views/ActivityView/index.test.tsx +++ b/app/components/Views/ActivityView/index.test.tsx @@ -1,14 +1,21 @@ import React from 'react'; import ActivityView from '.'; +import { BackHandler } from 'react-native'; import { backgroundState } from '../../../util/test/initial-root-state'; import renderWithProvider from '../../../util/test/renderWithProvider'; import { createStackNavigator } from '@react-navigation/stack'; -import { fireEvent } from '@testing-library/react-native'; +import { cleanup, fireEvent } from '@testing-library/react-native'; // eslint-disable-next-line import-x/no-namespace import * as networkManagerUtils from '../../UI/NetworkManager'; import { useCurrentNetworkInfo } from '../../hooks/useCurrentNetworkInfo'; import { ActivitiesViewSelectorsIDs } from './ActivitiesView.testIds'; import { WalletViewSelectorsIDs } from '../Wallet/WalletView.testIds'; +import Routes from '../../../constants/navigation/Routes'; + +let mockMoneyHomeScreenEnabled = false; +jest.mock('../../UI/Money/selectors/featureFlags', () => ({ + selectMoneyHomeScreenEnabledFlag: jest.fn(() => mockMoneyHomeScreenEnabled), +})); // Mock the Perps feature flag selector - will be controlled per test let mockPerpsEnabled = false; @@ -236,6 +243,8 @@ describe('ActivityView', () => { const mockUseCurrentNetworkInfo = useCurrentNetworkInfo as jest.MockedFunction; + let backHandlerSpy: jest.SpyInstance; + const defaultNetworkInfo = { enabledNetworks: [ { chainId: '0x1', enabled: true }, @@ -266,8 +275,14 @@ describe('ActivityView', () => { beforeEach(() => { jest.clearAllMocks(); + backHandlerSpy = jest + .spyOn(BackHandler, 'addEventListener') + .mockReturnValue({ remove: jest.fn() } as unknown as ReturnType< + typeof BackHandler.addEventListener + >); mockUseCurrentNetworkInfo.mockReturnValue(defaultNetworkInfo); mockIsEvmSelected = true; + mockMoneyHomeScreenEnabled = false; mockPerpsEnabled = false; mockPredictEnabled = false; mockAreAllEvmPopularNetworksEnabled = false; @@ -275,6 +290,11 @@ describe('ActivityView', () => { mockRoute.params = {}; }); + afterEach(() => { + cleanup(); + backHandlerSpy.mockRestore(); + }); + describe('Network Manager Integration', () => { beforeEach(() => { jest.clearAllMocks(); @@ -403,6 +423,80 @@ describe('ActivityView', () => { expect(mockNavigation.goBack).not.toHaveBeenCalled(); }); + + it('displays back button when Money home screen flag is enabled without showBackButton param', () => { + mockMoneyHomeScreenEnabled = true; + mockRoute.params = {}; + + const { getByTestId } = renderComponent(mockInitialState); + + expect(getByTestId('activity-view-back-button')).toBeOnTheScreen(); + }); + + it('calls navigation.navigate with HOME_TABS on back button press when Money flag is enabled', () => { + mockMoneyHomeScreenEnabled = true; + mockRoute.params = {}; + const { getByTestId } = renderComponent(mockInitialState); + + fireEvent.press(getByTestId('activity-view-back-button')); + + expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.HOME_TABS); + expect(mockNavigation.goBack).not.toHaveBeenCalled(); + }); + + it('calls navigation.navigate with HOME_TABS and not goBack when both flag and showBackButton param are true', () => { + mockMoneyHomeScreenEnabled = true; + mockRoute.params = { showBackButton: true }; + const { getByTestId } = renderComponent(mockInitialState); + + fireEvent.press(getByTestId('activity-view-back-button')); + + expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.HOME_TABS); + expect(mockNavigation.goBack).not.toHaveBeenCalled(); + }); + + it('registers hardwareBackPress handler when Money flag is enabled', () => { + mockMoneyHomeScreenEnabled = true; + mockRoute.params = {}; + + renderComponent(mockInitialState); + + expect(BackHandler.addEventListener).toHaveBeenCalledWith( + 'hardwareBackPress', + expect.any(Function), + ); + }); + + it('navigates to HOME_TABS when hardwareBackPress fires with Money flag enabled', () => { + mockMoneyHomeScreenEnabled = true; + mockRoute.params = {}; + renderComponent(mockInitialState); + const [[, handler]] = (BackHandler.addEventListener as jest.Mock).mock + .calls; + + const result = handler(); + + expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.HOME_TABS); + expect(result).toBe(true); + }); + + it('does not navigate to HOME_TABS on hardwareBackPress when Money flag is disabled', () => { + mockMoneyHomeScreenEnabled = false; + mockRoute.params = {}; + + renderComponent(mockInitialState); + + const hardwareBackPressCalls = ( + BackHandler.addEventListener as jest.Mock + ).mock.calls.filter(([event]: [string]) => event === 'hardwareBackPress'); + hardwareBackPressCalls.forEach(([, handler]: [string, () => boolean]) => + handler(), + ); + + expect(mockNavigation.navigate).not.toHaveBeenCalledWith( + Routes.HOME_TABS, + ); + }); }); describe('header and SafeAreaView', () => { @@ -463,6 +557,18 @@ describe('ActivityView', () => { queryByTestId(ActivitiesViewSelectorsIDs.HEADER_COMPACT_STANDARD), ).toBeNull(); }); + + it('renders HeaderCompactStandard when Money home screen flag is enabled', () => { + mockMoneyHomeScreenEnabled = true; + mockRoute.params = {}; + + const { getByTestId, queryByTestId } = renderComponent(mockInitialState); + + expect( + getByTestId(ActivitiesViewSelectorsIDs.HEADER_COMPACT_STANDARD), + ).toBeOnTheScreen(); + expect(queryByTestId(ActivitiesViewSelectorsIDs.HEADER_ROOT)).toBeNull(); + }); }); describe('Perps tab', () => { diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index bcb28723cef6..d977b85e1bcf 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -1061,10 +1061,7 @@ const Wallet = ({ MetaMetricsEvents.ACTIVITY_CLICKED, ).build(), ); - navigation.navigate(Routes.TRANSACTIONS_VIEW, { - screen: Routes.TRANSACTIONS_VIEW, - params: { showBackButton: true }, - }); + navigation.navigate(Routes.TRANSACTIONS_VIEW); }, [navigation, trackEvent]); const getTokenAddedAnalyticsParams = useCallback( diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index c3851369f74e..0593e003de7a 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -1,4 +1,5 @@ const Routes = { + HOME_TABS: 'Home', WALLET_VIEW: 'WalletView', BROWSER_TAB_HOME: 'BrowserTabHome', BROWSER_VIEW: 'BrowserView',