Skip to content

Commit

Permalink
Add animated balance text
Browse files Browse the repository at this point in the history
  • Loading branch information
Perronef5 committed Oct 16, 2024
1 parent 9531e9e commit 3d89bee
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 44 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"react-error-boundary": "^4.0.13",
"react-i18next": "11.18.4",
"react-native": "0.74.5",
"react-native-animated-numbers": "^0.6.2",
"react-native-appstate-hook": "1.0.6",
"react-native-ble-plx": "2.0.3",
"react-native-charts-wrapper": "0.5.10",
Expand Down
89 changes: 60 additions & 29 deletions src/components/BalanceText.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,81 @@
import React, { memo, useMemo } from 'react'
import { TextProps } from '@shopify/restyle'
import { locale } from '@utils/i18n'
import { Theme } from '@theme/theme'
import React, { memo, useEffect, useMemo, useState } from 'react'
import AnimatedNumbers from 'react-native-animated-numbers'
import { useTextVariants } from '@theme/themeHooks'
import { Easing } from 'react-native-reanimated'
import { useBalance } from '@utils/Balance'
import Box from './Box'
import Text from './Text'

const BalanceText = ({
amount,
decimals,
...rest
}: {
amount: number | undefined
decimals: number | undefined
} & TextProps<Theme>) => {
const BalanceText = () => {
const { total: amount } = useBalance()

const textVariants = useTextVariants()
const integral = useMemo(() => Math.floor(amount || 0), [amount])
const [realAmount, setAmount] = useState(amount || 0)

useEffect(() => {
setTimeout(() => {
setAmount(amount || 0)
}, 3000)
}, [amount])

const firstFractional = useMemo(() => {
if (amount === undefined) return 0
const decimal = amount - integral
const fraction = decimal.toString().split('.')[1]
// Fraction with max length of decimals
const fractionWithMaxDecimals = fraction?.slice(0, 1)
return fraction ? Number(fractionWithMaxDecimals) : 0
}, [amount, integral])

const fractional = useMemo(() => {
if (amount === undefined) return ''
const secondFractional = useMemo(() => {
if (amount === undefined) return 0
const decimal = amount - integral
const fraction = decimal.toString().split('.')[1]
// Fraction with max length of decimals
const fractionWithMaxDecimals = fraction?.slice(0, decimals)
return fraction ? `.${fractionWithMaxDecimals}` : '.00'
}, [amount, integral, decimals])
const fractionWithMaxDecimals = fraction?.slice(1, 2)
return fraction ? Number(fractionWithMaxDecimals) : 0
}, [amount, integral])

return (
<Box>
<Box flexDirection="row" alignItems="flex-end">
<Text
paddingTop="2"
adjustsFontSizeToFit
variant="displayLgBold"
color={
amount === undefined ? 'text.placeholder-subtle' : 'primaryText'
}
{...rest}
>
{amount === undefined ? '-' : `$${integral.toLocaleString(locale)}`}
</Text>
<AnimatedNumbers
includeComma
animateToNumber={realAmount || 0}
fontStyle={{
fontSize: textVariants.displayLgBold.fontSize,
fontWeight: 'bold',
fontFamily: textVariants.displayLgBold.fontFamily,
}}
easing={Easing.elastic(1.2)}
animationDuration={1400}
/>
<Text
adjustsFontSizeToFit
variant="displayLgBold"
color="text.placeholder-subtle"
>
{fractional}
.
</Text>
<AnimatedNumbers
includeComma
animateToNumber={firstFractional || 0}
fontStyle={{
fontSize: textVariants.displayLgBold.fontSize,
fontWeight: 'bold',
fontFamily: textVariants.displayLgBold.fontFamily,
}}
/>
<AnimatedNumbers
includeComma
animateToNumber={secondFractional || 0}
fontStyle={{
fontSize: textVariants.displayLgBold.fontSize,
fontWeight: 'bold',
fontFamily: textVariants.displayLgBold.fontFamily,
}}
/>
</Box>
{/*
TODO: Bring this back once we are tracking balances on the wallet api
Expand Down
9 changes: 3 additions & 6 deletions src/features/account/AccountTokenBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata'
import { BoxProps } from '@shopify/restyle'
import { PublicKey } from '@solana/web3.js'
import { useAccountStorage } from '@storage/AccountStorageProvider'
import { Theme } from '@theme/theme'
import { TextVariant, Theme } from '@theme/theme'
import { IOT_SUB_DAO_KEY, MOBILE_SUB_DAO_KEY } from '@utils/constants'
import { getEscrowTokenAccount, humanReadable } from '@utils/solanaUtils'
import BN from 'bn.js'
Expand All @@ -17,11 +17,7 @@ import { useTranslation } from 'react-i18next'

type Props = {
mint: PublicKey
textVariant?:
| 'displayLgRegular'
| 'displayMdRegular'
| 'displaySmRegular'
| 'displaySmMedium'
textVariant?: TextVariant
showTicker?: boolean
} & BoxProps<Theme>

Expand Down Expand Up @@ -68,6 +64,7 @@ const AccountTokenBalance = ({
decimals,
loading: loadingOwned,
} = useOwnedAmount(wallet, mint)

const balanceStr =
typeof decimals !== 'undefined' && balance
? humanReadable(new BN(balance?.toString() || '0'), decimals)
Expand Down
1 change: 1 addition & 0 deletions src/features/account/TokenListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const TokenListItem = ({ mint }: Props) => {
decimals,
loading: loadingOwned,
} = useOwnedAmount(wallet, mint)

const { triggerImpact } = useHaptic()
const { json, symbol, loading } = useMetaplexMetadata(mint)
const mintStr = mint.toBase58()
Expand Down
73 changes: 65 additions & 8 deletions src/services/WalletService/pages/WalletPage/TokensScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Box from '@components/Box'
import React, { useCallback, useMemo } from 'react'
import React, { useCallback, useEffect, useMemo } from 'react'
import BalanceText from '@components/BalanceText'
import { FlatList, RefreshControl } from 'react-native'
import { AppState, FlatList, Platform, RefreshControl } from 'react-native'
import { useBalance } from '@utils/Balance'
import { DC_MINT, truthy } from '@helium/spl-utils'
import { DEFAULT_TOKENS, useVisibleTokens } from '@storage/TokensProvider'
Expand All @@ -16,7 +16,7 @@ import { useColors, useSpacing } from '@theme/themeHooks'
import WalletAlertBanner from '@components/WalletAlertBanner'
import { NavBarHeight } from '@components/ServiceNavBar'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useAsyncCallback } from 'react-async-hook'
import { useAsync, useAsyncCallback } from 'react-async-hook'
import { useAccountStorage } from '@storage/AccountStorageProvider'
import { useAppDispatch } from '@store/store'
import { syncTokenAccounts } from '@store/slices/balancesSlice'
Expand All @@ -30,22 +30,53 @@ import Text from '@components/Text'
import { useTranslation } from 'react-i18next'
import { getSortValue } from '@utils/solanaUtils'
import ScrollBox from '@components/ScrollBox'
import SharedGroupPreferences from 'react-native-shared-group-preferences'
import { useAppStorage } from '@storage/AppStorageProvider'
import { CSAccount } from '@storage/cloudStorage'
import { RootNavigationProp } from 'src/navigation/rootTypes'
import { useNotificationStorage } from '@storage/NotificationStorageProvider'
import { ServiceSheetNavigationProp } from '@services/serviceSheetTypes'
import { WalletNavigationProp } from './WalletPageNavigator'
import { useSolana } from '../../../../solana/SolanaProvider'

const TokensScreen = () => {
const widgetGroup = 'group.com.helium.mobile.wallet.widget'
const { anchorProvider, cluster } = useSolana()
const { tokenAccounts } = useBalance()
const { visibleTokens } = useVisibleTokens()
const colors = useColors()
const spacing = useSpacing()
const { bottom } = useSafeAreaInsets()
const { currentAccount } = useAccountStorage()
const { currentAccount, sortedAccounts, defaultAccountAddress } =
useAccountStorage()
const dispatch = useAppDispatch()
const { locked, currency } = useAppStorage()
const rootNav = useNavigation<RootNavigationProp>()
const cache = useAccountFetchCache()
const navigation = useNavigation<WalletNavigationProp>()
const serviceNav = useNavigation<ServiceSheetNavigationProp>()
const { openedNotification } = useNotificationStorage()
const { t } = useTranslation()
const { total } = useBalance()

// Hook that is used for helium balance widget.
useAsync(async () => {
if (Platform.OS === 'ios') {
const defaultAccount = sortedAccounts.find(
(account: CSAccount) => account.address === defaultAccountAddress,
)

await SharedGroupPreferences.setItem(
'heliumWalletWidgetKey',
{
defaultAccountAddress: defaultAccount?.solanaAddress,
defaultAccountAlias: defaultAccount?.alias,
currencyType: currency,
cluster,
},
widgetGroup,
)
}
}, [defaultAccountAddress, sortedAccounts])

const { loading: refetchingTokens, execute: refetchTokens } =
useAsyncCallback(async () => {
Expand Down Expand Up @@ -117,6 +148,32 @@ const TokensScreen = () => {
}
})

// Trigger refresh when the app comes into the foreground from the background
useEffect(() => {
const listener = AppState.addEventListener('change', (state) => {
if (state === 'active') {
refetchTokens()
}
})
return () => {
listener.remove()
}
}, [refetchTokens])

// if user signs out from lockscreen
useEffect(() => {
if (sortedAccounts.length === 0) {
rootNav.replace('OnboardingNavigator')
}
}, [rootNav, sortedAccounts.length])

useEffect(() => {
if (openedNotification && !locked) {
// navigate to notifications if we are coming from tapping a push
serviceNav.push('NotificationsService')
}
}, [serviceNav, openedNotification, locked])

const mints = useMemo(() => {
const taMints = tokenAccounts
?.filter(
Expand All @@ -141,11 +198,11 @@ const TokensScreen = () => {
<Box marginBottom="3xl">
<WalletAlertBanner />
<Box alignItems="center" width="100%">
<BalanceText amount={total} decimals={2} />
<BalanceText />
</Box>
</Box>
)
}, [total])
}, [])

const contentContainerStyle = useMemo(
() => ({
Expand Down Expand Up @@ -200,7 +257,7 @@ const TokensScreen = () => {

const keyExtractor = useCallback((mint: PublicKey) => {
const isGov = GovMints.some((m) => new PublicKey(m).equals(mint))
return `${mint.toBase58()}${isGov ? '-gov' : ''}`
return `${mint.toBase58()}${isGov ? '-gov' : '-spl'}`
}, [])

return (
Expand Down
1 change: 0 additions & 1 deletion src/utils/Balance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { humanReadable } from './solanaUtils'
import { useBalanceHistory } from './useBalanceHistory'
import { usePollTokenPrices } from './usePollTokenPrices'

export const ORACLE_POLL_INTERVAL = 1000 * 15 * 60 // 15 minutes
const useBalanceHook = () => {
const { currentAccount } = useAccountStorage()
const { cluster, anchorProvider } = useSolana()
Expand Down
11 changes: 11 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13717,6 +13717,7 @@ __metadata:
react-error-boundary: ^4.0.13
react-i18next: 11.18.4
react-native: 0.74.5
react-native-animated-numbers: ^0.6.2
react-native-appstate-hook: 1.0.6
react-native-ble-plx: 2.0.3
react-native-charts-wrapper: 0.5.10
Expand Down Expand Up @@ -19155,6 +19156,16 @@ __metadata:
languageName: node
linkType: hard

"react-native-animated-numbers@npm:^0.6.2":
version: 0.6.2
resolution: "react-native-animated-numbers@npm:0.6.2"
peerDependencies:
react: ">=16.8.0"
react-native: ">=0.59"
checksum: 4a7e56bd06ac67e282633eeee7e0bcb836dd2e10d45e1e755b208bbb15c5fd4e111f9990669dfa288e88928adfbfe70a67a5ca1d9e864da22c3c87ed4f12263d
languageName: node
linkType: hard

"react-native-appstate-hook@npm:1.0.6":
version: 1.0.6
resolution: "react-native-appstate-hook@npm:1.0.6"
Expand Down

0 comments on commit 3d89bee

Please sign in to comment.