Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Componetize Tokens screen #11471

Merged
merged 39 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0248c74
feat: Add sortAssets utility function
gambinish Sep 27, 2024
6a2e8c7
chore: Breakup monolithic Tokens/index.tsx into smaller components
gambinish Sep 27, 2024
7a78717
fix: Correctly handle null check for buyable footer button
gambinish Sep 27, 2024
57295ee
fix: Tweak boolean logic for rendering footer components
gambinish Sep 27, 2024
d00354f
chore: Update snapshot
gambinish Sep 27, 2024
be87f81
chore: Type out tokens as TokenI[]
gambinish Sep 27, 2024
68c7b2e
chore: Remove unecessary comments around import statements in Networt…
gambinish Sep 27, 2024
a02e139
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Sep 27, 2024
de5bbed
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Sep 27, 2024
0fd9ad5
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Sep 27, 2024
032618c
chore: Add unit tests to utility file, provide exports from index file
gambinish Sep 30, 2024
a58f1ba
chore: Rename handleBalance to me more descriptive of what its logic …
gambinish Sep 30, 2024
1dd31e3
chore: rename Networth to PortfolioBalance for clarity
gambinish Sep 30, 2024
16ded6a
chore: Cleanup styles extra whitespace
gambinish Sep 30, 2024
52d3d85
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Sep 30, 2024
dd3ffdf
fix: Update snapshot tests
gambinish Sep 30, 2024
da640bc
Merge branch 'chore/componetize-tokens-screen' of github.com:MetaMask…
gambinish Sep 30, 2024
ebcbcf6
fix: Remove unused component
gambinish Sep 30, 2024
31fad19
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Sep 30, 2024
ab6f500
fix: StackNavigator derives its own types
gambinish Oct 1, 2024
da5ca37
Update app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDeta…
gambinish Oct 1, 2024
3fdc1af
Update app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDeta…
gambinish Oct 1, 2024
812d219
Update app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDeta…
gambinish Oct 1, 2024
7ac0290
Update app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDeta…
gambinish Oct 1, 2024
a6412b7
Update app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDeta…
gambinish Oct 1, 2024
50d278d
Update app/components/UI/Tokens/util/sortAssets.test.ts
gambinish Oct 1, 2024
58d461a
Update app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDeta…
gambinish Oct 1, 2024
356fc57
fix: Type out StackNavigationProp in TokenList
gambinish Oct 1, 2024
5c49a2a
Merge branch 'chore/componetize-tokens-screen' of github.com:MetaMask…
gambinish Oct 1, 2024
dcd0335
fix: Derive StackNavigator props in stake button icon component
gambinish Oct 1, 2024
8f4d4dd
fix: Remove Typescript todos
gambinish Oct 1, 2024
60d72e8
chore: Extract nested ternary in TokenListItem to an independant if/e…
gambinish Oct 1, 2024
9abfc8e
fix: Remove needless assignment of mainBalance variable in TokenListItem
gambinish Oct 1, 2024
e30107f
fix: Lint unused import
gambinish Oct 1, 2024
7f6ce57
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Oct 1, 2024
a531043
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Oct 1, 2024
560a0a9
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Oct 2, 2024
b144738
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Oct 2, 2024
3e8c909
Merge branch 'main' into chore/componetize-tokens-screen
gambinish Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from 'react';
import { View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useSelector } from 'react-redux';
import useIsOriginalNativeTokenSymbol from '../../../../hooks/useIsOriginalNativeTokenSymbol/useIsOriginalNativeTokenSymbol';
import { useMetrics } from '../../../../hooks/useMetrics';
import { useTheme } from '../../../../../util/theme';
import AppConstants from '../../../../../core/AppConstants';
import Engine from '../../../../../core/Engine';
import Routes from '../../../../../constants/navigation/Routes';
import { MetaMetricsEvents } from '../../../../../core/Analytics';
import {
selectChainId,
selectProviderConfig,
selectTicker,
} from '../../../../../selectors/networkController';
import { selectCurrentCurrency } from '../../../../../selectors/currencyRateController';
import { RootState } from '../../../../../reducers';
import { renderFiat } from '../../../../../util/number';
import { isTestNet } from '../../../../../util/networks';
import { isPortfolioUrl } from '../../../../../util/url';
import createStyles from '../../styles';
import Button, {
ButtonVariants,
ButtonSize,
ButtonWidthTypes,
} from '../../../../../component-library/components/Buttons/Button';
import Text from '../../../../../component-library/components/Texts/Text';
import AggregatedPercentage from '../../../../../component-library/components-temp/Price/AggregatedPercentage';
import { IconName } from '../../../../../component-library/components/Icons/Icon';
import { BrowserTab } from '../../types';
import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors';
import { strings } from '../../../../../../locales/i18n';

