diff --git a/packages/mobile/src/screens/all-coins-screen/AllCoinsScreen.tsx b/packages/mobile/src/screens/all-coins-screen/AllCoinsScreen.tsx
new file mode 100644
index 00000000000..9f7998ae24a
--- /dev/null
+++ b/packages/mobile/src/screens/all-coins-screen/AllCoinsScreen.tsx
@@ -0,0 +1,44 @@
+import { useArtistCoins } from '@audius/common/api'
+
+import {
+ Box,
+ Divider,
+ Flex,
+ LoadingSpinner,
+ Paper
+} from '@audius/harmony-native'
+import { Screen, ScreenContent, ScrollView } from 'app/components/core'
+
+import { CoinCard } from '../wallet-screen/components/CoinCard'
+
+// TODO: Add the search bar
+// TODO: Add the 'looking for more' section
+
+export const AllCoinsScreen = () => {
+ const { data: artistCoins, isPending: isLoadingCoins } = useArtistCoins()
+
+ return (
+
+
+
+ {isLoadingCoins ? (
+
+
+
+ ) : (
+
+
+ {artistCoins?.map((coin) => (
+
+
+
+
+ ))}
+
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/mobile/src/screens/all-coins-screen/index.ts b/packages/mobile/src/screens/all-coins-screen/index.ts
new file mode 100644
index 00000000000..67966653fff
--- /dev/null
+++ b/packages/mobile/src/screens/all-coins-screen/index.ts
@@ -0,0 +1 @@
+export { AllCoinsScreen } from './AllCoinsScreen'
diff --git a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx
index ed1cb1b7e82..73d28488477 100644
--- a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx
+++ b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx
@@ -21,12 +21,14 @@ import type { FilterButtonScreenParams } from '@audius/harmony-native'
import { useDrawer } from 'app/hooks/useDrawer'
import { setLastNavAction } from 'app/hooks/useNavigation'
import { AiGeneratedTracksScreen } from 'app/screens/ai-generated-tracks-screen'
+import { AllCoinsScreen } from 'app/screens/all-coins-screen'
import { AppDrawerContext } from 'app/screens/app-drawer-screen'
import { AudioScreen } from 'app/screens/audio-screen'
import { ChangeEmailModalScreen } from 'app/screens/change-email-screen/ChangeEmailScreen'
import { ChatListScreen } from 'app/screens/chat-screen/ChatListScreen'
import { ChatScreen } from 'app/screens/chat-screen/ChatScreen'
import { ChatUserListScreen } from 'app/screens/chat-screen/ChatUserListScreen'
+import { CoinDetailsScreen } from 'app/screens/coin-details-screen'
import { CollectionScreen } from 'app/screens/collection-screen/CollectionScreen'
import { EditProfileScreen } from 'app/screens/edit-profile-screen'
import { ProfileScreen } from 'app/screens/profile-screen'
@@ -110,6 +112,8 @@ export type AppTabScreenParamList = {
AudioScreen: undefined
RewardsScreen: undefined
wallet: undefined
+ AllCoinsScreen: undefined
+ CoinDetailsScreen: { mint: string }
Upload: {
initialMetadata?: Partial
}
@@ -225,6 +229,8 @@ export const AppTabScreen = ({ baseScreen, Stack }: AppTabScreenProps) => {
+
+
diff --git a/packages/mobile/src/screens/coin-details-screen/CoinDetailsScreen.tsx b/packages/mobile/src/screens/coin-details-screen/CoinDetailsScreen.tsx
new file mode 100644
index 00000000000..2b84cc0320e
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/CoinDetailsScreen.tsx
@@ -0,0 +1,38 @@
+import { useArtistCoin } from '@audius/common/api'
+import { useRoute } from '@react-navigation/native'
+
+import { Flex } from '@audius/harmony-native'
+import { Screen, ScreenContent, ScrollView } from 'app/components/core'
+
+import { BalanceCard } from './components/BalanceCard'
+import { CoinInfoCard } from './components/CoinInfoCard'
+import { CoinInsightsCard } from './components/CoinInsightsCard'
+import { CoinLeaderboardCard } from './components/CoinLeaderboardCard'
+
+// TODO: Add External Wallets section
+
+export const CoinDetailsScreen = () => {
+ const { mint } = useRoute().params as { mint: string }
+ const { data: coin } = useArtistCoin({ mint })
+ const { ticker } = coin ?? {}
+
+ return (
+
+
+
+
+
+
+
+
+ {/* */}
+
+
+
+
+ )
+}
diff --git a/packages/mobile/src/screens/coin-details-screen/components/BalanceCard.tsx b/packages/mobile/src/screens/coin-details-screen/components/BalanceCard.tsx
new file mode 100644
index 00000000000..39ea9fe0b2b
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/components/BalanceCard.tsx
@@ -0,0 +1,172 @@
+import { useTokenBalance, useArtistCoins } from '@audius/common/api'
+import { useFormattedTokenBalance } from '@audius/common/hooks'
+import { walletMessages } from '@audius/common/messages'
+import { Image } from 'react-native'
+
+import {
+ Paper,
+ Flex,
+ Text,
+ HexagonalIcon,
+ Button
+} from '@audius/harmony-native'
+
+const messages = {
+ becomeAMember: 'Become a Member',
+ hintDescription: (title: string) =>
+ `Buy ${title} to gain access to exclusive members-only perks!`
+}
+
+type BalanceStateProps = {
+ title: string
+ logoURI?: string
+ onBuy?: () => void
+ onReceive?: () => void
+ onSend?: () => void
+}
+
+const TokenIcon = ({ logoURI }: { logoURI?: string }) => {
+ if (!logoURI) return null
+
+ return (
+
+
+
+ )
+}
+
+const ZeroBalanceState = ({
+ title,
+ logoURI,
+ onBuy,
+ onReceive
+}: BalanceStateProps) => {
+ return (
+
+
+
+
+ {title}
+
+
+
+
+ {messages.becomeAMember}
+
+ {messages.hintDescription(title)}
+
+
+
+
+
+
+ )
+}
+
+const HasBalanceState = ({
+ title,
+ logoURI,
+ onBuy,
+ onSend,
+ onReceive,
+ mint
+}: BalanceStateProps & { mint: string }) => {
+ const {
+ tokenBalanceFormatted,
+ tokenDollarValue
+ // isTokenBalanceLoading,
+ // isTokenPriceLoading
+ } = useFormattedTokenBalance(mint)
+
+ // const isLoading = isTokenBalanceLoading || isTokenPriceLoading
+
+ return (
+
+
+
+
+
+
+ {tokenBalanceFormatted}
+
+
+ {title}
+
+
+
+ {tokenDollarValue}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export const BalanceCard = ({ mint }: { mint: string }) => {
+ const { data: coinInsights, isPending: coinsLoading } = useArtistCoins({
+ mint: [mint]
+ })
+ const { data: tokenBalance } = useTokenBalance({ mint })
+
+ const coin = coinInsights?.[0]
+
+ if (coinsLoading || !coin) {
+ // TODO: Add skeleton state
+ return null
+ }
+
+ const title = coin.ticker ?? ''
+ const logoURI = coin.logoUri
+
+ return (
+
+ {!tokenBalance?.balance ||
+ Number(tokenBalance.balance.toString()) === 0 ? (
+ {}}
+ onReceive={() => {}}
+ />
+ ) : (
+ {}}
+ onSend={() => {}}
+ onReceive={() => {}}
+ mint={mint}
+ />
+ )}
+
+ )
+}
diff --git a/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx b/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx
new file mode 100644
index 00000000000..5eb3d657349
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx
@@ -0,0 +1,221 @@
+import { useCallback } from 'react'
+
+import { useArtistCoin } from '@audius/common/api'
+import { WidthSizes } from '@audius/common/models'
+import type { Coin } from '@audius/sdk'
+import { decodeHashId } from '@audius/sdk'
+import { Image, Linking } from 'react-native'
+
+import {
+ Box,
+ Divider,
+ Flex,
+ HexagonalIcon,
+ IconExternalLink,
+ IconGift,
+ Paper,
+ PlainButton,
+ Text
+} from '@audius/harmony-native'
+import { useCoverPhoto } from 'app/components/image/CoverPhoto'
+import { useNavigation } from 'app/hooks/useNavigation'
+import { env } from 'app/services/env'
+
+// TODO: Move these to common messages
+const messages = {
+ loading: 'Loading...',
+ createdBy: 'Created By',
+ whatIs: (title: string) => `What is ${title}?`,
+ description1: (title: string) =>
+ `${title} is a community token on the Audius platform. You can use ${title} for tipping artists, participating in community activities, and engaging with the decentralized music ecosystem.`,
+ description2: (title: string) =>
+ `Holding ${title} gives you access to exclusive features and helps support your favorite artists on Audius.`,
+ learnMore: 'Learn More',
+ viewLeaderboard: 'View Leaderboard',
+ title: 'Bronze +',
+ profileFlair: 'Profile Flair',
+ customDiscordRole: 'Custom Discord Role',
+ messageBlasts: 'Message Blasts',
+ openDiscord: 'Open The Discord',
+ refreshDiscordRole: 'Refresh Discord Role',
+ browseRewards: 'Browse Rewards',
+ rewardTiers: 'Reward Tiers'
+}
+
+const BannerSection = ({ mint }: { mint: string }) => {
+ const { data: coin, isLoading } = useArtistCoin({ mint })
+
+ const userId = coin?.ownerId
+ ? (decodeHashId(coin.ownerId) ?? undefined)
+ : undefined
+
+ const { source } = useCoverPhoto({
+ userId,
+ size: WidthSizes.SIZE_640
+ })
+
+ if (isLoading || !coin) {
+ // TODO: Add loading state
+ return null
+ }
+
+ const logoURI = coin.logoUri
+ const name = coin.ticker
+
+ const bannerHeight = 120
+ const iconSize = 24
+
+ return (
+
+ {/* Background Image */}
+ {source && (
+
+ )}
+
+ {/* Background Overlay */}
+
+
+ {/* Content */}
+
+
+ {messages.createdBy}
+
+
+
+
+
+
+
+
+ {name}
+
+
+
+
+
+ )
+}
+
+const CoinDescriptionSection = ({ coin }: { coin: Coin }) => {
+ if (!coin.description) return null
+
+ const title = coin.ticker ?? ''
+ const descriptionParagraphs = coin.description.split('\n') ?? []
+
+ return (
+
+
+
+ {messages.whatIs(title)}
+
+
+
+
+ {descriptionParagraphs.map((paragraph) => {
+ if (paragraph.trim() === '') {
+ return null
+ }
+
+ return (
+
+ {paragraph}
+
+ )
+ })}
+
+
+ )
+}
+
+export const CoinInfoCard = ({ mint }: { mint: string }) => {
+ const { data: coin, isLoading } = useArtistCoin({ mint })
+ const navigation = useNavigation()
+
+ const handleLearnMore = useCallback(() => {
+ // Open the coin website in browser
+ if (coin?.website) {
+ Linking.openURL(coin?.website)
+ }
+ }, [coin?.website])
+
+ const handleBrowseRewards = useCallback(() => {
+ navigation.navigate('RewardsScreen')
+ }, [navigation])
+
+ if (isLoading || !coin) {
+ return null
+ }
+
+ const isWAudio = coin.mint === env.WAUDIO_MINT_ADDRESS
+ const CTAIcon = isWAudio ? IconGift : IconExternalLink
+
+ return (
+
+
+
+
+
+
+ {isWAudio ? messages.browseRewards : messages.learnMore}
+
+
+
+ )
+}
diff --git a/packages/mobile/src/screens/coin-details-screen/components/CoinInsightsCard.tsx b/packages/mobile/src/screens/coin-details-screen/components/CoinInsightsCard.tsx
new file mode 100644
index 00000000000..73d4fa2cc3b
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/components/CoinInsightsCard.tsx
@@ -0,0 +1,123 @@
+import { useArtistCoinInsights } from '@audius/common/api'
+
+import {
+ Flex,
+ IconCaretDown,
+ IconCaretUp,
+ IconButton,
+ Paper,
+ Text,
+ IconKebabHorizontal
+} from '@audius/harmony-native'
+
+import type { MetricData } from './coinMetricsUtil'
+import { createCoinMetrics } from './coinMetricsUtil'
+
+const messages = {
+ title: 'Insights',
+ pricePerCoin: 'Price per coin',
+ holdersOnAudius: 'Holders on Audius',
+ uniqueHolders: 'Unique Holders',
+ volume24hr: 'Volume (24hr)',
+ totalTransfers: 'Total Transfers'
+}
+
+const MetricRow = ({ metric }: { metric: MetricData }) => {
+ const changeColor = metric.change?.isPositive ? 'premium' : 'danger'
+
+ return (
+
+
+
+ {metric.value}
+
+
+ {metric.label}
+
+
+
+ {metric.change ? (
+
+
+ {metric.change.value}
+
+
+ {metric.change.isPositive ? (
+
+ ) : (
+
+ )}
+
+
+ ) : null}
+
+ )
+}
+
+export const CoinInsightsCard = ({ mint }: { mint: string }) => {
+ const {
+ data: coinInsights,
+ isPending,
+ error
+ } = useArtistCoinInsights({ mint })
+
+ if (isPending || !coinInsights) {
+ return null
+ }
+
+ const metrics = createCoinMetrics(coinInsights)
+
+ return (
+
+
+
+ {messages.title}
+
+ {}}
+ />
+
+
+ {error || !coinInsights ? (
+
+
+ Unable to load insights
+
+
+ ) : (
+ metrics.map((metric) => (
+
+ ))
+ )}
+
+ )
+}
diff --git a/packages/mobile/src/screens/coin-details-screen/components/CoinLeaderboardCard.tsx b/packages/mobile/src/screens/coin-details-screen/components/CoinLeaderboardCard.tsx
new file mode 100644
index 00000000000..648b94f566f
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/components/CoinLeaderboardCard.tsx
@@ -0,0 +1,86 @@
+import { useCallback } from 'react'
+
+import { useArtistCoinMembers, useUsers } from '@audius/common/api'
+import { TouchableOpacity } from 'react-native'
+
+import {
+ Divider,
+ Flex,
+ IconButton,
+ IconCaretRight,
+ LoadingSpinner,
+ Paper,
+ Text
+} from '@audius/harmony-native'
+import { useNavigation } from 'app/hooks/useNavigation'
+import { ProfilePictureList } from 'app/screens/notifications-screen/Notification'
+
+const messages = {
+ title: 'Members Leaderboard',
+ leaderboard: 'Leaderboard'
+}
+
+export const CoinLeaderboardCard = ({ mint }: { mint: string }) => {
+ const navigation = useNavigation()
+ const { data: leaderboardUsers, isPending: isLeaderboardPending } =
+ useArtistCoinMembers({ mint })
+ const { data: users, isPending: isUsersPending } = useUsers(
+ leaderboardUsers?.map((user) => user.userId)
+ )
+ const isPending = isLeaderboardPending || isUsersPending
+
+ const handleViewLeaderboard = useCallback(() => {
+ navigation.navigate('CoinLeaderboard', {
+ mint
+ })
+ }, [mint, navigation])
+
+ if (!mint || !users?.length) return null
+
+ return (
+
+
+
+ {messages.title}
+
+
+
+
+ {isPending ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/mobile/src/screens/coin-details-screen/components/coinMetricsUtil.ts b/packages/mobile/src/screens/coin-details-screen/components/coinMetricsUtil.ts
new file mode 100644
index 00000000000..e191bc5bf34
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/components/coinMetricsUtil.ts
@@ -0,0 +1,87 @@
+import { formatCount, formatCurrencyWithMax } from '@audius/common/utils'
+import type { CoinInsights } from '@audius/sdk'
+
+// TODO: Move all of this to a shared location with web
+
+export type MetricData = {
+ value: string
+ label: string
+ change?: {
+ value: string
+ isPositive: boolean
+ }
+}
+
+const messages = {
+ pricePerCoin: 'Price per coin',
+ holdersOnAudius: 'Holders on Audius',
+ uniqueHolders: 'Unique Holders',
+ volume24hr: 'Volume (24hr)',
+ totalTransfers: 'Total Transfers'
+}
+
+const CURRENCY_FORMAT_MAX = 100_000
+
+const formatPercentage = (num: number): string => {
+ return `${num >= 0 ? '+' : ''}${num.toFixed(2)}%`
+}
+
+const createChangeData = (changePercent: number | undefined) => {
+ if (!changePercent || isNaN(changePercent)) {
+ return undefined
+ }
+ return {
+ value: formatPercentage(changePercent),
+ isPositive: changePercent >= 0
+ }
+}
+
+const createMetric = (
+ value: string,
+ label: string,
+ changePercent?: number
+): MetricData | null => {
+ try {
+ return {
+ value,
+ label,
+ change: createChangeData(changePercent)
+ }
+ } catch {
+ return null
+ }
+}
+
+export const createCoinMetrics = (coinInsights: CoinInsights): MetricData[] => {
+ const potentialMetrics = [
+ createMetric(
+ formatCurrencyWithMax(coinInsights.price, CURRENCY_FORMAT_MAX),
+ messages.pricePerCoin,
+ coinInsights.priceChange24hPercent
+ ),
+ createMetric(
+ formatCount(coinInsights.members),
+ messages.holdersOnAudius,
+ coinInsights.membersChange24hPercent
+ ),
+ createMetric(
+ formatCount(coinInsights.holder || 0),
+ messages.uniqueHolders,
+ coinInsights.uniqueWallet24hChangePercent
+ ),
+ createMetric(
+ formatCurrencyWithMax(coinInsights.v24hUSD, CURRENCY_FORMAT_MAX),
+ messages.volume24hr,
+ coinInsights.v24hChangePercent
+ ),
+ createMetric(
+ formatCount(coinInsights.trade24h),
+ messages.totalTransfers,
+ coinInsights.trade24hChangePercent
+ )
+ ]
+
+ return potentialMetrics.filter(
+ (metric): metric is MetricData => metric !== null
+ )
+}
diff --git a/packages/mobile/src/screens/coin-details-screen/index.ts b/packages/mobile/src/screens/coin-details-screen/index.ts
new file mode 100644
index 00000000000..af2d873e1df
--- /dev/null
+++ b/packages/mobile/src/screens/coin-details-screen/index.ts
@@ -0,0 +1 @@
+export { CoinDetailsScreen } from './CoinDetailsScreen'
diff --git a/packages/mobile/src/screens/user-list-screen/CoinLeaderboardScreen.tsx b/packages/mobile/src/screens/user-list-screen/CoinLeaderboardScreen.tsx
index 167d87df83c..0ae208afa90 100644
--- a/packages/mobile/src/screens/user-list-screen/CoinLeaderboardScreen.tsx
+++ b/packages/mobile/src/screens/user-list-screen/CoinLeaderboardScreen.tsx
@@ -1,6 +1,6 @@
import { useEffect } from 'react'
-import { useArtistCoinMembers } from '@audius/common/api'
+import { useArtistCoin, useArtistCoinMembers } from '@audius/common/api'
import {
coinLeaderboardUserListActions,
coinLeaderboardUserListSelectors
@@ -14,7 +14,7 @@ import { UserList } from './UserList'
import { UserListScreen } from './UserListScreen'
const messages = {
- title: 'BONK Leaderboard'
+ title: (ticker: string) => `${ticker + ' '}Leaderboard`
}
export const CoinLeaderboardScreen = () => {
@@ -22,6 +22,7 @@ export const CoinLeaderboardScreen = () => {
const { params } = useRoute<'CoinLeaderboard'>()
const { mint } = params
const reduxMint = useSelector(coinLeaderboardUserListSelectors.getMint)
+ const { data: coin } = useArtistCoin({ mint: reduxMint || mint })
const { data, isFetchingNextPage, fetchNextPage, isPending } =
useArtistCoinMembers({ mint: reduxMint || mint })
@@ -40,19 +41,22 @@ export const CoinLeaderboardScreen = () => {
if (!mint && !reduxMint) return null
return (
-
+
member.user_id)}
+ data={data?.map((member) => member.userId)}
isFetchingNextPage={isFetchingNextPage}
isPending={isPending}
fetchNextPage={fetchNextPage}
tag='COIN_LEADERBOARD'
showRank={true}
renderRightContent={(userId) => {
- const member = data?.find((m) => m.user_id === userId)
+ const member = data?.find((m) => m.userId === userId)
return member ? (
- {member.balance.toLocaleString()}
+ {member.balanceLocaleString}
) : null
}}
diff --git a/packages/mobile/src/screens/wallet-screen/WalletScreen.tsx b/packages/mobile/src/screens/wallet-screen/WalletScreen.tsx
index 5b90518711e..e907bb465c7 100644
--- a/packages/mobile/src/screens/wallet-screen/WalletScreen.tsx
+++ b/packages/mobile/src/screens/wallet-screen/WalletScreen.tsx
@@ -1,12 +1,8 @@
import React from 'react'
-import { useFeatureFlag } from '@audius/common/hooks'
-import { FeatureFlags } from '@audius/common/services'
-
import { Flex, IconWallet } from '@audius/harmony-native'
import { Screen, ScreenContent, ScrollView } from 'app/components/core'
-import { BonkLeaderboardButton } from './components/BonkLeaderboardButton'
import { CashWallet } from './components/CashWallet'
import { YourCoins } from './components/YourCoins'
@@ -15,9 +11,6 @@ const messages = {
}
export const WalletScreen = () => {
- const { isEnabled: isArtistCoinsEnabled } = useFeatureFlag(
- FeatureFlags.ARTIST_COINS
- )
return (
{
>
-
+
- {isArtistCoinsEnabled && }
diff --git a/packages/mobile/src/screens/wallet-screen/components/BonkLeaderboardButton.tsx b/packages/mobile/src/screens/wallet-screen/components/BonkLeaderboardButton.tsx
deleted file mode 100644
index 9a04ad0cbf2..00000000000
--- a/packages/mobile/src/screens/wallet-screen/components/BonkLeaderboardButton.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React, { useCallback } from 'react'
-
-import { Button, IconTrophy } from '@audius/harmony-native'
-import { useNavigation } from 'app/hooks/useNavigation'
-
-const messages = {
- viewLeaderboard: 'View BONK Leaderboard'
-}
-
-// Hardcoded example for now. When we add mobile token pages, this should slot into there
-export const BonkLeaderboardButton = () => {
- const navigation = useNavigation()
-
- const handlePress = useCallback(() => {
- // Navigate to the BONK leaderboard with the BONK mint address
- navigation.navigate('CoinLeaderboard', {
- mint: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263'
- })
- }, [navigation])
-
- return (
-
- )
-}
diff --git a/packages/mobile/src/screens/wallet-screen/components/CoinCard.tsx b/packages/mobile/src/screens/wallet-screen/components/CoinCard.tsx
new file mode 100644
index 00000000000..08370d8215f
--- /dev/null
+++ b/packages/mobile/src/screens/wallet-screen/components/CoinCard.tsx
@@ -0,0 +1,146 @@
+import { useCallback } from 'react'
+
+import { useArtistCoins } from '@audius/common/api'
+import { useFeatureFlag, useFormattedTokenBalance } from '@audius/common/hooks'
+import { FeatureFlags } from '@audius/common/services'
+import { Image, TouchableOpacity } from 'react-native'
+
+import {
+ Flex,
+ HexagonalIcon,
+ IconCaretRight,
+ Skeleton,
+ Text
+} from '@audius/harmony-native'
+import { useNavigation } from 'app/hooks/useNavigation'
+
+// TODO: Fix loading states
+// Skeletons are not working as expected
+
+const CoinCardSkeleton = () => {
+ return (
+
+ Test Ticker Name
+
+
+ )
+}
+
+const HexagonSkeleton = () => {
+ return (
+
+ )
+}
+
+export type CoinCardProps = {
+ mint: string
+ showUserBalance?: boolean
+}
+
+export const CoinCard = ({ mint, showUserBalance = true }: CoinCardProps) => {
+ const navigation = useNavigation()
+ const ICON_SIZE = showUserBalance ? 64 : 40
+ const { isEnabled: isArtistCoinsEnabled } = useFeatureFlag(
+ FeatureFlags.ARTIST_COINS
+ )
+
+ const { data: coinsData, isPending: coinsDataLoading } = useArtistCoins({
+ mint: [mint]
+ })
+
+ const coinData = coinsData?.[0] ?? null
+ const icon = coinData?.logoUri ?? ''
+
+ const onPress = useCallback(() => {
+ if (isArtistCoinsEnabled) {
+ navigation.navigate('CoinDetailsScreen', { mint })
+ } else {
+ navigation.navigate('AudioScreen')
+ }
+ }, [mint, navigation, isArtistCoinsEnabled])
+
+ const {
+ tokenBalanceFormatted: balance,
+ tokenDollarValue: dollarValue,
+ isTokenBalanceLoading,
+ isTokenPriceLoading
+ } = useFormattedTokenBalance(mint)
+
+ const isLoading =
+ isTokenBalanceLoading || isTokenPriceLoading || coinsDataLoading
+
+ const renderIcon = () => {
+ if (typeof icon === 'string') {
+ return (
+
+
+
+ )
+ }
+ return icon
+ }
+
+ return (
+
+
+
+ {isLoading ? : renderIcon()}
+
+ {isLoading ? (
+
+ ) : (
+ <>
+
+ {showUserBalance ? (
+
+ {balance}
+
+ ) : null}
+
+ {coinData?.ticker}
+
+ {/* TODO: Add coin value for AllCoinsScreen */}
+ {/* {!showUserBalance ? (
+
+ (Coin Value)
+
+ ) : null} */}
+
+ {showUserBalance ? (
+
+ {dollarValue}
+
+ ) : null}
+ >
+ )}
+
+
+
+
+
+ )
+}
diff --git a/packages/mobile/src/screens/wallet-screen/components/YourCoins.tsx b/packages/mobile/src/screens/wallet-screen/components/YourCoins.tsx
index c9121d57b69..137b9725eed 100644
--- a/packages/mobile/src/screens/wallet-screen/components/YourCoins.tsx
+++ b/packages/mobile/src/screens/wallet-screen/components/YourCoins.tsx
@@ -1,101 +1,114 @@
import React, { useCallback } from 'react'
-import { useFeatureFlag, useFormattedAudioBalance } from '@audius/common/hooks'
-import { buySellMessages as messages } from '@audius/common/messages'
+import { useCurrentUserId, useUserCoins } from '@audius/common/api'
+import { useFeatureFlag } from '@audius/common/hooks'
+import { buySellMessages } from '@audius/common/messages'
import { FeatureFlags } from '@audius/common/services'
+import type { CoinPairItem } from '@audius/common/store'
+import { TouchableOpacity } from 'react-native'
import {
+ Box,
Button,
+ Divider,
Flex,
IconCaretRight,
- IconTokenAUDIO,
Paper,
- Skeleton,
- Text,
- cornerRadius
+ Text
} from '@audius/harmony-native'
import { useNavigation } from 'app/hooks/useNavigation'
-const TokensHeader = () => {
- const navigation = useNavigation()
+import { CoinCard } from './CoinCard'
- const handleBuySellClick = useCallback(() => {
- navigation.navigate('BuySell', { initialTab: 'buy' })
- }, [navigation])
+const messages = {
+ ...buySellMessages,
+ findMoreCoins: 'Find More Coins',
+ exploreArtistCoins: 'Explore available artist coins on Audius.'
+}
+const TokensHeader = () => {
return (
-
+
{messages.yourCoins}
-
)
}
-export const YourCoins = () => {
+const FindMoreCoins = () => {
const navigation = useNavigation()
- const { isEnabled: isWalletUIBuySellEnabled } = useFeatureFlag(
- FeatureFlags.WALLET_UI_BUY_SELL
- )
-
- const {
- audioBalanceFormatted,
- audioDollarValue,
- isAudioBalanceLoading,
- isAudioPriceLoading
- } = useFormattedAudioBalance()
- const handleTokenClick = useCallback(() => {
- navigation.navigate('AudioScreen')
+ const handlePress = useCallback(() => {
+ navigation.navigate('AllCoinsScreen')
}, [navigation])
return (
-
- {isWalletUIBuySellEnabled ? : null}
+
-
-
-
-
- {isAudioBalanceLoading ? (
-
- ) : (
- <>
-
- {audioBalanceFormatted}
-
-
- {messages.audioTicker}
-
- >
- )}
-
- {isAudioPriceLoading ? (
-
- ) : (
-
- {audioDollarValue}
-
- )}
-
+
+
+ {messages.findMoreCoins}
+
+ {messages.exploreArtistCoins}
+
+ )
+}
+
+export const YourCoins = () => {
+ const { data: currentUserId } = useCurrentUserId()
+ const { isEnabled: isArtistCoinsEnabled } = useFeatureFlag(
+ FeatureFlags.ARTIST_COINS
+ )
+ const { isEnabled: isWalletUIBuySellEnabled } = useFeatureFlag(
+ FeatureFlags.WALLET_UI_BUY_SELL
+ )
+
+ const { data: artistCoins } = useUserCoins({
+ userId: currentUserId
+ })
+ const cards = isArtistCoinsEnabled
+ ? [...(artistCoins || []), 'find-more']
+ : (artistCoins?.slice(0, 1) ?? [])
+
+ return (
+
+
+
+ {cards.map((item: CoinPairItem) => (
+
+ {item === 'find-more' ? (
+
+ ) : (
+
+ )}
+
+
+ ))}
+
+ {isWalletUIBuySellEnabled ? (
+
+
+
+ ) : null}
)
}