diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx index 26f131b8638..cae66bb27ae 100644 --- a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -3,7 +3,7 @@ import Animated from 'react-native-reanimated'; import { Box, Stack, Text, useColorMode } from '@/design-system'; import * as i18n from '@/languages'; import { TokenFamilyHeaderHeight } from './NFTLoadingSkeleton'; -import { MINTS, useExperimentalFlag } from '@/config'; +import { MINTS, NFTS_ENABLED, useExperimentalFlag } from '@/config'; import { useRemoteConfig } from '@/model/remoteConfig'; import { IS_TEST } from '@/env'; import { useMints } from '@/resources/mints'; @@ -44,7 +44,8 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp as={Animated.View} borderRadius={15} justifyContent="center" - paddingHorizontal="10px" + paddingVertical="12px" + paddingHorizontal="20px" style={[{ backgroundColor: isDarkMode ? SEPARATOR_COLOR : LIGHT_SEPARATOR_COLOR }]} > @@ -58,15 +59,18 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp }; export function NFTEmptyState() { - const { mints_enabled } = useRemoteConfig(); + const { mints_enabled, nfts_enabled } = useRemoteConfig(); const { accountAddress } = useAccountSettings(); const { data: { featuredMint }, } = useMints({ walletAddress: accountAddress }); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; + if (!nftsEnabled) return null; + return ( { }; const NFTLoadingSkeleton = ({ items = 5 }) => { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; + + if (!nftsEnabled) return null; + return ( {[...Array(items)].map((_, index) => ( diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index bb233041e82..de588f3e021 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -4,6 +4,9 @@ import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; import useNftSort from '@/hooks/useNFTsSortBy'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; const TokenFamilyHeaderHeight = 48; @@ -30,7 +33,12 @@ const getMenuItemIcon = (value: NftCollectionSortCriterion) => { }; const CollectiblesHeader = () => { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; const { nftSort, updateNFTSort } = useNftSort(); + + if (!nftsEnabled) return null; + return ( diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx index 17d7f6dd44e..9781c473237 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx @@ -2,6 +2,9 @@ import React from 'react'; import { TokenFamilyHeader } from '../../token-family'; import { useLatestCallback, useOpenFamilies } from '@/hooks'; import { ThemeContextProps } from '@/theme'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; type Props = { name: string; @@ -12,6 +15,9 @@ type Props = { }; export default React.memo(function WrappedTokenFamilyHeader({ name, total, image, theme, testID }: Props) { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; + const { openFamilies, updateOpenFamilies } = useOpenFamilies(); const isFamilyOpen = openFamilies[name]; @@ -21,6 +27,8 @@ export default React.memo(function WrappedTokenFamilyHeader({ name, total, image }) ); + if (!nftsEnabled) return null; + return ( { return r1.uid !== r2.uid; @@ -54,6 +57,8 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ scrollIndicatorInsets?: object; type?: AssetListType; }) { + const remoteConfig = useRemoteConfig(); + const experimentalConfig = useContext(RainbowContext).config; const currentDataProvider = useMemoOne(() => dataProvider.cloneWithRows(briefSectionsData), [briefSectionsData]); const { isCoinListEdited, setIsCoinListEdited } = useCoinListEdited(); const y = useRecyclerAssetListPosition()!; @@ -65,8 +70,16 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ const cardIds = useMemo(() => getCardIdsForScreen(name as keyof typeof Routes), [getCardIdsForScreen, name]); const layoutProvider = useMemo( - () => getLayoutProvider(briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet), - [briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet] + () => + getLayoutProvider({ + briefSectionsData, + isCoinListEdited, + cardIds, + isReadOnlyWallet, + remoteConfig, + experimentalConfig, + }), + [briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet, experimentalConfig] ); const { accountAddress } = useAccountSettings(); diff --git a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx index 13619bef984..ffe67eafd07 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx @@ -2,6 +2,11 @@ import { Dimension, Layout, LayoutManager, LayoutProvider } from 'recyclerlistvi import ViewDimensions from './ViewDimensions'; import { BaseCellType, CellType } from './ViewTypes'; import { deviceUtils } from '@/utils'; +import { getRemoteConfig, RainbowConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, REMOTE_CARDS, useExperimentalFlag } from '@/config'; +import { useContext } from 'react'; +import { RainbowContextType } from '@/helpers/RainbowContext'; +import { IS_TEST } from '@/env'; const getStyleOverridesForIndex = (indices: number[]) => (index: number) => { if (indices.includes(index)) { @@ -30,7 +35,24 @@ class BetterLayoutProvider extends LayoutProvider { } } -const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: boolean, cardIds: string[], isReadOnlyWallet: boolean) => { +const getLayoutProvider = ({ + briefSectionsData, + isCoinListEdited, + cardIds, + isReadOnlyWallet, + experimentalConfig, + remoteConfig, +}: { + briefSectionsData: BaseCellType[]; + isCoinListEdited: boolean; + cardIds: string[]; + isReadOnlyWallet: boolean; + experimentalConfig: ReturnType>['config']; + remoteConfig: RainbowConfig; +}) => { + const remoteCardsEnabled = (remoteConfig.remote_cards_enabled || experimentalConfig[REMOTE_CARDS]) && !IS_TEST; + const nftsEnabled = (remoteConfig.nfts_enabled || experimentalConfig[NFTS_ENABLED]) && !IS_TEST; + const indicesToOverride = []; for (let i = 0; i < briefSectionsData.length; i++) { const val = briefSectionsData[i]; @@ -55,7 +77,24 @@ const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: dim.height = ViewDimensions[type].height; dim.width = ViewDimensions[type].width || dim.width; - if ((type === CellType.REMOTE_CARD_CAROUSEL && !cardIds.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) { + // NOTE: If NFTs are disabled, we don't want to render the NFTs section, so adjust the height to 0 + if ( + [ + CellType.NFTS_EMPTY, + CellType.NFTS_HEADER_SPACE_AFTER, + CellType.NFTS_HEADER_SPACE_BEFORE, + CellType.NFTS_HEADER, + CellType.NFTS_LOADING, + CellType.NFT, + CellType.FAMILY_HEADER, + ].includes(type) && + !nftsEnabled + ) { + dim.height = 0; + } + + // NOTE: If remote cards are disabled, we don't want to render the remote cards section, so adjust the height to 0 + if (type === CellType.REMOTE_CARD_CAROUSEL && (!remoteCardsEnabled || !cardIds.length || isReadOnlyWallet)) { dim.height = 0; } } diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx index 0e6e1fb85a0..19d0760d3c1 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx @@ -35,7 +35,7 @@ export function ProfileAvatarRow({ size = ProfileAvatarSize }: { size?: number } const ContextMenuButton = onAvatarPressProfile ? React.Fragment : ContextMenu; const handlePressMenuItem = useLatestCallback((e: any) => { - const index = avatarContextMenuConfig.menuItems?.findIndex(item => item.actionKey === e.nativeEvent.actionKey); + const index = avatarContextMenuConfig?.menuItems?.findIndex(item => item && item.actionKey === e.nativeEvent.actionKey); onSelectionCallback(index); }); diff --git a/src/config/experimental.ts b/src/config/experimental.ts index b380ec0b0ac..ba81ea0fc60 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -29,6 +29,7 @@ export const DAPP_BROWSER = 'Dapp Browser'; export const ETH_REWARDS = 'ETH Rewards'; export const DEGEN_MODE = 'Degen Mode'; export const FEATURED_RESULTS = 'Featured Results'; +export const NFTS_ENABLED = 'Nfts Enabled'; /** * A developer setting that pushes log lines to an array in-memory so that @@ -66,6 +67,7 @@ export const defaultConfig: Record = { [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, [FEATURED_RESULTS]: { settings: true, value: false }, + [NFTS_ENABLED]: { settings: true, value: false }, }; const storageKey = 'config'; diff --git a/src/config/experimentalHooks.ts b/src/config/experimentalHooks.ts index 4fd975e45b3..f99fa930c37 100644 --- a/src/config/experimentalHooks.ts +++ b/src/config/experimentalHooks.ts @@ -6,7 +6,6 @@ import isTestFlight from '@/helpers/isTestFlight'; const useExperimentalFlag = (name: any) => { if (IS_DEV || isTestFlight) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'config' does not exist on type '{}'. // eslint-disable-next-line react-hooks/rules-of-hooks return useContext(RainbowContext).config[name]; } else { diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index c0734b50313..27260d4c004 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -4,7 +4,7 @@ import { useSharedValue } from 'react-native-reanimated'; import DevButton from '../components/dev-buttons/DevButton'; import Emoji from '../components/text/Emoji'; import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug'; -import { defaultConfig } from '../config/experimental'; +import { defaultConfig } from '@/config/experimental'; import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; @@ -16,7 +16,18 @@ import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; -export const RainbowContext = createContext({}); +export type RainbowContextType = { + config: Record; + setConfig: (newConfig: Record) => void; + setGlobalState: (newState: any) => void; +}; + +export const RainbowContext = createContext({ + config: {}, + setConfig: () => {}, + setGlobalState: () => {}, +}); + const storageKey = 'config'; const storage = new MMKV({ diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index 1523bae55c4..c95e6871d6c 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -135,7 +135,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { } setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; - avatarUrl = avatarUrl || avatar?.imageUrl; + avatarUrl = avatarUrl || (avatar && avatar?.imageUrl); setBusy(false); startImportProfile(name, guardedForceColor, address, avatarUrl); analytics.track('Show wallet profile modal for ENS address', { diff --git a/src/hooks/useOnAvatarPress.ts b/src/hooks/useOnAvatarPress.ts index bda2d6f20f5..d4b3f3c9bac 100644 --- a/src/hooks/useOnAvatarPress.ts +++ b/src/hooks/useOnAvatarPress.ts @@ -238,7 +238,7 @@ export default ({ screenType = 'transaction' }: UseOnAvatarPressProps = {}) => { ].filter(x => x), }; - const avatarActionSheetOptions = avatarContextMenuConfig.menuItems.map(item => item.actionTitle).concat(ios ? ['Cancel'] : []); + const avatarActionSheetOptions = avatarContextMenuConfig.menuItems.map(item => item && item.actionTitle).concat(ios ? ['Cancel'] : []); const onAvatarPressProfile = useCallback(() => { navigate(Routes.PROFILE_SHEET, { diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index bf79c65490d..e2fa3709f8c 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -92,6 +92,7 @@ export interface RainbowConfig extends Record degen_mode: boolean; featured_results: boolean; + nfts_enabled: boolean; } export const DEFAULT_CONFIG: RainbowConfig = { @@ -175,6 +176,7 @@ export const DEFAULT_CONFIG: RainbowConfig = { degen_mode: false, featured_results: false, + nfts_enabled: true, }; export async function fetchRemoteConfig(): Promise { @@ -230,7 +232,8 @@ export async function fetchRemoteConfig(): Promise { key === 'idfa_check_enabled' || key === 'rewards_enabled' || key === 'degen_mode' || - key === 'featured_results' + key === 'featured_results' || + key === 'nfts_enabled' ) { config[key] = entry.asBoolean(); } else { diff --git a/src/screens/points/PointsScreen.tsx b/src/screens/points/PointsScreen.tsx index 3d24b8ded00..61fe24d9f2d 100644 --- a/src/screens/points/PointsScreen.tsx +++ b/src/screens/points/PointsScreen.tsx @@ -75,7 +75,7 @@ export function PointsScreen() { navigate(Routes.CHANGE_WALLET_SHEET)} scaleTo={0.8} overflowMargin={50}> {accountImage ? ( @@ -83,7 +83,7 @@ export function PointsScreen() { )} - ) + ) : null } rightComponent={pointsNotificationsToggleEnabled ? : undefined} title={rewardsEnabled ? i18n.t(i18n.l.account.tab_rewards) : i18n.t(i18n.l.account.tab_points)}