From 5a9ea61acbcee10ec4d54a0b72f008c9acfe382f Mon Sep 17 00:00:00 2001 From: JellyWang <38491708+ezailWang@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:56:34 +0800 Subject: [PATCH] Fix/swap history loop & mob event opt ok-34383 ok-34377 ok-34378 (#6354) --- packages/kit-bg/src/services/ServiceSwap.ts | 34 ++++++--- .../Container/InAppNotification/index.tsx | 3 - .../src/states/jotai/contexts/swap/actions.ts | 72 ++++++------------- .../src/states/jotai/contexts/swap/atoms.ts | 4 +- .../Swap/components/SwapRefreshButton.tsx | 52 +++++++++++--- .../src/views/Swap/hooks/useSwapAccount.ts | 7 +- .../src/views/Swap/hooks/useSwapBuiltTx.ts | 4 +- .../kit/src/views/Swap/hooks/useSwapQuote.ts | 8 ++- .../kit/src/views/Swap/hooks/useSwapState.ts | 5 +- 9 files changed, 109 insertions(+), 80 deletions(-) diff --git a/packages/kit-bg/src/services/ServiceSwap.ts b/packages/kit-bg/src/services/ServiceSwap.ts index aa190e2b4ce..eecda257048 100644 --- a/packages/kit-bg/src/services/ServiceSwap.ts +++ b/packages/kit-bg/src/services/ServiceSwap.ts @@ -84,6 +84,8 @@ export default class ServiceSwap extends ServiceBase { private historyStateIntervals: Record> = {}; + private historyCurrentStateIntervalIds: string[] = []; + private historyStateIntervalCountMap: Record = {}; private _crossChainReceiveTxBlockNotificationMap: Record = @@ -1021,13 +1023,18 @@ export default class ServiceSwap extends ServiceBase { @backgroundMethod() async cleanHistoryStateIntervals(historyId?: string) { if (!historyId) { - Object.values(this.historyStateIntervals).forEach((interval) => { - clearInterval(interval); - }); - this.historyStateIntervals = {}; - this.historyStateIntervalCountMap = {}; + this.historyCurrentStateIntervalIds = []; + await Promise.all( + Object.keys(this.historyStateIntervals).map(async (id) => { + clearInterval(this.historyStateIntervals[id]); + delete this.historyStateIntervals[id]; + delete this.historyStateIntervalCountMap[id]; + }), + ); } else if (this.historyStateIntervals[historyId]) { clearInterval(this.historyStateIntervals[historyId]); + this.historyCurrentStateIntervalIds = + this.historyCurrentStateIntervalIds.filter((id) => id !== historyId); delete this.historyStateIntervals[historyId]; delete this.historyStateIntervalCountMap[historyId]; } @@ -1074,7 +1081,10 @@ export default class ServiceSwap extends ServiceBase { const error = e as { message?: string }; console.error('Swap History Status Fetch Error', error?.message); } finally { - if (enableInterval) { + if ( + enableInterval && + this.historyCurrentStateIntervalIds.includes(swapTxHistory.txInfo.txId) + ) { this.historyStateIntervalCountMap[swapTxHistory.txInfo.txId] = (this.historyStateIntervalCountMap[swapTxHistory.txInfo.txId] ?? 0) + 1; @@ -1101,10 +1111,16 @@ export default class ServiceSwap extends ServiceBase { item.status === ESwapTxHistoryStatus.PENDING || item.status === ESwapTxHistoryStatus.CANCELING, ); - await this.cleanHistoryStateIntervals(); - if (!statusPendingList.length) return; + const newHistoryStatePendingList = statusPendingList.filter( + (item) => !this.historyCurrentStateIntervalIds.includes(item.txInfo.txId), + ); + if (!newHistoryStatePendingList.length) return; await Promise.all( - statusPendingList.map(async (swapTxHistory) => { + newHistoryStatePendingList.map(async (swapTxHistory) => { + this.historyCurrentStateIntervalIds = [ + ...this.historyCurrentStateIntervalIds, + swapTxHistory.txInfo.txId, + ]; await this.swapHistoryStatusRunFetch(swapTxHistory); }), ); diff --git a/packages/kit/src/provider/Container/InAppNotification/index.tsx b/packages/kit/src/provider/Container/InAppNotification/index.tsx index f6110086b7b..f106ea12e97 100644 --- a/packages/kit/src/provider/Container/InAppNotification/index.tsx +++ b/packages/kit/src/provider/Container/InAppNotification/index.tsx @@ -9,9 +9,6 @@ const InAppNotification = () => { useEffect(() => { void backgroundApiProxy.serviceSwap.swapHistoryStatusFetchLoop(); - return () => { - void backgroundApiProxy.serviceSwap.cleanHistoryStateIntervals(); - }; }, [swapHistoryPendingList]); return null; }; diff --git a/packages/kit/src/states/jotai/contexts/swap/actions.ts b/packages/kit/src/states/jotai/contexts/swap/actions.ts index 99dca567ff1..9678621aaa8 100644 --- a/packages/kit/src/states/jotai/contexts/swap/actions.ts +++ b/packages/kit/src/states/jotai/contexts/swap/actions.ts @@ -318,7 +318,6 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { return; } await backgroundApiProxy.serviceSwap.setApprovingTransaction(undefined); - // let enableInterval = true; try { if (!loadingDelayEnable) { set(swapQuoteFetchingAtom(), true); @@ -336,13 +335,17 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { if (!loadingDelayEnable) { set(swapQuoteFetchingAtom(), false); set(swapQuoteListAtom(), res); - set(swapQuoteEventTotalCountAtom(), res.length); + set(swapQuoteEventTotalCountAtom(), { + count: res.length, + }); } else { set(swapSilenceQuoteLoading(), true); setTimeout(() => { set(swapSilenceQuoteLoading(), false); set(swapQuoteListAtom(), res); - set(swapQuoteEventTotalCountAtom(), res.length); + set(swapQuoteEventTotalCountAtom(), { + count: res.length, + }); }, 800); } } catch (e: any) { @@ -350,29 +353,8 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { if (e?.cause !== ESwapFetchCancelCause.SWAP_QUOTE_CANCEL) { set(swapQuoteFetchingAtom(), false); } - // } else { - // // enableInterval = false; - // } } finally { set(swapQuoteActionLockAtom(), (v) => ({ ...v, actionLock: false })); - // if (enableInterval) { - // const quoteIntervalCount = get(swapQuoteIntervalCountAtom()); - // if (quoteIntervalCount <= swapQuoteIntervalMaxCount) { - // void this.recoverQuoteInterval.call( - // set, - // { - // key: autoSlippage - // ? ESwapSlippageSegmentKey.AUTO - // : ESwapSlippageSegmentKey.CUSTOM, - // value: slippagePercentage, - // }, - // address, - // accountId, - // true, - // ); - // } - // set(swapQuoteIntervalCountAtom(), quoteIntervalCount + 1); - // } } }, ); @@ -391,8 +373,6 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { ) => { switch (event.type) { case 'open': { - // set(swapQuoteListAtom(), []); - // set(swapQuoteEventTotalCountAtom(), 0); break; } case 'message': { @@ -445,7 +425,10 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { (dataJson as ISwapQuoteEventInfo).totalQuoteCount === 0 ) { const { totalQuoteCount } = dataJson as ISwapQuoteEventInfo; - set(swapQuoteEventTotalCountAtom(), totalQuoteCount); + set(swapQuoteEventTotalCountAtom(), { + eventId: (dataJson as ISwapQuoteEventInfo).eventId, + count: totalQuoteCount, + }); if (totalQuoteCount === 0) { set(swapQuoteListAtom(), [ { @@ -461,7 +444,11 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { const swapAutoSlippageSuggestedValue = get( swapAutoSlippageSuggestedValueAtom(), ); - if (quoteResultData.data?.length) { + const quoteEventTotalCount = get(swapQuoteEventTotalCountAtom()); + if ( + quoteResultData.data?.length && + quoteEventTotalCount.eventId === quoteResultData.data[0].eventId + ) { const quoteResultsUpdateSlippage = quoteResultData.data.map( (quote) => { if ( @@ -518,9 +505,10 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { ?.filter( (q) => !q.eventId || - (q.eventId && + (quoteEventTotalCount.eventId && quoteResultData?.data?.[0]?.eventId && - q.eventId === quoteResultData.data[0].eventId), + quoteEventTotalCount.eventId === + quoteResultData.data[0].eventId), ); set(swapQuoteListAtom(), [...newQuoteList]); } @@ -531,22 +519,6 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { } case 'done': { set(swapQuoteActionLockAtom(), (v) => ({ ...v, actionLock: false })); - // const quoteIntervalCount = get(swapQuoteIntervalCountAtom()); - // if (quoteIntervalCount <= swapQuoteIntervalMaxCount) { - // void this.recoverQuoteInterval.call( - // set, - // { - // key: event.params.autoSlippage - // ? ESwapSlippageSegmentKey.AUTO - // : ESwapSlippageSegmentKey.CUSTOM, - // value: event.params.slippagePercentage, - // }, - // event.params.userAddress, - // event.accountId, - // true, - // ); - // } - // set(swapQuoteIntervalCountAtom(), quoteIntervalCount + 1); this.closeQuoteEvent(); break; } @@ -647,7 +619,9 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { ); } else { set(swapQuoteFetchingAtom(), false); - set(swapQuoteEventTotalCountAtom(), 0); + set(swapQuoteEventTotalCountAtom(), { + count: 0, + }); set(swapQuoteListAtom(), []); set(swapQuoteActionLockAtom(), (v) => ({ ...v, actionLock: false })); } @@ -896,8 +870,8 @@ class ContentJotaiActionsSwap extends ContextJotaiActionsBase { if ( !networks.length || !swapFromAddressInfo.accountInfo?.ready || - (quoteEventTotalCount > 0 && - quoteResultList.length < quoteEventTotalCount) + (quoteEventTotalCount.count > 0 && + quoteResultList.length < quoteEventTotalCount.count) ) { return; } diff --git a/packages/kit/src/states/jotai/contexts/swap/atoms.ts b/packages/kit/src/states/jotai/contexts/swap/atoms.ts index 1d5b1c10fff..b5485562695 100644 --- a/packages/kit/src/states/jotai/contexts/swap/atoms.ts +++ b/packages/kit/src/states/jotai/contexts/swap/atoms.ts @@ -156,7 +156,9 @@ export const { export const { atom: swapQuoteEventTotalCountAtom, use: useSwapQuoteEventTotalCountAtom, -} = contextAtom(0); +} = contextAtom<{ eventId?: string; count: number }>({ + count: 0, +}); export const { atom: swapShouldRefreshQuoteAtom, diff --git a/packages/kit/src/views/Swap/components/SwapRefreshButton.tsx b/packages/kit/src/views/Swap/components/SwapRefreshButton.tsx index 58c085d1831..6128de45b14 100644 --- a/packages/kit/src/views/Swap/components/SwapRefreshButton.tsx +++ b/packages/kit/src/views/Swap/components/SwapRefreshButton.tsx @@ -1,5 +1,6 @@ import { memo, useCallback, useEffect, useRef } from 'react'; +import { debounce } from 'lodash'; import { Animated } from 'react-native'; import { LottieView, XStack } from '@onekeyhq/components'; @@ -20,35 +21,56 @@ const SwapRefreshButton = ({ const themeVariant = useThemeVariant(); const lottieRef = useRef(null); const isFocused = useRouteIsFocused(); - const { isRefreshQuote } = useSwapActionState(); + const { isRefreshQuote, isLoading } = useSwapActionState(); const isRefreshQuoteRef = useRef(isRefreshQuote); if (isRefreshQuoteRef.current !== isRefreshQuote) { isRefreshQuoteRef.current = isRefreshQuote; } + const listenerRef = useRef(null); + const isFocusedRef = useRef(isFocused); + if (isFocusedRef.current !== isFocused) { + isFocusedRef.current = isFocused; + } + const loadingAnimRef = useRef(loadingAnim); + if (loadingAnimRef.current !== loadingAnim) { + loadingAnimRef.current = loadingAnim; + } + const refreshActionRef = useRef(refreshAction); + if (refreshActionRef.current !== refreshAction) { + refreshActionRef.current = refreshAction; + } + // eslint-disable-next-line react-hooks/exhaustive-deps const onRefresh = useCallback( - (manual?: boolean) => { - if (!isFocused) return; - loadingAnim.setValue(0); - Animated.timing(loadingAnim, { + debounce((manual?: boolean) => { + if (!isFocusedRef.current) return; + loadingAnimRef.current.setValue(0); + Animated.timing(loadingAnimRef.current, { toValue: -1, duration: 500, useNativeDriver: true, }).start((finished) => { if (finished) { - refreshAction(manual); + refreshActionRef.current(manual); } }); - }, - [isFocused, loadingAnim, refreshAction], + }, 10), + [], ); useEffect(() => { - const fn = processAnim.addListener(({ value }) => { + if (listenerRef.current) return; + listenerRef.current = processAnim.addListener(({ value }) => { + // mobile will trigger twice, so we need to debounce it , when max value if (value === swapRefreshInterval) { onRefresh(); } }); - return () => processAnim.removeListener(fn); + return () => { + if (listenerRef.current) { + processAnim.removeListener(listenerRef.current); + listenerRef.current = null; + } + }; }, [onRefresh, processAnim]); useEffect(() => { @@ -67,6 +89,16 @@ const SwapRefreshButton = ({ } }, [processAnim, isRefreshQuote]); + useEffect(() => { + if (isFocusedRef.current) { + if (isLoading) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + lottieRef.current?.reset(); + processAnimRef.current?.reset(); + } + } + }, [isLoading]); + useEffect(() => { if (isFocused) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access diff --git a/packages/kit/src/views/Swap/hooks/useSwapAccount.ts b/packages/kit/src/views/Swap/hooks/useSwapAccount.ts index 0c5c6e10502..d2be1aa42e0 100644 --- a/packages/kit/src/views/Swap/hooks/useSwapAccount.ts +++ b/packages/kit/src/views/Swap/hooks/useSwapAccount.ts @@ -292,9 +292,10 @@ export function useSwapRecipientAddressInfo(enable: boolean) { swapToAnotherAccountSwitchOn ) { if ( - getToNetWorkAddressFromAccountId?.result?.accountAddress && - getToNetWorkAddressFromAccountId?.result?.accountAddress !== - swapToAnotherAddressInfo.address && + ((getToNetWorkAddressFromAccountId?.result?.accountAddress && + getToNetWorkAddressFromAccountId?.result?.accountAddress !== + swapToAnotherAddressInfo.address) || + !getToNetWorkAddressFromAccountId?.result?.accountAddress) && swapToAnotherAddressInfo.networkId === currentQuoteRes?.toTokenInfo.networkId ) { diff --git a/packages/kit/src/views/Swap/hooks/useSwapBuiltTx.ts b/packages/kit/src/views/Swap/hooks/useSwapBuiltTx.ts index c6b87034e49..2c2c782b3c5 100644 --- a/packages/kit/src/views/Swap/hooks/useSwapBuiltTx.ts +++ b/packages/kit/src/views/Swap/hooks/useSwapBuiltTx.ts @@ -102,7 +102,9 @@ export function useSwapBuildTx() { if (data?.[0]) { setSwapFromTokenAmount(''); // send success, clear from token amount setSwapQuoteResultList([]); - setSwapQuoteEventTotalCount(0); + setSwapQuoteEventTotalCount({ + count: 0, + }); setSettings((v) => ({ // reset account switch for reset swap receive address ...v, diff --git a/packages/kit/src/views/Swap/hooks/useSwapQuote.ts b/packages/kit/src/views/Swap/hooks/useSwapQuote.ts index 4ab3fe6cbda..adb32e8fd55 100644 --- a/packages/kit/src/views/Swap/hooks/useSwapQuote.ts +++ b/packages/kit/src/views/Swap/hooks/useSwapQuote.ts @@ -275,13 +275,15 @@ export function useSwapQuote() { } else if (isHiddenModel) { if ( swapQuoteFetchingRef.current || - (swapQuoteEventTotalCountRef.current > 0 && + (swapQuoteEventTotalCountRef.current.count > 0 && swapQuoteResultListRef.current.length < - swapQuoteEventTotalCountRef.current) + swapQuoteEventTotalCountRef.current.count) ) { // reset tab quote data when swap modal is open and tab quote data is fetching closeQuoteEvent(); - setSwapQuoteEventTotalCount(0); + setSwapQuoteEventTotalCount({ + count: 0, + }); setSwapQuoteResultList([]); setFromTokenAmount(''); } diff --git a/packages/kit/src/views/Swap/hooks/useSwapState.ts b/packages/kit/src/views/Swap/hooks/useSwapState.ts index 78ec5184e27..35f8752ce6b 100644 --- a/packages/kit/src/views/Swap/hooks/useSwapState.ts +++ b/packages/kit/src/views/Swap/hooks/useSwapState.ts @@ -107,7 +107,10 @@ export function useSwapQuoteLoading() { export function useSwapQuoteEventFetching() { const [quoteEventTotalCount] = useSwapQuoteEventTotalCountAtom(); const [quoteResult] = useSwapQuoteListAtom(); - return quoteEventTotalCount > 0 && quoteResult.length < quoteEventTotalCount; + return ( + quoteEventTotalCount.count > 0 && + quoteResult.length < quoteEventTotalCount.count + ); } export function useSwapActionState() {