export const PortfolioBalance = () => {
const { colors } = useTheme();
const styles = createStyles(colors);
gambinish marked this conversation as resolved.
Show resolved Hide resolved
const balance = Engine.getTotalFiatAccountBalance();
const navigation = useNavigation();
const { trackEvent, isEnabled } = useMetrics();

const { type } = useSelector(selectProviderConfig);
const chainId = useSelector(selectChainId);
const ticker = useSelector(selectTicker);
const isDataCollectionForMarketingEnabled = useSelector(
(state: RootState) => state.security.dataCollectionForMarketing,
);
const currentCurrency = useSelector(selectCurrentCurrency);
const browserTabs = useSelector((state: RootState) => state.browser.tabs);

const isOriginalNativeTokenSymbol = useIsOriginalNativeTokenSymbol(
chainId,
ticker,
type,
);

let total;
if (isOriginalNativeTokenSymbol) {
const tokenFiatTotal = balance?.tokenFiat ?? 0;
const ethFiatTotal = balance?.ethFiat ?? 0;
total = tokenFiatTotal + ethFiatTotal;
} else {
total = balance?.tokenFiat ?? 0;
}

const fiatBalance = `${renderFiat(total, currentCurrency)}`;

const onOpenPortfolio = () => {
const existingPortfolioTab = browserTabs.find(({ url }: BrowserTab) =>
isPortfolioUrl(url),
);

let existingTabId;
let newTabUrl;
if (existingPortfolioTab) {
existingTabId = existingPortfolioTab.id;
} else {
const analyticsEnabled = isEnabled();
const portfolioUrl = new URL(AppConstants.PORTFOLIO.URL);

portfolioUrl.searchParams.append('metamaskEntry', 'mobile');

// Append user's privacy preferences for metrics + marketing on user navigation to Portfolio.
portfolioUrl.searchParams.append(
'metricsEnabled',
String(analyticsEnabled),
);
portfolioUrl.searchParams.append(
'marketingEnabled',
String(!!isDataCollectionForMarketingEnabled),
);

newTabUrl = portfolioUrl.href;
}
const params = {
...(newTabUrl && { newTabUrl }),
...(existingTabId && { existingTabId, newTabUrl: undefined }),
timestamp: Date.now(),
};
navigation.navigate(Routes.BROWSER.HOME, {
screen: Routes.BROWSER.VIEW,
params,
});
trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, {
portfolioUrl: AppConstants.PORTFOLIO.URL,
});
};

return (
<View style={styles.portfolioBalance}>
<View>
<Text
style={styles.fiatBalance}
testID={WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT}
>
{fiatBalance}
</Text>

{!isTestNet(chainId) ? (
<AggregatedPercentage
ethFiat={balance?.ethFiat}
tokenFiat={balance?.tokenFiat}
tokenFiat1dAgo={balance?.tokenFiat1dAgo}
ethFiat1dAgo={balance?.ethFiat1dAgo}
/>
) : null}
</View>
<Button
variant={ButtonVariants.Secondary}
size={ButtonSize.Md}
width={ButtonWidthTypes.Full}
style={styles.buyButton}
onPress={onOpenPortfolio}
label={strings('asset_overview.portfolio_button')}
testID={WalletViewSelectorsIDs.PORTFOLIO_BUTTON}
endIconName={IconName.Export}
/>
</View>
);
};
48 changes: 48 additions & 0 deletions app/components/UI/Tokens/TokenList/ScamWarningIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { TokenI } from '../../types';
import useIsOriginalNativeTokenSymbol from '../../../../hooks/useIsOriginalNativeTokenSymbol/useIsOriginalNativeTokenSymbol';
import { useSelector } from 'react-redux';
import {
selectChainId,
selectProviderConfig,
selectTicker,
} from '../../../../../selectors/networkController';
import ButtonIcon, {
ButtonIconSizes,
} from '../../../../../../app/component-library/components/Buttons/ButtonIcon';
import {
IconColor,
IconName,
} from '../../../../../component-library/components/Icons/Icon';

interface ScamWarningIconProps {
asset: TokenI;
setShowScamWarningModal: (arg: boolean) => void;
}

export const ScamWarningIcon = ({
asset,
setShowScamWarningModal,
}: ScamWarningIconProps) => {
const { type } = useSelector(selectProviderConfig);
const chainId = useSelector(selectChainId);
const ticker = useSelector(selectTicker);
const isOriginalNativeTokenSymbol = useIsOriginalNativeTokenSymbol(
chainId,
ticker,
type,
);
if (!isOriginalNativeTokenSymbol && asset.isETH) {
return (
<ButtonIcon
iconName={IconName.Danger}
onPressIn={() => {
setShowScamWarningModal(true);
}}
iconColor={IconColor.Error}
size={ButtonIconSizes.Lg}
/>
);
}
return null;
};
81 changes: 81 additions & 0 deletions app/components/UI/Tokens/TokenList/ScamWarningModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import Modal from 'react-native-modal';
import { useTheme } from '../../../../../util/theme';
import createStyles from '../../styles';
import Box from '../../../../UI/Ramp/components/Box';
import { View } from 'react-native';
import SheetHeader from '../../../../../../app/component-library/components/Sheet/SheetHeader';
import { strings } from '../../../../../../locales/i18n';
import Text from '../../../../../component-library/components/Texts/Text';
import Button, {
ButtonVariants,
ButtonSize,
} from '../../../../../component-library/components/Buttons/Button';
import {
selectTicker,
selectProviderConfig,
} from '../../../../../selectors/networkController';
import { useSelector } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import Routes from '../../../../../constants/navigation/Routes';

