diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js
index ba0b2c93ca5c..fb117d7790e7 100644
--- a/app/components/Nav/Main/MainNavigator.js
+++ b/app/components/Nav/Main/MainNavigator.js
@@ -26,6 +26,7 @@ import Asset from '../../Views/Asset';
import AssetDetails from '../../Views/AssetDetails';
import AddAsset from '../../Views/AddAsset';
import Collectible from '../../Views/Collectible';
+import NftFullView from '../../Views/NftFullView';
import TokensFullView from '../../Views/TokensFullView';
import SendLegacy from '../../Views/confirmations/legacy/Send';
import SendTo from '../../Views/confirmations/legacy/SendFlow/SendTo';
@@ -998,6 +999,11 @@ const MainNavigator = () => {
name="NftDetailsFullImage"
component={NftDetailsFullImageModeView}
/>
+
{() => }
diff --git a/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap b/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap
index a85374932888..516947632173 100644
--- a/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap
+++ b/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap
@@ -110,6 +110,15 @@ exports[`MainNavigator matches rendered snapshot 1`] = `
component={[Function]}
name="NftDetailsFullImage"
/>
+
;
@@ -17,6 +22,7 @@ const mockUseSelector = useSelector as jest.MockedFunction;
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({
navigate: mockNavigate,
+ push: mockPush,
}),
}));
@@ -135,6 +141,7 @@ jest.mock('../../../../locales/i18n', () => ({
const strings: Record = {
'wallet.no_collectibles': 'No NFTs yet',
'wallet.add_collectibles': 'Import NFTs',
+ 'wallet.view_all_nfts': 'View all NFTs',
};
return strings[key] || key;
},
@@ -153,6 +160,33 @@ jest.mock('../CollectibleMedia', () => () => null);
jest.mock('@metamask/design-system-react-native', () => ({
Text: ({ children }: { children: React.ReactNode }) => children,
TextVariant: { BodyMd: 'BodyMd', BodySm: 'BodySm' },
+ Box: ({
+ children,
+ testID,
+ }: {
+ children: React.ReactNode;
+ testID?: string;
+ }) => {
+ const { View } = jest.requireActual('react-native');
+ return {children};
+ },
+ Button: ({
+ children,
+ onPress,
+ testID,
+ }: {
+ children: React.ReactNode;
+ onPress: () => void;
+ testID?: string;
+ }) => {
+ const { TouchableOpacity, Text } = jest.requireActual('react-native');
+ return (
+
+ {children}
+
+ );
+ },
+ ButtonVariant: { Secondary: 'Secondary' },
}));
// Mock ButtonIcon and its enums
@@ -224,6 +258,11 @@ jest.mock('../../../util/trace', () => ({
TraceName: { LoadCollectibles: 'LoadCollectibles' },
}));
+// Mock useTailwind
+jest.mock('@metamask/design-system-twrnc-preset', () => ({
+ useTailwind: () => (className: string) => ({ className }),
+}));
+
describe('NftGrid', () => {
const mockNft: Nft = {
address: '0x123',
@@ -268,8 +307,100 @@ describe('NftGrid', () => {
});
await waitFor(() => {
- expect(getByTestId('collectible-Test NFT-456')).toBeDefined();
- expect(getByTestId('nft-grid-header')).toBeDefined();
+ expect(getByTestId('collectible-Test NFT-456')).toBeOnTheScreen();
+ expect(getByTestId('nft-grid-header')).toBeOnTheScreen();
+ });
+ });
+
+ it('renders control bar with add button', async () => {
+ const mockCollectibles = { '0x1': [mockNft] };
+ mockUseSelector
+ .mockReturnValueOnce(false) // isNftFetchingProgress
+ .mockReturnValueOnce(mockCollectibles); // multichainCollectiblesByEnabledNetworksSelector
+ const store = mockStore(initialState);
+
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+
+ await waitFor(() => {
+ expect(getByTestId('base-control-bar')).toBeOnTheScreen();
+ expect(getByTestId('import-token-button')).toBeOnTheScreen();
+ });
+ });
+
+ it('applies full view styling when isFullView is true', async () => {
+ const mockCollectibles = { '0x1': [mockNft] };
+ mockUseSelector
+ .mockReturnValueOnce(false) // isNftFetchingProgress
+ .mockReturnValueOnce(mockCollectibles); // multichainCollectiblesByEnabledNetworksSelector
+ const store = mockStore(initialState);
+
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+
+ await waitFor(() => {
+ expect(getByTestId('base-control-bar')).toBeOnTheScreen();
+ expect(getByTestId('import-token-button')).toBeOnTheScreen();
+ });
+ });
+
+ it('shows view all button when maxItems is exceeded', async () => {
+ const mockCollectibles = {
+ '0x1': [mockNft, { ...mockNft, tokenId: '789' }],
+ };
+ mockUseSelector
+ .mockReturnValueOnce(false) // isNftFetchingProgress
+ .mockReturnValueOnce(mockCollectibles); // multichainCollectiblesByEnabledNetworksSelector
+ const store = mockStore(initialState);
+
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+
+ await waitFor(() => {
+ expect(getByTestId('view-all-nfts-button')).toBeOnTheScreen();
+ });
+ });
+
+ it('hides view all button when maxItems is not exceeded', async () => {
+ const mockCollectibles = { '0x1': [mockNft] };
+ mockUseSelector
+ .mockReturnValueOnce(false) // isNftFetchingProgress
+ .mockReturnValueOnce(mockCollectibles); // multichainCollectiblesByEnabledNetworksSelector
+ const store = mockStore(initialState);
+
+ const { queryByTestId } = render(
+
+
+ ,
+ );
+
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+
+ await waitFor(() => {
+ expect(queryByTestId('view-all-nfts-button')).toBeNull();
});
});
@@ -296,19 +427,21 @@ describe('NftGrid', () => {
});
await waitFor(() => {
- expect(getByTestId('collectible-Test NFT-456')).toBeDefined();
+ expect(getByTestId('collectible-Test NFT-456')).toBeOnTheScreen();
expect(queryByTestId('collectible-Test NFT-789')).toBeNull();
});
});
- it('calls navigation when add collectible is triggered from empty state', async () => {
- let callCount = 0;
- mockUseSelector.mockImplementation(() => {
- callCount++;
- if (callCount % 2 === 1) {
- return false; // isNftFetchingProgress
+ it('navigates to AddAsset when add collectible button is pressed', async () => {
+ const mockCollectibles = { '0x1': [mockNft] };
+ mockUseSelector.mockImplementation((selector) => {
+ if (selector === isNftFetchingProgressSelector) {
+ return false;
+ }
+ if (selector === multichainCollectiblesByEnabledNetworksSelector) {
+ return mockCollectibles;
}
- return {}; // multichainCollectiblesByEnabledNetworksSelector
+ return {};
});
const store = mockStore(initialState);
@@ -322,15 +455,10 @@ describe('NftGrid', () => {
jest.advanceTimersByTime(100);
});
- await waitFor(() => {
- const emptyState = getByTestId('import-collectible-button');
- expect(emptyState).toBeDefined();
- });
-
- const emptyState = getByTestId('import-collectible-button');
- fireEvent.press(emptyState);
+ const addButton = getByTestId('import-token-button');
+ fireEvent.press(addButton);
- expect(mockNavigate).toHaveBeenCalledWith('AddAsset', {
+ expect(mockPush).toHaveBeenCalledWith('AddAsset', {
assetType: 'collectible',
});
expect(mockTrackEvent).toHaveBeenCalled();
@@ -382,7 +510,7 @@ describe('NftGrid', () => {
});
await waitFor(() => {
- expect(getByTestId('collectible-null-456')).toBeDefined();
+ expect(getByTestId('collectible-null-456')).toBeOnTheScreen();
});
});
@@ -404,7 +532,7 @@ describe('NftGrid', () => {
});
await waitFor(() => {
- expect(getByTestId('collectible-contracts-spinner')).toBeDefined();
+ expect(getByTestId('collectible-contracts-spinner')).toBeOnTheScreen();
});
});
@@ -447,7 +575,7 @@ describe('NftGrid', () => {
});
await waitFor(() => {
- expect(getByTestId('collectibles-empty-state')).toBeDefined();
+ expect(getByTestId('collectibles-empty-state')).toBeOnTheScreen();
});
});
@@ -472,47 +600,18 @@ describe('NftGrid', () => {
});
});
- it('disables add NFT button when isAddNFTEnabled is false', async () => {
- // Given a user with no collectibles
- mockUseSelector
- .mockReturnValueOnce(false) // isNftFetchingProgress
- .mockReturnValueOnce({}); // multichainCollectiblesByEnabledNetworksSelector
- const store = mockStore(initialState);
-
- // When the component renders
- const { getByTestId } = render(
-
-
- ,
- );
-
- act(() => {
- jest.advanceTimersByTime(100);
- });
-
- // When the add button is pressed
- await waitFor(() => {
- const addButton = getByTestId('import-token-button');
- fireEvent.press(addButton);
- });
-
- // Then it should be temporarily disabled during navigation
- const addButton = getByTestId('import-token-button');
- expect(addButton.props.disabled).toBe(false);
- });
-
- it('calls navigation when add collectible button in control bar is pressed', async () => {
- // Given a user with collectibles
- const mockCollectibles = { '0x1': [mockNft] };
+ it('navigates to full view when view all button is pressed', async () => {
+ const mockCollectibles = {
+ '0x1': [mockNft, { ...mockNft, tokenId: '789' }],
+ };
mockUseSelector
.mockReturnValueOnce(false) // isNftFetchingProgress
.mockReturnValueOnce(mockCollectibles); // multichainCollectiblesByEnabledNetworksSelector
const store = mockStore(initialState);
- // When the component renders
const { getByTestId } = render(
-
+
,
);
@@ -520,16 +619,11 @@ describe('NftGrid', () => {
jest.advanceTimersByTime(100);
});
- // When the add button in control bar is pressed
await waitFor(() => {
- const addButton = getByTestId('import-token-button');
- fireEvent.press(addButton);
+ const viewAllButton = getByTestId('view-all-nfts-button');
+ fireEvent.press(viewAllButton);
});
- // Then it should navigate to AddAsset screen
- expect(mockNavigate).toHaveBeenCalledWith('AddAsset', {
- assetType: 'collectible',
- });
- expect(mockTrackEvent).toHaveBeenCalled();
+ expect(mockNavigate).toHaveBeenCalledWith('NftFullView');
});
});
diff --git a/app/components/UI/NftGrid/NftGrid.tsx b/app/components/UI/NftGrid/NftGrid.tsx
index 8b3f0604d65c..7c52a9ec5ed0 100644
--- a/app/components/UI/NftGrid/NftGrid.tsx
+++ b/app/components/UI/NftGrid/NftGrid.tsx
@@ -5,7 +5,7 @@ import React, {
useEffect,
useCallback,
} from 'react';
-import { FlashList } from '@shopify/flash-list';
+import { FlashList, FlashListProps } from '@shopify/flash-list';
import { useSelector } from 'react-redux';
import { RefreshTestId, SpinnerTestId } from './constants';
import { endTrace, trace, TraceName } from '../../../util/trace';
@@ -20,31 +20,73 @@ import ActionSheet from '@metamask/react-native-actionsheet';
import NftGridItemActionSheet from './NftGridItemActionSheet';
import NftGridHeader from './NftGridHeader';
import { useNavigation } from '@react-navigation/native';
+import { StackNavigationProp } from '@react-navigation/stack';
import { MetaMetricsEvents, useMetrics } from '../../hooks/useMetrics';
import { CollectiblesEmptyState } from '../CollectiblesEmptyState';
import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors';
-import { ActivityIndicator, StyleSheet, View } from 'react-native';
+import { ActivityIndicator } from 'react-native';
+import {
+ Box,
+ Button,
+ ButtonVariant,
+} from '@metamask/design-system-react-native';
+import Routes from '../../../constants/navigation/Routes';
+import { strings } from '../../../../locales/i18n';
import BaseControlBar from '../shared/BaseControlBar';
import ButtonIcon, {
ButtonIconSizes,
} from '../../../component-library/components/Buttons/ButtonIcon';
import { IconName } from '../../../component-library/components/Icons/Icon';
-import { useStyles } from '../../hooks/useStyles';
-import createControlBarStyles from '../shared/ControlBarStyles';
+import { useTailwind } from '@metamask/design-system-twrnc-preset';
+
+interface NFTNavigationParamList {
+ AddAsset: { assetType: string };
+ [key: string]: undefined | object;
+}
-const style = StyleSheet.create({
- container: {
- flex: 1,
- },
-});
+interface NftGridProps {
+ flashListProps?: Partial>;
+ maxItems?: number;
+ isFullView?: boolean;
+}
-const NftGrid = () => {
- const navigation = useNavigation();
+const NftRow = ({
+ items,
+ onLongPress,
+}: {
+ items: Nft[];
+ onLongPress: (nft: Nft) => void;
+}) => (
+
+ {items.map((item, index) => {
+ // Create a truly unique key combining multiple identifiers
+ const uniqueKey = `${item.address}-${item.tokenId}-${item.chainId}-${index}`;
+ return (
+
+
+
+ );
+ })}
+ {/* Fill remaining slots if less than 3 items */}
+ {items.length < 3 &&
+ Array.from({ length: 3 - items.length }).map((_, index) => (
+
+ ))}
+
+);
+
+const NftGrid = ({
+ flashListProps,
+ maxItems,
+ isFullView = false,
+}: NftGridProps) => {
+ const navigation =
+ useNavigation>();
const { trackEvent, createEventBuilder } = useMetrics();
const [isAddNFTEnabled, setIsAddNFTEnabled] = useState(true);
const [longPressedCollectible, setLongPressedCollectible] =
useState(null);
- const { styles } = useStyles(createControlBarStyles, undefined);
+ const tw = useTailwind();
const isNftFetchingProgress = useSelector(isNftFetchingProgressSelector);
@@ -64,6 +106,18 @@ const NftGrid = () => {
return owned;
}, [collectiblesByEnabledNetworks]);
+ const groupedCollectibles: Nft[][] = useMemo(() => {
+ const groups: Nft[][] = [];
+ const itemsToProcess = maxItems
+ ? allFilteredCollectibles.slice(0, maxItems)
+ : allFilteredCollectibles;
+
+ for (let i = 0; i < itemsToProcess.length; i += 3) {
+ groups.push(itemsToProcess.slice(i, i + 3));
+ }
+ return groups;
+ }, [allFilteredCollectibles, maxItems]);
+
useEffect(() => {
if (longPressedCollectible) {
actionSheetRef.current.show();
@@ -72,7 +126,7 @@ const NftGrid = () => {
const goToAddCollectible = useCallback(() => {
setIsAddNFTEnabled(false);
- navigation.navigate('AddAsset', { assetType: 'collectible' });
+ navigation.push('AddAsset', { assetType: 'collectible' });
trackEvent(
createEventBuilder(MetaMetricsEvents.WALLET_ADD_COLLECTIBLES).build(),
);
@@ -85,28 +139,51 @@ const NftGrid = () => {
size={ButtonIconSizes.Lg}
onPress={goToAddCollectible}
iconName={IconName.Add}
- disabled={!isAddNFTEnabled}
- isDisabled={!isAddNFTEnabled}
- style={styles.controlIconButton}
/>
);
+ const handleViewAllNfts = useCallback(() => {
+ navigation.navigate(Routes.WALLET.NFTS_FULL_VIEW);
+ }, [navigation]);
+
+ // Determine if we should show the "View all NFTs" button
+ const shouldShowViewAllButton =
+ maxItems && allFilteredCollectibles.length > maxItems;
+
+ // Default flashListProps for full view
+ const defaultFullViewProps = useMemo(
+ () => ({
+ contentContainerStyle: tw`px-4`,
+ scrollEnabled: true,
+ }),
+ [tw],
+ );
+
+ // Merge default props with passed props
+ const mergedFlashListProps = useMemo(() => {
+ if (isFullView) {
+ return { ...defaultFullViewProps, ...flashListProps };
+ }
+ return flashListProps;
+ }, [isFullView, defaultFullViewProps, flashListProps]);
+
return (
-
+ <>
}
- data={allFilteredCollectibles}
+ data={groupedCollectibles}
renderItem={({ item }) => (
-
+
)}
- keyExtractor={(item, index) => `nft-${item.address}-${index}`}
+ keyExtractor={(_, index) => `nft-row-${index}`}
testID={RefreshTestId}
decelerationRate="fast"
refreshControl={}
@@ -130,14 +207,28 @@ const NftGrid = () => {
)}
>
}
- numColumns={3}
+ {...mergedFlashListProps}
/>
-
+
+ {/* View all NFTs button - shown when there are more items than maxItems */}
+ {shouldShowViewAllButton && (
+
+
+
+ )}
+ >
);
};
diff --git a/app/components/UI/NftGrid/NftGridItem.tsx b/app/components/UI/NftGrid/NftGridItem.tsx
index 622da2137dd1..6b7fa4bdc2b3 100644
--- a/app/components/UI/NftGrid/NftGridItem.tsx
+++ b/app/components/UI/NftGrid/NftGridItem.tsx
@@ -9,7 +9,6 @@ import CollectibleMedia from '../CollectibleMedia';
const styles = StyleSheet.create({
container: {
flex: 1,
- padding: 5,
},
collectible: {
aspectRatio: 1,
diff --git a/app/components/UI/shared/ControlBarStyles.ts b/app/components/UI/shared/ControlBarStyles.ts
index 964ff8e3bfd0..d43dfe2b1347 100644
--- a/app/components/UI/shared/ControlBarStyles.ts
+++ b/app/components/UI/shared/ControlBarStyles.ts
@@ -28,6 +28,7 @@ const createControlBarStyles = (params: { theme: Theme }) => {
controlButtonInnerWrapper: {
flexDirection: 'row',
gap: 12,
+ alignItems: 'center',
},
controlButton: {
backgroundColor: colors.background.default,
diff --git a/app/components/Views/NftFullView/NftFullView.test.tsx b/app/components/Views/NftFullView/NftFullView.test.tsx
new file mode 100644
index 000000000000..89d42929069c
--- /dev/null
+++ b/app/components/Views/NftFullView/NftFullView.test.tsx
@@ -0,0 +1,161 @@
+import { renderScreen } from '../../../util/test/renderWithProvider';
+import NftFullView from './NftFullView';
+import { useNavigation } from '@react-navigation/native';
+
+// Mock external dependencies that are not under test
+jest.mock('@metamask/design-system-twrnc-preset', () => ({
+ useTailwind: () => (className: string) => ({ className }),
+}));
+
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: jest.fn(),
+}));
+
+// Mock child components to avoid complex Redux state setup
+jest.mock('../../UI/NftGrid/NftGrid', () => {
+ const React = jest.requireActual('react');
+ const { View, Text } = jest.requireActual('react-native');
+
+ return function MockNftGrid({ isFullView }: { isFullView?: boolean }) {
+ return React.createElement(
+ View,
+ { testID: 'nft-grid' },
+ React.createElement(
+ Text,
+ null,
+ `NftGrid ${isFullView ? 'Full View' : 'Tab View'}`,
+ ),
+ );
+ };
+});
+
+jest.mock(
+ '../../../component-library/components/BottomSheets/BottomSheetHeader',
+ () => {
+ const React = jest.requireActual('react');
+ const { View, TouchableOpacity, Text } = jest.requireActual('react-native');
+
+ return function MockBottomSheetHeader({
+ onBack,
+ children,
+ }: {
+ onBack: () => void;
+ children: string;
+ }) {
+ return React.createElement(
+ View,
+ { testID: 'bottom-sheet-header' },
+ React.createElement(
+ TouchableOpacity,
+ { testID: 'back-button', onPress: onBack },
+ React.createElement(Text, null, 'Back'),
+ ),
+ React.createElement(Text, { testID: 'header-title' }, children),
+ );
+ };
+ },
+);
+
+// Mock Box component
+jest.mock('@metamask/design-system-react-native', () => ({
+ Box: ({
+ children,
+ testID,
+ }: {
+ children: React.ReactNode;
+ testID?: string;
+ }) => {
+ const React = jest.requireActual('react');
+ const { View } = jest.requireActual('react-native');
+ return React.createElement(View, { testID }, children);
+ },
+}));
+
+// Type the mocked functions
+const mockUseNavigation = useNavigation as jest.MockedFunction<
+ typeof useNavigation
+>;
+
+describe('NftFullView', () => {
+ const mockGoBack = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ // Setup default mocks
+ mockUseNavigation.mockReturnValue({
+ goBack: mockGoBack,
+ } as unknown as ReturnType);
+ });
+
+ it('renders header with title and back button', () => {
+ // Arrange
+ const { getByTestId } = renderScreen(NftFullView, {
+ name: 'NftFullView',
+ });
+
+ // Act
+ const header = getByTestId('bottom-sheet-header');
+ const backButton = getByTestId('back-button');
+ const headerTitle = getByTestId('header-title');
+
+ // Assert
+ expect(header).toBeOnTheScreen();
+ expect(backButton).toBeOnTheScreen();
+ expect(headerTitle).toBeOnTheScreen();
+ });
+
+ it('renders NFT grid with isFullView prop', () => {
+ // Arrange
+ const { getByTestId } = renderScreen(NftFullView, {
+ name: 'NftFullView',
+ });
+
+ // Act
+ const nftGrid = getByTestId('nft-grid');
+
+ // Assert
+ expect(nftGrid).toBeOnTheScreen();
+ });
+
+ it('calls goBack when back button is pressed', () => {
+ // Arrange
+ const { getByTestId } = renderScreen(NftFullView, {
+ name: 'NftFullView',
+ });
+
+ // Act
+ const backButton = getByTestId('back-button');
+ backButton.props.onPress();
+
+ // Assert
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+
+ it('displays correct header title', () => {
+ // Arrange
+ const { getByTestId } = renderScreen(NftFullView, {
+ name: 'NftFullView',
+ });
+
+ // Act
+ const headerTitle = getByTestId('header-title');
+
+ // Assert
+ expect(headerTitle).toBeOnTheScreen();
+ });
+
+ it('renders with safe area view', () => {
+ // Arrange
+ const { getByTestId } = renderScreen(NftFullView, {
+ name: 'NftFullView',
+ });
+
+ // Act
+ const header = getByTestId('bottom-sheet-header');
+
+ // Assert
+ expect(header).toBeOnTheScreen();
+ });
+});
diff --git a/app/components/Views/NftFullView/NftFullView.tsx b/app/components/Views/NftFullView/NftFullView.tsx
new file mode 100644
index 000000000000..cf8bd72355fe
--- /dev/null
+++ b/app/components/Views/NftFullView/NftFullView.tsx
@@ -0,0 +1,37 @@
+import React, { useCallback } from 'react';
+import { useNavigation } from '@react-navigation/native';
+import { StackNavigationProp } from '@react-navigation/stack';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useTailwind } from '@metamask/design-system-twrnc-preset';
+import BottomSheetHeader from '../../../component-library/components/BottomSheets/BottomSheetHeader';
+import { strings } from '../../../../locales/i18n';
+import { Box } from '@metamask/design-system-react-native';
+import NftGrid from '../../UI/NftGrid/NftGrid';
+
+interface NFTNavigationParamList {
+ AddAsset: { assetType: string };
+ [key: string]: undefined | object;
+}
+
+const NftFullView = () => {
+ const navigation =
+ useNavigation>();
+ const tw = useTailwind();
+
+ const handleBackPress = useCallback(() => {
+ navigation.goBack();
+ }, [navigation]);
+
+ return (
+
+
+ {strings('wallet.collectibles')}
+
+
+
+
+
+ );
+};
+
+export default NftFullView;
diff --git a/app/components/Views/NftFullView/index.ts b/app/components/Views/NftFullView/index.ts
new file mode 100644
index 000000000000..7f5091ba3007
--- /dev/null
+++ b/app/components/Views/NftFullView/index.ts
@@ -0,0 +1 @@
+export { default } from './NftFullView';
diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx
index 9937a57f58b4..82bbfe5920d0 100644
--- a/app/components/Views/Wallet/index.tsx
+++ b/app/components/Views/Wallet/index.tsx
@@ -184,7 +184,7 @@ import { EVM_SCOPE } from '../../UI/Earn/constants/networks';
import { useCurrentNetworkInfo } from '../../hooks/useCurrentNetworkInfo';
import { createAddressListNavigationDetails } from '../../Views/MultichainAccounts/AddressList';
import { useRewardsIntroModal } from '../../UI/Rewards/hooks/useRewardsIntroModal';
-import NftGrid from '../../UI/NftGrid';
+import NftGrid from '../../UI/NftGrid/NftGrid';
import { AssetPollingProvider } from '../../hooks/AssetPolling/AssetPollingProvider';
import { selectDisplayCardButton } from '../../../core/redux/slices/card';
diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts
index 784cd6716b73..9026fb8c0f05 100644
--- a/app/constants/navigation/Routes.ts
+++ b/app/constants/navigation/Routes.ts
@@ -214,6 +214,7 @@ const Routes = {
HOME: 'WalletTabHome',
TAB_STACK_FLOW: 'WalletTabStackFlow',
WALLET_CONNECT_SESSIONS_VIEW: 'WalletConnectSessionsView',
+ NFTS_FULL_VIEW: 'NftFullView',
TOKENS_FULL_VIEW: 'TokensFullView',
},
VAULT_RECOVERY: {
diff --git a/locales/languages/en.json b/locales/languages/en.json
index e32f751e6ca9..1bddeeddf3b4 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -1844,6 +1844,7 @@
"wallet": {
"title": "Wallet",
"tokens": "Tokens",
+ "view_all_nfts": "View all NFTs",
"view_all_tokens": "View all tokens",
"collectible": "Collectible",
"collectibles": "NFTs",