import { NativeStackScreenProps } from '@react-navigation/native-stack' import React, { useRef } from 'react' import { useTranslation } from 'react-i18next' import { ScrollView, StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import AppAnalytics from 'src/analytics/AppAnalytics' import { AssetsEvents } from 'src/analytics/Events' import { TokenProperties } from 'src/analytics/Properties' import CeloNewsFeed from 'src/celoNews/CeloNewsFeed' import BackButton from 'src/components/BackButton' import { BottomSheetModalRefType } from 'src/components/BottomSheet' import Button, { BtnSizes } from 'src/components/Button' import PercentageIndicator from 'src/components/PercentageIndicator' import TokenDisplay from 'src/components/TokenDisplay' import TokenIcon, { IconSize } from 'src/components/TokenIcon' import Touchable from 'src/components/Touchable' import CustomHeader from 'src/components/header/CustomHeader' import { CICOFlow } from 'src/fiatExchanges/types' import ArrowRightThick from 'src/icons/ArrowRightThick' import DataDown from 'src/icons/DataDown' import DataUp from 'src/icons/DataUp' import SwapArrows from 'src/icons/SwapArrows' import QuickActionsAdd from 'src/icons/quick-actions/Add' import QuickActionsMore from 'src/icons/quick-actions/More' import QuickActionsSend from 'src/icons/quick-actions/Send' import QuickActionsWithdraw from 'src/icons/quick-actions/Withdraw' import { getLocalCurrencySymbol } from 'src/localCurrency/selectors' import { noHeader } from 'src/navigator/Headers' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { isAppSwapsEnabledSelector } from 'src/navigator/selectors' import { StackParamList } from 'src/navigator/types' import PriceHistoryChart from 'src/priceHistory/PriceHistoryChart' import { useSelector } from 'src/redux/hooks' import { NETWORK_NAMES } from 'src/shared/conts' import Colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import { Spacing } from 'src/styles/styles' import variables from 'src/styles/variables' import { TokenBalanceItem } from 'src/tokens/TokenBalanceItem' import TokenDetailsMoreActions from 'src/tokens/TokenDetailsMoreActions' import { useCashInTokens, useCashOutTokens, useSwappableTokens, useTokenInfo, } from 'src/tokens/hooks' import { sortedTokensWithBalanceSelector } from 'src/tokens/selectors' import { TokenBalance } from 'src/tokens/slice' import { TokenAction, TokenActionName } from 'src/tokens/types' import { getSupportedNetworkIdsForSend, getTokenAnalyticsProps, isHistoricalPriceUpdated, } from 'src/tokens/utils' import networkConfig from 'src/web3/networkConfig' type Props = NativeStackScreenProps const MAX_ACTION_BUTTONS = 3 export default function TokenDetailsScreen({ route }: Props) { const { tokenId } = route.params const { t } = useTranslation() const token = useTokenInfo(tokenId) if (!token) throw new Error(`token with id ${tokenId} not found`) const actions = useActions(token) const tokenDetailsMoreActionsBottomSheetRef = useRef(null) const localCurrencySymbol = useSelector(getLocalCurrencySymbol) return ( } /> {token.name} {!token.isStableCoin && } {token.isNative && ( )} {t('tokenDetails.yourBalance')} {!!token.infoUrl && ( )} {token.tokenId === networkConfig.celoTokenId && } ) } TokenDetailsScreen.navigationOptions = { ...noHeader, } function PriceInfo({ token }: { token: TokenBalance }) { const { t } = useTranslation() if (!token.priceUsd) { return ( {t('tokenDetails.priceUnavailable')} ) } if (!token.historicalPricesUsd || !isHistoricalPriceUpdated(token)) { return null } return ( ) } export const useActions = (token: TokenBalance) => { const { t } = useTranslation() const supportedNetworkIdsForSend = getSupportedNetworkIdsForSend() const sendableTokensWithBalance = useSelector((state) => sortedTokensWithBalanceSelector(state, supportedNetworkIdsForSend) ) const { swappableFromTokens } = useSwappableTokens() const cashInTokens = useCashInTokens() const cashOutTokens = useCashOutTokens() const isSwapEnabled = useSelector(isAppSwapsEnabledSelector) const showWithdraw = !!cashOutTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId) return [ { name: TokenActionName.Send, title: t('tokenDetails.actions.send'), details: t('tokenDetails.actionDescriptions.sendV1_74', { supportedNetworkNames: supportedNetworkIdsForSend .map((networkId) => NETWORK_NAMES[networkId]) .join(', '), count: supportedNetworkIdsForSend.length, }), iconComponent: QuickActionsSend, onPress: () => { navigate(Screens.SendSelectRecipient, { defaultTokenIdOverride: token.tokenId }) }, visible: !!sendableTokensWithBalance.find((tokenInfo) => tokenInfo.tokenId === token.tokenId), }, { name: TokenActionName.Swap, title: t('tokenDetails.actions.swap'), details: t('tokenDetails.actionDescriptions.swap'), iconComponent: SwapArrows, onPress: () => { navigate(Screens.SwapScreenWithBack, { fromTokenId: token.tokenId }) }, visible: isSwapEnabled && !!swappableFromTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId), }, { name: TokenActionName.Add, title: t('tokenDetails.actions.add'), details: t('tokenDetails.actionDescriptions.add'), iconComponent: QuickActionsAdd, onPress: () => { navigate(Screens.FiatExchangeAmount, { tokenId: token.tokenId, flow: CICOFlow.CashIn, tokenSymbol: token.symbol, }) }, visible: !!cashInTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId), }, { name: TokenActionName.Withdraw, title: t('tokenDetails.actions.withdraw'), details: t('tokenDetails.actionDescriptions.withdraw'), iconComponent: QuickActionsWithdraw, onPress: () => { navigate(Screens.WithdrawSpend) }, visible: showWithdraw, }, ].filter((action) => action.visible) } function Actions({ token, bottomSheetRef, actions, }: { token: TokenBalance bottomSheetRef: React.RefObject actions: TokenAction[] }) { const { t } = useTranslation() const cashOutTokens = useCashOutTokens() const showWithdraw = !!cashOutTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId) const moreAction = { name: TokenActionName.More, title: t('tokenDetails.actions.more'), iconComponent: QuickActionsMore, onPress: () => { bottomSheetRef.current?.snapToIndex(0) }, } // if there are 4 actions or 3 actions and one of them is withdraw, show the // More button. The withdraw condition exists to avoid the visual overflow, // since the icon + withdraw text is bigger const actionButtons = actions.length > MAX_ACTION_BUTTONS || (actions.length === MAX_ACTION_BUTTONS && showWithdraw) ? [...actions.slice(0, MAX_ACTION_BUTTONS - 1), moreAction] : actions return ( {actionButtons.map((action) => (