From f8aa16eeaea2a820091b3c73ad3dd33c07cf7bd5 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Tue, 26 Nov 2024 18:56:20 -0800 Subject: [PATCH] fixup! Split data+logic for Portfolio vs Discover --- src/components/scenes/Staking/EarnScene.tsx | 160 +++++++++++++------- 1 file changed, 107 insertions(+), 53 deletions(-) diff --git a/src/components/scenes/Staking/EarnScene.tsx b/src/components/scenes/Staking/EarnScene.tsx index 4205c94355e..034d7311ca3 100644 --- a/src/components/scenes/Staking/EarnScene.tsx +++ b/src/components/scenes/Staking/EarnScene.tsx @@ -27,11 +27,10 @@ import { cacheStyles, Theme, useTheme } from '../../services/ThemeContext' interface Props extends EdgeAppSceneProps<'earnScene'> {} let USERNAME: string | undefined -let DISCOVER_ITEMS: DiscoverStakeInfo[] = [] -let PORTFOLIO_ITEMS: PortfolioStakeInfo[] = [] +let DISCOVER_MAP: DiscoverStakeMap = {} +let PORTFOLIO_MAP: PortfolioStakeMap = {} interface DiscoverStakeInfo { - pluginId: string stakePlugin: StakePlugin stakePolicy: StakePolicy } @@ -40,23 +39,49 @@ interface PortfolioStakeInfo extends DiscoverStakeInfo { walletStakeInfos: WalletStakeInfo[] } +interface DiscoverStakeMap { + [stakePolicyId: string]: DiscoverStakeInfo +} + +interface PortfolioStakeMap { + [stakePolicyId: string]: PortfolioStakeInfo +} + interface WalletStakeInfo { wallet: EdgeCurrencyWallet - isPositionOpen: boolean stakePosition: StakePosition } +/** Hook to ensure the UI updates on map changes, while retaining cached data + * functionality */ +const useStakeMaps = () => { + const [, forceUpdate] = React.useReducer(x => x + 1, 0) + + const updateMaps = React.useCallback((updates: () => void) => { + updates() + forceUpdate() + }, []) + + return { + discoverMap: DISCOVER_MAP, + portfolioMap: PORTFOLIO_MAP, + updateMaps + } +} + export const EarnScene = (props: Props) => { const { navigation } = props const theme = useTheme() const styles = getStyles(theme) + const { discoverMap, portfolioMap, updateMaps } = useStakeMaps() + const account = useSelector(state => state.core.account) if (USERNAME !== account.username) { // Reset local variable if user changes USERNAME = account.username - DISCOVER_ITEMS = [] - PORTFOLIO_ITEMS = [] + DISCOVER_MAP = {} + PORTFOLIO_MAP = {} } const currencyConfigMap = useSelector(state => state.core.account.currencyConfig) @@ -76,27 +101,32 @@ export const EarnScene = (props: Props) => { useAsyncEffect( async () => { const pluginIds = Object.keys(currencyConfigMap) - DISCOVER_ITEMS = [] for (const pluginId of pluginIds) { + setIsLoadingDiscover(true) + const isStakingSupported = SPECIAL_CURRENCY_INFO[pluginId]?.isStakingSupported === true && ENV.ENABLE_STAKING if (!isStakingSupported) continue const stakePlugins = await getStakePlugins(pluginId) - for (const stakePlugin of stakePlugins) { - const stakePolicies = stakePlugin.getPolicies({ pluginId }).filter(stakePolicy => !stakePolicy.deprecated) - - for (const stakePolicy of stakePolicies) { - DISCOVER_ITEMS.push({ - pluginId, - stakePlugin, - stakePolicy - }) + updateMaps(() => { + for (const stakePlugin of stakePlugins) { + for (const stakePolicy of stakePlugin.getPolicies({ pluginId }).filter(stakePolicy => !stakePolicy.deprecated)) { + DISCOVER_MAP[stakePolicy.stakePolicyId] = { + stakePlugin, + stakePolicy + } + } } - } + }) + + console.debug('getStakePlugins', pluginId, 'complete') + setIsLoadingDiscover(false) } + setIsLoadingDiscover(false) + return () => {} }, [], 'EarnScene Initialize Discover Items' @@ -107,46 +137,69 @@ export const EarnScene = (props: Props) => { async () => { if (!isLoadingDiscover || (isFocused && !isPrevFocused)) { setIsLoadingPortfolio(true) - PORTFOLIO_ITEMS = [] - - for (const discoverInfo of DISCOVER_ITEMS) { - const { pluginId, stakePlugin, stakePolicy } = discoverInfo - console.debug(`refreshing stake positions for ${pluginId} ${stakePolicy.stakePolicyId}`) - const matchingWallets = wallets.filter((wallet: EdgeCurrencyWallet) => wallet.currencyInfo.pluginId === pluginId) - const walletStakeInfos = [] - - for (const wallet of matchingWallets) { - try { - const stakePosition = await stakePlugin.fetchStakePosition({ - stakePolicyId: stakePolicy.stakePolicyId, - wallet, - account - }) - const allocations = getPositionAllocations(stakePosition) - const { staked, earned, unstaked } = allocations - const isPositionOpen = [...staked, ...earned, ...unstaked].some(positionAllocation => !zeroString(positionAllocation.nativeAmount)) - if (isPositionOpen) { - walletStakeInfos.push({ wallet, isPositionOpen, stakePosition }) + const controller = new AbortController() + const signal = controller.signal + + try { + const stakePolicyIds = Object.keys(discoverMap) + for (const stakePolicyId of stakePolicyIds) { + if (signal.aborted) break + + const discoverInfo = discoverMap[stakePolicyId] + const { stakePlugin, stakePolicy } = discoverInfo + + // Find matching wallets based on the first stake asset's pluginId + const pluginId = stakePolicy.stakeAssets[0].pluginId + const matchingWallets = wallets.filter((wallet: EdgeCurrencyWallet) => wallet.currencyInfo.pluginId === pluginId) + + const walletStakeInfoPromises = matchingWallets.map(async wallet => { + if (signal.aborted) return null + try { + const stakePosition = await stakePlugin.fetchStakePosition({ + stakePolicyId: stakePolicy.stakePolicyId, + wallet, + account + }) + const allocations = getPositionAllocations(stakePosition) + const { staked, earned, unstaked } = allocations + const isPositionOpen = [...staked, ...earned, ...unstaked].some(positionAllocation => !zeroString(positionAllocation.nativeAmount)) + + if (isPositionOpen) { + return { wallet, stakePosition } + } + } catch (e) { + showDevError(e) } - } catch (e) { - showDevError(e) + return null + }) + + if (!signal.aborted) { + const walletStakeInfos = (await Promise.all(walletStakeInfoPromises)).filter( + (info: WalletStakeInfo | null): info is WalletStakeInfo => info != null + ) + + updateMaps(() => { + PORTFOLIO_MAP[stakePolicyId] = { + ...discoverInfo, + walletStakeInfos + } + }) } } - - if (walletStakeInfos.length > 0) { - PORTFOLIO_ITEMS.push({ - ...discoverInfo, - walletStakeInfos - }) + } finally { + if (!signal.aborted) { + setIsLoadingPortfolio(false) + setIsPrevFocused(isFocused) } } - setIsLoadingPortfolio(false) + return () => { + controller.abort() + } } - setIsPrevFocused(isFocused) }, - [isFocused, isLoadingDiscover], + [isFocused, isLoadingDiscover, updateMaps], 'EarnScene Refresh Portfolio Data' ) @@ -202,6 +255,7 @@ export const EarnScene = (props: Props) => { const renderPortfolioItem = (portfolioStakeInfo: PortfolioStakeInfo, currencyInfo: EdgeCurrencyInfo) => { const { stakePlugin, stakePolicy, walletStakeInfos } = portfolioStakeInfo + if (walletStakeInfos.length === 0) return null const handlePress = async () => { let walletId: string | undefined @@ -215,9 +269,7 @@ export const EarnScene = (props: Props) => { stakePosition = existingStakePosition } else { // Select from wallets that have an open position - const allowedWalletIds = walletStakeInfos - .filter(walletStakeInfo => walletStakeInfo.isPositionOpen) - .map(walletStakePosition => walletStakePosition.wallet.id) + const allowedWalletIds = walletStakeInfos.map(walletStakePosition => walletStakePosition.wallet.id) const result = await Airship.show(bridge => ( { - {isPortfolioSelected && PORTFOLIO_ITEMS.map(info => renderPortfolioItem(info, currencyConfigMap[info.pluginId].currencyInfo))} - {!isPortfolioSelected && DISCOVER_ITEMS.map(info => renderDiscoverItem(info, currencyConfigMap[info.pluginId].currencyInfo))} + {isPortfolioSelected && + Object.values(portfolioMap).map(info => renderPortfolioItem(info, currencyConfigMap[info.stakePolicy.stakeAssets[0].pluginId].currencyInfo))} + {!isPortfolioSelected && + Object.values(discoverMap).map(info => renderDiscoverItem(info, currencyConfigMap[info.stakePolicy.stakeAssets[0].pluginId].currencyInfo))} {((isLoadingDiscover && !isPortfolioSelected) || (isLoadingPortfolio && isPortfolioSelected)) && ( )}