interface ScamWarningModalProps {
showScamWarningModal: boolean;
setShowScamWarningModal: (arg: boolean) => void;
}

export const ScamWarningModal = ({
showScamWarningModal,
setShowScamWarningModal,
}: ScamWarningModalProps) => {
const navigation = useNavigation();
const { colors } = useTheme();

const ticker = useSelector(selectTicker);
const { rpcUrl } = useSelector(selectProviderConfig);

const styles = createStyles(colors);

const goToNetworkEdit = () => {
navigation.navigate(Routes.ADD_NETWORK, {
network: rpcUrl,
});
setShowScamWarningModal(false);
};

return (
<Modal
isVisible={showScamWarningModal}
onBackdropPress={() => setShowScamWarningModal(false)}
onSwipeComplete={() => setShowScamWarningModal(false)}
swipeDirection="down"
propagateSwipe
avoidKeyboard
style={styles.bottomModal}
backdropColor={colors.overlay.default}
backdropOpacity={1}
>
<Box style={styles.box}>
<View style={styles.notch} />
<SheetHeader title={strings('wallet.potential_scam')} />

<Box style={styles.boxContent}>
<Text>
{strings('wallet.network_not_matching')}
{` ${ticker},`}
{strings('wallet.target_scam_network')}
</Text>
</Box>
<Box style={styles.boxContent}>
<Button
variant={ButtonVariants.Secondary}
label={strings('networks.edit_network_details')}
onPress={goToNetworkEdit}
style={styles.editNetworkButton}
size={ButtonSize.Lg}
/>
</Box>
</Box>
</Modal>
);
};
95 changes: 95 additions & 0 deletions app/components/UI/Tokens/TokenList/StakeButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { TokenI, BrowserTab } from '../../types';
import { useNavigation } from '@react-navigation/native';
import { isPooledStakingFeatureEnabled } from '../../../Stake/constants';
import Routes from '../../../../../constants/navigation/Routes';
import { useSelector } from 'react-redux';
import AppConstants from '../../../../../core/AppConstants';
import {
MetaMetricsEvents,
useMetrics,
} from '../../../../../components/hooks/useMetrics';
import { getDecimalChainId } from '../../../../../util/networks';
import { selectChainId } from '../../../../../selectors/networkController';
import { Pressable } from 'react-native';
import Text, {
TextColor,
TextVariant,
} from '../../../../../component-library/components/Texts/Text';
import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors';
import { useTheme } from '../../../../../util/theme';
import createStyles from '../../styles';
import Icon, {
IconColor,
IconName,
IconSize,
} from '../../../../../component-library/components/Icons/Icon';
import { strings } from '../../../../../../locales/i18n';
import { RootState } from '../../../../../reducers';

interface StakeButtonProps {
asset: TokenI;
}

export const StakeButton = ({ asset }: StakeButtonProps) => {
const { colors } = useTheme();
const styles = createStyles(colors);
const navigation = useNavigation();
const { trackEvent } = useMetrics();

const browserTabs = useSelector((state: RootState) => state.browser.tabs);
const chainId = useSelector(selectChainId);

const onStakeButtonPress = () => {
if (isPooledStakingFeatureEnabled()) {
navigation.navigate('StakeScreens', { screen: Routes.STAKING.STAKE });
} else {
const existingStakeTab = browserTabs.find((tab: BrowserTab) =>
tab.url.includes(AppConstants.STAKE.URL),
);
let existingTabId;
let newTabUrl;
if (existingStakeTab) {
existingTabId = existingStakeTab.id;
} else {
newTabUrl = `${AppConstants.STAKE.URL}?metamaskEntry=mobile`;
}
const params = {
...(newTabUrl && { newTabUrl }),
...(existingTabId && { existingTabId, newTabUrl: undefined }),
timestamp: Date.now(),
};
navigation.navigate(Routes.BROWSER.HOME, {
screen: Routes.BROWSER.VIEW,
params,
});
}
trackEvent(MetaMetricsEvents.STAKE_BUTTON_CLICKED, {
chain_id: getDecimalChainId(chainId),
location: 'Home Screen',
text: 'Stake',
token_symbol: asset.symbol,
url: AppConstants.STAKE.URL,
});
};

return (
<Pressable
onPress={onStakeButtonPress}
testID={WalletViewSelectorsIDs.STAKE_BUTTON}
style={styles.stakeButton}
>
<Text variant={TextVariant.BodyLGMedium}>
{' • '}
<Text color={TextColor.Primary} variant={TextVariant.BodyLGMedium}>
{`${strings('stake.stake')} `}
</Text>
</Text>
<Icon
name={IconName.Plant}
size={IconSize.Sm}
color={IconColor.Primary}
/>
</Pressable>
);
};
Loading
Loading