diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a85de240a..b761b4cf7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -861,9 +861,9 @@ SPEC CHECKSUMS: FBLazyVector: f1897022b53abf1469d6ad692ee2c69f57d967f3 FBReactNativeSpec: 627fd07f1b9d498c9fa572e76d7f1a6b1ee9a444 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 791fe035093b84822da7f0870421a25839ca7870 helium-react-native-sdk: 32c0a7e3abc733a7f3d291013b2db31475fc6980 - hermes-engine: 0784cadad14b011580615c496f77e0ae112eed75 + hermes-engine: 7a53ccac09146018a08239c5425625fdb79a6162 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 MapboxCommon: fdf7fd31c90b7b607cd9c63e37797f023c01d860 MapboxCoreMaps: 24270c7c6b8cb71819fc2f3c549db9620ee4d019 @@ -871,7 +871,7 @@ SPEC CHECKSUMS: MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d OneSignalXCFramework: 81ceac017a290f23793443323090cfbe888f74ea - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 85766c3226c7ec638f05ad7cb3cf6a268d6c4241 RCTRequired: bd6045fbd511da5efe6db89eecb21e4e36bd7cbf RCTTypeSafety: c06d9f906faa69dd1c88223204c3a24767725fd8 React: b9ea33557ef1372af247f95d110fbdea114ed3b2 diff --git a/src/features/account/AccountManageTokenListScreen.tsx b/src/features/account/AccountManageTokenListScreen.tsx index f286f7b47..4d48ada77 100644 --- a/src/features/account/AccountManageTokenListScreen.tsx +++ b/src/features/account/AccountManageTokenListScreen.tsx @@ -13,14 +13,20 @@ import { usePublicKey } from '@hooks/usePublicKey' import CheckBox from '@react-native-community/checkbox' import { useNavigation } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' import { useVisibleTokens } from '@storage/TokensProvider' import { useColors, useHitSlop } from '@theme/themeHooks' import { useBalance } from '@utils/Balance' import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { memo, useCallback, useMemo } from 'react' +import { useAsyncCallback } from 'react-async-hook' +import { RefreshControl } from 'react-native' import { FlatList } from 'react-native-gesture-handler' import { Edge } from 'react-native-safe-area-context' +import { useSolana } from '../../solana/SolanaProvider' +import { syncTokenAccounts } from '../../store/slices/balancesSlice' +import { useAppDispatch } from '../../store/store' import { HomeNavigationProp } from '../home/homeTypes' import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance' import { getSortValue } from './AccountTokenList' @@ -120,7 +126,18 @@ const AccountManageTokenListScreen: React.FC = () => { return getSortValue(b) - getSortValue(a) }) }, [tokenAccounts]) + const dispatch = useAppDispatch() + const { anchorProvider, cluster } = useSolana() + const { currentAccount } = useAccountStorage() + const colors = useColors() + const { loading: refetchingTokens, execute: refetchTokens } = + useAsyncCallback(async () => { + if (!anchorProvider || !currentAccount || !cluster) return + await dispatch( + syncTokenAccounts({ cluster, acct: currentAccount, anchorProvider }), + ) + }) const renderItem = useCallback( // eslint-disable-next-line react/no-unused-prop-types ({ index, item: token }: { index: number; item: string }) => { @@ -163,6 +180,15 @@ const AccountManageTokenListScreen: React.FC = () => { + } data={mints} renderItem={renderItem} keyExtractor={keyExtractor} diff --git a/src/features/account/AccountTokenList.tsx b/src/features/account/AccountTokenList.tsx index c8169c06d..b952ae756 100644 --- a/src/features/account/AccountTokenList.tsx +++ b/src/features/account/AccountTokenList.tsx @@ -3,15 +3,24 @@ import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' import { BottomSheetFlatList } from '@gorhom/bottom-sheet' import { BottomSheetFlatListProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetScrollable/types' +import { useAccountFetchCache } from '@helium/account-fetch-cache-hooks' import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' import { useNavigation } from '@react-navigation/native' +import { getAssociatedTokenAddressSync } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' -import { useVisibleTokens, DEFAULT_TOKENS } from '@storage/TokensProvider' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { DEFAULT_TOKENS, useVisibleTokens } from '@storage/TokensProvider' +import { useColors } from '@theme/themeHooks' import { useBalance } from '@utils/Balance' import { times } from 'lodash' -import React, { useCallback, useMemo } from 'react' +import React, { useCallback, useEffect, useMemo } from 'react' +import { useAsyncCallback } from 'react-async-hook' import { useTranslation } from 'react-i18next' +import { AppState, RefreshControl } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useSolana } from '../../solana/SolanaProvider' +import { syncTokenAccounts } from '../../store/slices/balancesSlice' +import { useAppDispatch } from '../../store/store' import { HomeNavigationProp } from '../home/homeTypes' import TokenListItem, { TokenSkeleton } from './TokenListItem' @@ -33,6 +42,46 @@ const AccountTokenList = ({ onLayout }: Props) => { const navigation = useNavigation() const { t } = useTranslation() const { visibleTokens } = useVisibleTokens() + const { currentAccount } = useAccountStorage() + const dispatch = useAppDispatch() + const { anchorProvider, cluster } = useSolana() + const colors = useColors() + const cache = useAccountFetchCache() + + const { loading: refetchingTokens, execute: refetchTokens } = + useAsyncCallback(async () => { + if (!anchorProvider || !currentAccount || !cluster) return + await dispatch( + syncTokenAccounts({ cluster, acct: currentAccount, anchorProvider }), + ) + await Promise.all( + [...visibleTokens].map((mintStr) => { + // Trigger a refetch on all visible token accounts + return cache.search( + getAssociatedTokenAddressSync( + new PublicKey(mintStr), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + new PublicKey(currentAccount!.solanaAddress!), + ), + undefined, + false, + true, + ) + }), + ) + }) + + // 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]) const onManageTokenList = useCallback(() => { navigation.navigate('AccountManageTokenListScreen') @@ -107,6 +156,17 @@ const AccountTokenList = ({ onLayout }: Props) => { onLayout={onLayout} data={mints} numColumns={2} + refreshControl={ + + } + refreshing={refetchingTokens} + onRefresh={refetchTokens} columnWrapperStyle={{ flexDirection: 'column', }}