diff --git a/src/App.js b/src/App.js index 114473f3f37..6ba0ee90353 100644 --- a/src/App.js +++ b/src/App.js @@ -57,6 +57,7 @@ import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { RemotePromoSheetProvider } from '@/components/remote-promo-sheet/RemotePromoSheetProvider'; import { RemoteCardProvider } from '@/components/cards/remote-cards'; import { initializeRemoteConfig } from '@/model/remoteConfig'; +import { SwapProvider } from './__swaps__/screens/Swap/providers/swap-provider'; if (__DEV__) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); @@ -223,14 +224,16 @@ class OldApp extends Component { {this.state.initialRoute && ( - - - - - - - - + + + + + + + + + + )} diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index 0a42fc22806..0093774c548 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -20,7 +20,7 @@ import { SliderAndKeyboard } from '@/__swaps__/screens/Swap/components/SliderAnd import { SwapBottomPanel } from '@/__swaps__/screens/Swap/components/SwapBottomPanel'; import { SwapWarning } from './components/SwapWarning'; import { useSwapContext } from './providers/swap-provider'; -import { UserAssetsSync } from './components/UserAssetsSync'; +import { CleanupAssetsOnUnmount } from './components/CleanupAssetsOnUnmount'; /** README * This prototype is largely driven by Reanimated and Gesture Handler, which @@ -60,7 +60,6 @@ import { UserAssetsSync } from './components/UserAssetsSync'; */ export function SwapScreen() { - const { AnimatedSwapStyles } = useSwapContext(); return ( @@ -69,29 +68,42 @@ export function SwapScreen() { - - - - - - - - + + - - {/* NOTE: The components below render null and are solely for keeping react-query and Zustand in sync */} - + + ); } +const SliderAndKeyboardAndBottomControls = () => { + const { AnimatedSwapStyles } = useSwapContext(); + return ( + + + + + ); +}; + +const ExchangeRateBubbleAndWarning = () => { + const { AnimatedSwapStyles } = useSwapContext(); + return ( + + + + + ); +}; + export const styles = StyleSheet.create({ rootViewBackground: { borderRadius: IS_ANDROID ? 20 : ScreenCornerRadius, diff --git a/src/__swaps__/screens/Swap/components/CleanupAssetsOnUnmount.tsx b/src/__swaps__/screens/Swap/components/CleanupAssetsOnUnmount.tsx new file mode 100644 index 00000000000..31dbf35b475 --- /dev/null +++ b/src/__swaps__/screens/Swap/components/CleanupAssetsOnUnmount.tsx @@ -0,0 +1,6 @@ +import { useCleanupOnExit } from '../hooks/useCleanupOnExit'; + +export const CleanupAssetsOnUnmount = () => { + useCleanupOnExit(); + return null; +}; diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 392c632d1f0..8dde9a62fa9 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -112,7 +112,7 @@ function EstimatedArrivalTime() { export function ReviewPanel() { const { isDarkMode } = useColorMode(); - const { configProgress, SwapSettings, SwapInputController, internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); + const { configProgress, SwapSettings, SwapInputsController, internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const unknown = i18n.t(i18n.l.swap.unknown); @@ -121,11 +121,11 @@ export function ReviewPanel() { ); const minimumReceived = useDerivedValue(() => { - if (!SwapInputController.formattedOutputAmount.value || !internalSelectedOutputAsset.value?.symbol) { + if (!SwapInputsController.formattedOutputAmount.value || !internalSelectedOutputAsset.value?.symbol) { return unknown; } - return `${SwapInputController.formattedOutputAmount.value} ${internalSelectedOutputAsset.value.symbol}`; + return `${SwapInputsController.formattedOutputAmount.value} ${internalSelectedOutputAsset.value.symbol}`; }); const handleDecrementSlippage = () => { diff --git a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx index 6ca074c82a6..4be63c6b14a 100644 --- a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx @@ -42,7 +42,7 @@ function SwapInputActionButton() { } function SwapInputAmount() { - const { focusedInput, SwapTextStyles, SwapInputController, AnimatedSwapStyles } = useSwapContext(); + const { focusedInput, SwapTextStyles, SwapInputsController, AnimatedSwapStyles } = useSwapContext(); return ( @@ -106,7 +106,7 @@ export function SwapInputAsset() { inputProgress, AnimatedSwapStyles, SwapTextStyles, - SwapInputController, + SwapInputsController, internalSelectedInputAsset, SwapNavigation, } = useSwapContext(); @@ -129,7 +129,7 @@ export function SwapInputAsset() { numberOfLines={1} size="17pt" style={SwapTextStyles.inputNativeValueStyle} - text={SwapInputController.formattedInputNativeValue} + text={SwapInputsController.formattedInputNativeValue} weight="heavy" /> diff --git a/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx b/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx index 1768701ee61..64105c8c476 100644 --- a/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx +++ b/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx @@ -32,36 +32,36 @@ type numberPadCharacter = number | 'backspace' | '.'; export const SwapNumberPad = () => { const { isDarkMode } = useColorMode(); - const { focusedInput, isQuoteStale, SwapInputController, configProgress } = useSwapContext(); + const { focusedInput, isQuoteStale, SwapInputsController, configProgress } = useSwapContext(); const longPressTimer = useSharedValue(0); const addNumber = (number?: number) => { 'worklet'; // immediately stop the quote fetching interval - SwapInputController.quoteFetchingInterval.stop(); + SwapInputsController.quoteFetchingInterval.stop(); isQuoteStale.value = 1; const inputKey = focusedInput.value; - if (SwapInputController.inputMethod.value !== inputKey) { - SwapInputController.inputMethod.value = inputKey; + if (SwapInputsController.inputMethod.value !== inputKey) { + SwapInputsController.inputMethod.value = inputKey; - if (typeof SwapInputController.inputValues.value[inputKey] === 'number') { - SwapInputController.inputValues.modify(value => { + if (typeof SwapInputsController.inputValues.value[inputKey] === 'number') { + SwapInputsController.inputValues.modify(value => { return { ...value, [inputKey]: inputKey === 'inputAmount' - ? stripCommas(SwapInputController.formattedInputAmount.value) - : stripCommas(SwapInputController.formattedOutputAmount.value), + ? stripCommas(SwapInputsController.formattedInputAmount.value) + : stripCommas(SwapInputsController.formattedOutputAmount.value), }; }); } } - const currentValue = SwapInputController.inputValues.value[inputKey]; + const currentValue = SwapInputsController.inputValues.value[inputKey]; const newValue = currentValue === 0 || currentValue === '0' ? `${number}` : `${currentValue}${number}`; - SwapInputController.inputValues.modify(value => { + SwapInputsController.inputValues.modify(value => { return { ...value, [inputKey]: newValue, @@ -72,25 +72,25 @@ export const SwapNumberPad = () => { const addDecimalPoint = () => { 'worklet'; const inputKey = focusedInput.value; - const currentValue = SwapInputController.inputValues.value[inputKey].toString(); + const currentValue = SwapInputsController.inputValues.value[inputKey].toString(); if (!currentValue.includes('.')) { - if (SwapInputController.inputMethod.value !== inputKey) { - SwapInputController.inputMethod.value = inputKey; + if (SwapInputsController.inputMethod.value !== inputKey) { + SwapInputsController.inputMethod.value = inputKey; - SwapInputController.inputValues.modify(values => { + SwapInputsController.inputValues.modify(values => { return { ...values, [inputKey]: inputKey === 'inputAmount' - ? stripCommas(SwapInputController.formattedInputAmount.value) - : stripCommas(SwapInputController.formattedOutputAmount.value), + ? stripCommas(SwapInputsController.formattedInputAmount.value) + : stripCommas(SwapInputsController.formattedOutputAmount.value), }; }); } const newValue = `${currentValue}.`; - SwapInputController.inputValues.modify(values => { + SwapInputsController.inputValues.modify(values => { return { ...values, [inputKey]: newValue, @@ -102,26 +102,25 @@ export const SwapNumberPad = () => { const deleteLastCharacter = () => { 'worklet'; const inputKey = focusedInput.value; - isQuoteStale.value = 1; - - if (SwapInputController.inputMethod.value !== inputKey) { - SwapInputController.inputMethod.value = inputKey; + if (SwapInputsController.inputMethod.value !== inputKey) { + SwapInputsController.inputMethod.value = inputKey; + isQuoteStale.value = 1; - SwapInputController.inputValues.modify(values => { + SwapInputsController.inputValues.modify(values => { return { ...values, [inputKey]: inputKey === 'inputAmount' - ? stripCommas(SwapInputController.formattedInputAmount.value) - : stripCommas(SwapInputController.formattedOutputAmount.value), + ? stripCommas(SwapInputsController.formattedInputAmount.value) + : stripCommas(SwapInputsController.formattedOutputAmount.value), }; }); } - const currentValue = SwapInputController.inputValues.value[inputKey].toString(); + const currentValue = SwapInputsController.inputValues.value[inputKey].toString(); // Handle deletion, ensuring a placeholder zero remains if the entire number is deleted const newValue = currentValue.length > 1 ? currentValue.slice(0, -1) : 0; if (newValue === 0) { - SwapInputController.inputValues.modify(values => { + SwapInputsController.inputValues.modify(values => { return { ...values, inputAmount: 0, @@ -131,7 +130,7 @@ export const SwapNumberPad = () => { }; }); } else { - SwapInputController.inputValues.modify(values => { + SwapInputsController.inputValues.modify(values => { return { ...values, [inputKey]: newValue, diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index 8b3ebf05028..1f39303eb74 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -42,7 +42,7 @@ function SwapOutputActionButton() { } function SwapOutputAmount() { - const { focusedInput, SwapTextStyles, SwapInputController, AnimatedSwapStyles } = useSwapContext(); + const { focusedInput, SwapTextStyles, SwapInputsController, AnimatedSwapStyles } = useSwapContext(); return ( @@ -106,7 +106,7 @@ export function SwapOutputAsset() { inputProgress, AnimatedSwapStyles, SwapTextStyles, - SwapInputController, + SwapInputsController, internalSelectedOutputAsset, SwapNavigation, } = useSwapContext(); @@ -129,7 +129,7 @@ export function SwapOutputAsset() { numberOfLines={1} size="17pt" style={SwapTextStyles.outputNativeValueStyle} - text={SwapInputController.formattedOutputNativeValue} + text={SwapInputsController.formattedOutputNativeValue} weight="heavy" /> diff --git a/src/__swaps__/screens/Swap/components/SwapSlider.tsx b/src/__swaps__/screens/Swap/components/SwapSlider.tsx index 87fca3e4789..fb97c6b8193 100644 --- a/src/__swaps__/screens/Swap/components/SwapSlider.tsx +++ b/src/__swaps__/screens/Swap/components/SwapSlider.tsx @@ -56,7 +56,7 @@ export const SwapSlider = ({ const { isDarkMode } = useColorMode(); const { AnimatedSwapStyles, - SwapInputController, + SwapInputsController, internalSelectedInputAsset, internalSelectedOutputAsset, sliderXPosition, @@ -77,9 +77,9 @@ export const SwapSlider = ({ // Callback function to handle percentage change once slider is at rest const onChangeWrapper = useCallback( (percentage: number, setStale = true) => { - SwapInputController.onChangedPercentage(percentage, setStale); + SwapInputsController.onChangedPercentage(percentage, setStale); }, - [SwapInputController] + [SwapInputsController] ); const colors = useDerivedValue(() => ({ @@ -115,7 +115,7 @@ export const SwapSlider = ({ useAnimatedReaction( () => ({ x: sliderXPosition.value }), (current, previous) => { - if (current !== previous && SwapInputController.inputMethod.value === 'slider') { + if (current !== previous && SwapInputsController.inputMethod.value === 'slider') { if (current.x >= width * 0.995 && previous?.x && previous?.x < width * 0.995) { runOnJS(triggerHapticFeedback)('impactMedium'); } @@ -130,7 +130,7 @@ export const SwapSlider = ({ const onPressDown = useAnimatedGestureHandler({ onStart: () => { sliderPressProgress.value = withSpring(1, sliderConfig); - SwapInputController.quoteFetchingInterval.stop(); + SwapInputsController.quoteFetchingInterval.stop(); }, onActive: () => { sliderPressProgress.value = withSpring(SLIDER_COLLAPSED_HEIGHT / height, sliderConfig); @@ -141,9 +141,9 @@ export const SwapSlider = ({ onStart: (_, ctx: { startX: number }) => { ctx.startX = sliderXPosition.value; sliderPressProgress.value = withSpring(1, sliderConfig); - SwapInputController.inputMethod.value = 'slider'; + SwapInputsController.inputMethod.value = 'slider'; - // On Android, for some reason waiting until onActive to set SwapInputController.isQuoteStale.value = 1 + // On Android, for some reason waiting until onActive to set SwapInputsController.isQuoteStale.value = 1 // causes the outputAmount text color to break. It's preferable to set it in // onActive, so we're setting it in onStart for Android only. It's possible that // migrating this handler to the RNGH v2 API will remove the need for this. @@ -193,7 +193,7 @@ export const SwapSlider = ({ } else if (xPercentage.value < 0.005) { runOnJS(onChangeWrapper)(0); sliderXPosition.value = withSpring(0, snappySpringConfig); - // SwapInputController.isQuoteStale.value = 0; + // SwapInputsController.isQuoteStale.value = 0; } else { runOnJS(onChangeWrapper)(xPercentage.value); } @@ -245,7 +245,7 @@ export const SwapSlider = ({ sliderXPosition.value = withSpring(nextSnapPoint, snappierSpringConfig); // if (nextSnapPoint === 0) { - // SwapInputController.isQuoteStale.value = 0; + // SwapInputsController.isQuoteStale.value = 0; // } } else { // For low-velocity drags, skip snap points and let the slider rest at current position @@ -331,9 +331,9 @@ export const SwapSlider = ({ const percentageTextStyle = useAnimatedStyle(() => { const isAdjustingInputValue = - SwapInputController.inputMethod.value === 'inputAmount' || SwapInputController.inputMethod.value === 'inputNativeValue'; + SwapInputsController.inputMethod.value === 'inputAmount' || SwapInputsController.inputMethod.value === 'inputNativeValue'; const isAdjustingOutputValue = - SwapInputController.inputMethod.value === 'outputAmount' || SwapInputController.inputMethod.value === 'outputNativeValue'; + SwapInputsController.inputMethod.value === 'outputAmount' || SwapInputsController.inputMethod.value === 'outputNativeValue'; const isStale = isQuoteStale.value === 1 && (isAdjustingInputValue || isAdjustingOutputValue) ? 1 : 0; @@ -345,7 +345,7 @@ export const SwapSlider = ({ isStale, [0, 1], [ - (SwapInputController.inputMethod.value === 'slider' ? xPercentage.value < 0.005 : sliderXPosition.value === 0) + (SwapInputsController.inputMethod.value === 'slider' ? xPercentage.value < 0.005 : sliderXPosition.value === 0) ? zeroAmountColor : labelSecondary, zeroAmountColor, @@ -394,8 +394,8 @@ export const SwapSlider = ({ onPress={() => { 'worklet'; - SwapInputController.quoteFetchingInterval.stop(); - SwapInputController.inputMethod.value = 'slider'; + SwapInputsController.quoteFetchingInterval.stop(); + SwapInputsController.inputMethod.value = 'slider'; setTimeout(() => { sliderXPosition.value = withSpring(width, snappySpringConfig); onChangeWrapper(1); diff --git a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx index 658900de040..1638ab8e31c 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx @@ -49,10 +49,10 @@ export const ChainSelection = ({ allText, output }: ChainSelectionProps) => { const chainName = useSharedValue( output - ? chainNameFromChainIdWorklet(selectedOutputChainId.value) + ? chainNameForChainIdWithMainnetSubstitutionWorklet(selectedOutputChainId.value) : initialFilter === 'all' ? allText - : chainNameFromChainIdWorklet(initialFilter as ChainId) + : chainNameForChainIdWithMainnetSubstitutionWorklet(initialFilter as ChainId) ); useAnimatedReaction( @@ -74,12 +74,10 @@ export const ChainSelection = ({ allText, output }: ChainSelectionProps) => { userAssetsStore.setState({ filter: actionKey === 'all' ? 'all' : (Number(actionKey) as ChainId), }); - runOnUI(() => { - chainName.value = actionKey === 'all' ? allText : chainNameForChainIdWithMainnetSubstitutionWorklet(Number(actionKey) as ChainId); - }); + chainName.value = actionKey === 'all' ? allText : chainNameForChainIdWithMainnetSubstitutionWorklet(Number(actionKey) as ChainId); } }, - [allText, chainName, output, selectedOutputChainId] + [allText, chainName, output, setSelectedOutputChainId] ); const menuConfig = useMemo(() => { diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx index 67a8837b0d8..4d697369120 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx @@ -128,9 +128,8 @@ export const TokenToBuySection = ({ section }: { section: AssetToBuySection }) = - {/* TODO: fix this from causing the UI to be completely slow... */} } keyExtractor={item => `${item.uniqueId}-${section.id}`} renderItem={({ item }) => ( diff --git a/src/__swaps__/screens/Swap/components/UserAssetsSync.tsx b/src/__swaps__/screens/Swap/components/UserAssetsSync.tsx index 6784ae0d04c..f4ce00b882f 100644 --- a/src/__swaps__/screens/Swap/components/UserAssetsSync.tsx +++ b/src/__swaps__/screens/Swap/components/UserAssetsSync.tsx @@ -5,10 +5,20 @@ import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/sc import { Hex } from 'viem'; import { userAssetsStore } from '@/state/assets/userAssets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; +import { useRoute } from '@react-navigation/native'; +import Routes from '@/navigation/routesNames'; +import { parseSearchAsset } from '@/__swaps__/utils/assets'; +import { SearchAsset } from '@/__swaps__/types/search'; +import { useSwapContext } from '../providers/swap-provider'; +import { SwapAssetType } from '@/__swaps__/types/swap'; export const UserAssetsSync = () => { + const { name } = useRoute(); + const { setAsset } = useSwapContext(); const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); + // TODO: Should we setAsset here as well? + // probably only if they aren't on the SWAP screen... useUserAssets( { address: currentAddress as Hex, @@ -20,12 +30,24 @@ export const UserAssetsSync = () => { data, selector: selectUserAssetsList, }), - onSuccess: data => { + onSuccess: (data = []) => { userAssetsStore.setState({ userAssetsById: new Set(data.map(d => d.uniqueId)), userAssets: new Map(data.map(d => [d.uniqueId, d as ParsedSearchAsset])), }); + + if (name !== Routes.SWAP) { + const [firstAsset] = data; + const parsedAsset = parseSearchAsset({ + assetWithPrice: undefined, + searchAsset: firstAsset as unknown as SearchAsset, // NOTE: We don't really care about this since it's a userAsset + userAsset: firstAsset, + }); + + setAsset({ asset: parsedAsset, type: SwapAssetType.inputAsset }); + } }, + enabled: !!currentAddress, } ); diff --git a/src/__swaps__/screens/Swap/hooks/useCleanupOnExit.ts b/src/__swaps__/screens/Swap/hooks/useCleanupOnExit.ts new file mode 100644 index 00000000000..c3b2b259ddf --- /dev/null +++ b/src/__swaps__/screens/Swap/hooks/useCleanupOnExit.ts @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; +import { useSwapContext } from '../providers/swap-provider'; + +export const useCleanupOnExit = () => { + const { reset } = useSwapContext(); + + useEffect(() => { + return () => { + reset(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +}; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index df5d5cdfdae..60c4cc8bae5 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -143,6 +143,7 @@ export function useSwapInputsController({ }); const formattedOutputNativeValue = useDerivedValue(() => { + console.log(inputMethod.value, inputValues.value.outputNativeValue); if ((inputMethod.value === 'slider' && percentageToSwap.value === 0) || !inputValues.value.outputNativeValue) { return '$0.00'; } diff --git a/src/__swaps__/screens/Swap/hooks/useSwapNavigation.ts b/src/__swaps__/screens/Swap/hooks/useSwapNavigation.ts index 567f71737ad..42d6cdcef9a 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapNavigation.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapNavigation.ts @@ -12,12 +12,12 @@ export const enum NavigationSteps { } export function useSwapNavigation({ - SwapInputController, + SwapInputsController, inputProgress, outputProgress, configProgress, }: { - SwapInputController: ReturnType; + SwapInputsController: ReturnType; inputProgress: SharedValue; outputProgress: SharedValue; configProgress: SharedValue; @@ -71,7 +71,7 @@ export function useSwapNavigation({ 'worklet'; handleDismissReview(); handleDismissGas(); - SwapInputController.fetchQuoteAndAssetPrices(); + SwapInputsController.fetchQuoteAndAssetPrices(); if (inputProgress.value === NavigationSteps.TOKEN_LIST_FOCUSED) { inputProgress.value = NavigationSteps.INPUT_ELEMENT_FOCUSED; @@ -85,7 +85,7 @@ export function useSwapNavigation({ if (outputProgress.value === NavigationSteps.SEARCH_FOCUSED) { outputProgress.value = NavigationSteps.TOKEN_LIST_FOCUSED; } - }, [SwapInputController, handleDismissGas, handleDismissReview, inputProgress, outputProgress]); + }, [SwapInputsController, handleDismissGas, handleDismissReview, inputProgress, outputProgress]); const handleFocusInputSearch = useCallback(() => { 'worklet'; @@ -111,7 +111,7 @@ export function useSwapNavigation({ 'worklet'; handleDismissReview(); handleDismissGas(); - SwapInputController.quoteFetchingInterval.stop(); + SwapInputsController.quoteFetchingInterval.stop(); if (inputProgress.value === NavigationSteps.INPUT_ELEMENT_FOCUSED) { console.log('showing token list'); @@ -120,13 +120,13 @@ export function useSwapNavigation({ } else { inputProgress.value = NavigationSteps.INPUT_ELEMENT_FOCUSED; } - }, [handleDismissReview, handleDismissGas, inputProgress, outputProgress, SwapInputController]); + }, [handleDismissReview, handleDismissGas, inputProgress, outputProgress, SwapInputsController]); const handleOutputPress = useCallback(() => { 'worklet'; handleDismissReview(); handleDismissGas(); - SwapInputController.quoteFetchingInterval.stop(); + SwapInputsController.quoteFetchingInterval.stop(); if (outputProgress.value === NavigationSteps.INPUT_ELEMENT_FOCUSED) { outputProgress.value = NavigationSteps.TOKEN_LIST_FOCUSED; @@ -134,7 +134,7 @@ export function useSwapNavigation({ } else { outputProgress.value = NavigationSteps.INPUT_ELEMENT_FOCUSED; } - }, [SwapInputController, handleDismissReview, handleDismissGas, inputProgress, outputProgress]); + }, [SwapInputsController, handleDismissReview, handleDismissGas, inputProgress, outputProgress]); const handleSwapAction = useCallback(() => { 'worklet'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts index 4b65d56c23f..ffe39945345 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts @@ -27,7 +27,7 @@ import { useSwapInputsController } from './useSwapInputsController'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; export function useSwapTextStyles({ - SwapInputController, + SwapInputsController, internalSelectedInputAsset, internalSelectedOutputAsset, isQuoteStale, @@ -36,7 +36,7 @@ export function useSwapTextStyles({ outputProgress, sliderPressProgress, }: { - SwapInputController: ReturnType; + SwapInputsController: ReturnType; internalSelectedInputAsset: SharedValue; internalSelectedOutputAsset: SharedValue; isQuoteStale: SharedValue; @@ -53,15 +53,15 @@ export function useSwapTextStyles({ const isInputStale = useDerivedValue(() => { const isAdjustingOutputValue = - SwapInputController.inputMethod.value === 'outputAmount' || SwapInputController.inputMethod.value === 'outputNativeValue'; + SwapInputsController.inputMethod.value === 'outputAmount' || SwapInputsController.inputMethod.value === 'outputNativeValue'; return isQuoteStale.value === 1 && isAdjustingOutputValue ? 1 : 0; }); const isOutputStale = useDerivedValue(() => { const isAdjustingInputValue = - SwapInputController.inputMethod.value === 'inputAmount' || - SwapInputController.inputMethod.value === 'inputNativeValue' || - SwapInputController.inputMethod.value === 'slider'; + SwapInputsController.inputMethod.value === 'inputAmount' || + SwapInputsController.inputMethod.value === 'inputNativeValue' || + SwapInputsController.inputMethod.value === 'slider'; return isQuoteStale.value === 1 && isAdjustingInputValue ? 1 : 0; }); @@ -73,9 +73,9 @@ export function useSwapTextStyles({ const inputAmountTextStyle = useAnimatedStyle(() => { const isInputZero = - (SwapInputController.inputValues.value.inputAmount === 0 && SwapInputController.inputMethod.value !== 'slider') || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0); - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + (SwapInputsController.inputValues.value.inputAmount === 0 && SwapInputsController.inputMethod.value !== 'slider') || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0); + const isOutputZero = Number(SwapInputsController.inputValues.value.outputAmount) === 0; // eslint-disable-next-line no-nested-ternary const zeroOrAssetColor = isInputZero @@ -95,9 +95,9 @@ export function useSwapTextStyles({ const inputNativeValueStyle = useAnimatedStyle(() => { const isInputZero = - Number(SwapInputController.inputValues.value.inputAmount) === 0 || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0); - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + Number(SwapInputsController.inputValues.value.inputAmount) === 0 || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0); + const isOutputZero = Number(SwapInputsController.inputValues.value.outputAmount) === 0; const zeroOrColor = isInputZero ? zeroAmountColor : labelTertiary; const opacity = isInputStale.value !== 1 || (isInputZero && isOutputZero) ? withSpring(1, sliderConfig) : pulsingOpacity.value; @@ -110,11 +110,11 @@ export function useSwapTextStyles({ const outputAmountTextStyle = useAnimatedStyle(() => { const isInputZero = - Number(SwapInputController.inputValues.value.inputAmount) === 0 || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0); + Number(SwapInputsController.inputValues.value.inputAmount) === 0 || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0); const isOutputZero = - (SwapInputController.inputValues.value.outputAmount === 0 && SwapInputController.inputMethod.value !== 'slider') || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.outputAmount) === 0); + (SwapInputsController.inputValues.value.outputAmount === 0 && SwapInputsController.inputMethod.value !== 'slider') || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.outputAmount) === 0); // eslint-disable-next-line no-nested-ternary const zeroOrAssetColor = isOutputZero @@ -134,9 +134,9 @@ export function useSwapTextStyles({ const outputNativeValueStyle = useAnimatedStyle(() => { const isInputZero = - Number(SwapInputController.inputValues.value.inputAmount) === 0 || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0); - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + Number(SwapInputsController.inputValues.value.inputAmount) === 0 || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0); + const isOutputZero = Number(SwapInputsController.inputValues.value.outputAmount) === 0; const zeroOrColor = isOutputZero ? zeroAmountColor : labelTertiary; const opacity = isOutputStale.value !== 1 || (isInputZero && isOutputZero) ? withSpring(1, sliderConfig) : pulsingOpacity.value; @@ -153,8 +153,8 @@ export function useSwapTextStyles({ focusedInput.value === 'inputAmount' && inputProgress.value === 0 && outputProgress.value === 0 && - (SwapInputController.inputMethod.value !== 'slider' || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0) || + (SwapInputsController.inputMethod.value !== 'slider' || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0) || (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); const opacity = shouldShow @@ -171,8 +171,8 @@ export function useSwapTextStyles({ : withTiming(0, caretConfig); const isZero = - (SwapInputController.inputMethod.value !== 'slider' && SwapInputController.inputValues.value.inputAmount === 0) || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0); + (SwapInputsController.inputMethod.value !== 'slider' && SwapInputsController.inputValues.value.inputAmount === 0) || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0); return { display: shouldShow ? 'flex' : 'none', @@ -186,8 +186,8 @@ export function useSwapTextStyles({ focusedInput.value === 'outputAmount' && inputProgress.value === 0 && outputProgress.value === 0 && - (SwapInputController.inputMethod.value !== 'slider' || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0) || + (SwapInputsController.inputMethod.value !== 'slider' || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0) || (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); const opacity = shouldShow @@ -204,8 +204,8 @@ export function useSwapTextStyles({ : withTiming(0, caretConfig); const isZero = - (SwapInputController.inputMethod.value !== 'slider' && SwapInputController.inputValues.value.outputAmount === 0) || - (SwapInputController.inputMethod.value === 'slider' && Number(SwapInputController.inputValues.value.inputAmount) === 0); + (SwapInputsController.inputMethod.value !== 'slider' && SwapInputsController.inputValues.value.outputAmount === 0) || + (SwapInputsController.inputMethod.value === 'slider' && Number(SwapInputsController.inputValues.value.inputAmount) === 0); return { display: shouldShow ? 'flex' : 'none', diff --git a/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts b/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts index 5c62bad1f63..ce46ee0bec5 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts @@ -40,7 +40,7 @@ export interface SwapTimeEstimate { } type UsePriceImpactWarningProps = { - SwapInputController: ReturnType; + SwapInputsController: ReturnType; inputAsset: SharedValue; outputAsset: SharedValue; quote: SharedValue; @@ -58,7 +58,7 @@ type CurrentProps = { }; export const useSwapWarning = ({ - SwapInputController, + SwapInputsController, inputAsset, outputAsset, quote, @@ -169,8 +169,8 @@ export const useSwapWarning = ({ () => ({ inputAsset: inputAsset.value, outputAsset: outputAsset.value, - inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, - outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + inputNativeValue: SwapInputsController.inputValues.value.inputNativeValue, + outputNativeValue: SwapInputsController.inputValues.value.outputNativeValue, quote: quote.value, isFetching: isFetching.value, isQuoteStale: isQuoteStale.value, @@ -178,6 +178,7 @@ export const useSwapWarning = ({ }), (current, previous) => { if (!current.inputAsset || !current.outputAsset) { + updateWarning({ type: SwapWarningType.none, title: '', color: colorMap[SwapWarningType.none], icon: '', subtitle: '' }); return; } diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index b5f0209c7c3..c5860b44496 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -1,20 +1,5 @@ -// @refresh -import { INITIAL_SLIDER_POSITION, SLIDER_COLLAPSED_HEIGHT, SLIDER_HEIGHT, SLIDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; -import { useAnimatedSwapStyles } from '@/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles'; -import { useSwapInputsController } from '@/__swaps__/screens/Swap/hooks/useSwapInputsController'; -import { NavigationSteps, useSwapNavigation } from '@/__swaps__/screens/Swap/hooks/useSwapNavigation'; -import { useSwapTextStyles } from '@/__swaps__/screens/Swap/hooks/useSwapTextStyles'; -import { useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning'; -import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; -import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; -import { isSameAsset } from '@/__swaps__/utils/assets'; -import { parseAssetAndExtend } from '@/__swaps__/utils/swaps'; -import { logger } from '@/logger'; -import { swapsStore } from '@/state/swaps/swapsStore'; -import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; -import React, { ReactNode, createContext, useContext, useEffect } from 'react'; -import { StyleProp, TextInput, TextStyle } from 'react-native'; +import React, { createContext, useContext, ReactNode } from 'react'; +import { StyleProp, TextStyle, TextInput } from 'react-native'; import { AnimatedRef, SharedValue, @@ -24,7 +9,23 @@ import { useDerivedValue, useSharedValue, } from 'react-native-reanimated'; -import { useSwapSettings } from '../hooks/useSwapSettings'; +import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; +import { INITIAL_SLIDER_POSITION, SLIDER_COLLAPSED_HEIGHT, SLIDER_HEIGHT, SLIDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; +import { useAnimatedSwapStyles } from '@/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles'; +import { useSwapTextStyles } from '@/__swaps__/screens/Swap/hooks/useSwapTextStyles'; +import { useSwapNavigation, NavigationSteps } from '@/__swaps__/screens/Swap/hooks/useSwapNavigation'; +import { useSwapInputsController } from '@/__swaps__/screens/Swap/hooks/useSwapInputsController'; +import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; +import { useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning'; +import { useSwapGas } from '@/__swaps__/screens/Swap/hooks/useSwapGas'; +import { useSwapSettings } from '@/__swaps__/screens/Swap/hooks/useSwapSettings'; +import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; +import { swapsStore } from '@/state/swaps/swapsStore'; +import { isSameAsset, parseSearchAsset } from '@/__swaps__/utils/assets'; +import { parseAssetAndExtend } from '@/__swaps__/utils/swaps'; +import { ChainId } from '@/__swaps__/types/chains'; +import { logger } from '@/logger'; +import { userAssetsStore } from '@/state/assets/userAssets'; interface SwapContextType { isFetching: SharedValue; @@ -52,7 +53,7 @@ interface SwapContextType { quote: SharedValue; SwapSettings: ReturnType; - SwapInputController: ReturnType; + SwapInputsController: ReturnType; AnimatedSwapStyles: ReturnType; SwapTextStyles: ReturnType; SwapNavigation: ReturnType; @@ -61,6 +62,8 @@ interface SwapContextType { confirmButtonIcon: Readonly>; confirmButtonLabel: Readonly>; confirmButtonIconStyle: StyleProp; + + reset: () => void; } const SwapContext = createContext(undefined); @@ -76,7 +79,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const searchInputRef = useAnimatedRef(); const inputProgress = useSharedValue(NavigationSteps.INPUT_ELEMENT_FOCUSED); - const outputProgress = useSharedValue(NavigationSteps.INPUT_ELEMENT_FOCUSED); + const outputProgress = useSharedValue(NavigationSteps.TOKEN_LIST_FOCUSED); const configProgress = useSharedValue(NavigationSteps.INPUT_ELEMENT_FOCUSED); const sliderXPosition = useSharedValue(SLIDER_WIDTH * INITIAL_SLIDER_POSITION); @@ -85,10 +88,23 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const lastTypedInput = useSharedValue('inputAmount'); const focusedInput = useSharedValue('inputAmount'); - const selectedOutputChainId = useSharedValue(ChainId.mainnet); + // NOTE: On mount let's set the initial input asset to the first user asset (aka the user asset with the largest balance) + const INTERNAL_INITIAL_USER_ASSET = userAssetsStore.getState().userAssets.values().next().value; - const internalSelectedInputAsset = useSharedValue(null); + const internalSelectedInputAsset = useSharedValue( + parseAssetAndExtend({ asset: INTERNAL_INITIAL_USER_ASSET }) + ); const internalSelectedOutputAsset = useSharedValue(null); + const selectedOutputChainId = useSharedValue(INTERNAL_INITIAL_USER_ASSET?.chainId ?? ChainId.mainnet); + + if (INTERNAL_INITIAL_USER_ASSET) { + const parsedAsset = parseSearchAsset({ + assetWithPrice: undefined, + searchAsset: INTERNAL_INITIAL_USER_ASSET, + userAsset: INTERNAL_INITIAL_USER_ASSET, + }); + swapsStore.setState({ inputAsset: parsedAsset }); + } const quote = useSharedValue(null); @@ -96,7 +112,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { inputAsset: internalSelectedInputAsset, }); - const SwapInputController = useSwapInputsController({ + const SwapInputsController = useSwapInputsController({ focusedInput, lastTypedInput, inputProgress, @@ -110,14 +126,14 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }); const SwapNavigation = useSwapNavigation({ - SwapInputController, + SwapInputsController, inputProgress, outputProgress, configProgress, }); const SwapWarning = useSwapWarning({ - SwapInputController, + SwapInputsController, inputAsset: internalSelectedInputAsset, outputAsset: internalSelectedOutputAsset, quote, @@ -137,7 +153,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }); const SwapTextStyles = useSwapTextStyles({ - SwapInputController, + SwapInputsController, internalSelectedInputAsset, internalSelectedOutputAsset, isQuoteStale, @@ -178,13 +194,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }; const setSelectedOutputChainId = (chainId: ChainId) => { - const updateChainId = (chainId: ChainId) => { - 'worklet'; - selectedOutputChainId.value = chainId; - }; - swapsStore.setState({ selectedOutputChainId: chainId }); - runOnUI(updateChainId)(chainId); + selectedOutputChainId.value = chainId; }; const setAsset = ({ type, asset }: { type: SwapAssetType; asset: ParsedSearchAsset }) => { @@ -194,7 +205,6 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { switch (type) { case SwapAssetType.inputAsset: internalSelectedInputAsset.value = asset; - selectedOutputChainId.value = asset?.chainId ?? ChainId.mainnet; break; case SwapAssetType.outputAsset: internalSelectedOutputAsset.value = asset; @@ -206,28 +216,20 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }); }; - // const prevAsset = swapsStore.getState()[type]; - const prevOtherAsset = swapsStore.getState()[type === SwapAssetType.inputAsset ? SwapAssetType.outputAsset : SwapAssetType.inputAsset]; - - // TODO: Fix me. This is causing assets to not be set sometimes? - // if we're setting the same asset, exit early as it's a no-op - // if (prevAsset && isSameAsset(prevAsset, asset)) { - // logger.debug(`[setAsset]: Not setting ${type} asset as it's the same as what is already set`); - // handleProgressNavigation({ - // type, - // inputAsset: type === SwapAssetType.inputAsset ? asset : prevOtherAsset, - // outputAsset: type === SwapAssetType.outputAsset ? asset : prevOtherAsset, - // }); - // return; - // } + const otherAsset = swapsStore.getState()[type === SwapAssetType.inputAsset ? SwapAssetType.outputAsset : SwapAssetType.inputAsset]; // if we're setting the same asset as the other asset, we need to clear the other asset - if (prevOtherAsset && isSameAsset(prevOtherAsset, asset)) { + if (otherAsset && isSameAsset(otherAsset, asset)) { logger.debug(`[setAsset]: Swapping ${type} asset for ${type === SwapAssetType.inputAsset ? 'output' : 'input'} asset`); swapsStore.setState({ [type === SwapAssetType.inputAsset ? SwapAssetType.outputAsset : SwapAssetType.inputAsset]: null, }); + + if (type === SwapAssetType.inputAsset) { + setSelectedOutputChainId(otherAsset.chainId); + } + runOnUI(updateAssetValue)({ type: type === SwapAssetType.inputAsset ? SwapAssetType.outputAsset : SwapAssetType.inputAsset, asset: null, @@ -239,6 +241,9 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { swapsStore.setState({ [type]: asset, }); + if (type === SwapAssetType.inputAsset) { + setSelectedOutputChainId(asset.chainId); + } runOnUI(updateAssetValue)({ type, asset: parseAssetAndExtend({ asset }) }); }; @@ -253,12 +258,12 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { return ''; } - const isInputZero = Number(SwapInputController.inputValues.value.inputAmount) === 0; - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + const isInputZero = Number(SwapInputsController.inputValues.value.inputAmount) === 0; + const isOutputZero = Number(SwapInputsController.inputValues.value.outputAmount) === 0; - if (SwapInputController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value) { + if (SwapInputsController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value) { return ''; - } else if (SwapInputController.inputMethod.value === 'slider' && SwapInputController.percentageToSwap.value === 0) { + } else if (SwapInputsController.inputMethod.value === 'slider' && SwapInputsController.percentageToSwap.value === 0) { return ''; } else { return '􀕹'; @@ -276,14 +281,14 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { return 'Fetching prices'; } - const isInputZero = Number(SwapInputController.inputValues.value.inputAmount) === 0; - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + const isInputZero = Number(SwapInputsController.inputValues.value.inputAmount) === 0; + const isOutputZero = Number(SwapInputsController.inputValues.value.outputAmount) === 0; - if (SwapInputController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value) { + if (SwapInputsController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value) { return 'Enter Amount'; } else if ( - SwapInputController.inputMethod.value === 'slider' && - (SwapInputController.percentageToSwap.value === 0 || isInputZero || isOutputZero) + SwapInputsController.inputMethod.value === 'slider' && + (SwapInputsController.percentageToSwap.value === 0 || isInputZero || isOutputZero) ) { return 'Enter Amount'; } else { @@ -292,13 +297,13 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }); const confirmButtonIconStyle = useAnimatedStyle(() => { - const isInputZero = Number(SwapInputController.inputValues.value.inputAmount) === 0; - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + const isInputZero = Number(SwapInputsController.inputValues.value.inputAmount) === 0; + const isOutputZero = Number(SwapInputsController.inputValues.value.outputAmount) === 0; const sliderCondition = - SwapInputController.inputMethod.value === 'slider' && - (SwapInputController.percentageToSwap.value === 0 || isInputZero || isOutputZero); - const inputCondition = SwapInputController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value; + SwapInputsController.inputMethod.value === 'slider' && + (SwapInputsController.percentageToSwap.value === 0 || isInputZero || isOutputZero); + const inputCondition = SwapInputsController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value; const shouldHide = sliderCondition || inputCondition; @@ -307,17 +312,43 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }; }); - useEffect(() => { - return () => { - swapsStore.setState({ - inputAsset: null, - outputAsset: null, - quote: null, - }); + const reset = () => { + const firstUserAsset = userAssetsStore.getState().userAssets.values().next().value; + const parsedAsset = parseSearchAsset({ + assetWithPrice: undefined, + searchAsset: firstUserAsset, + userAsset: firstUserAsset, + }); - SwapInputController.quoteFetchingInterval.stop(); - }; - }, []); + // reset back to the user's asset with largest balance + setAsset({ + type: SwapAssetType.inputAsset, + asset: parsedAsset, + }); + + // reset output asset to null + internalSelectedOutputAsset.value = null; + swapsStore.setState({ outputAsset: null }); + + // reset input states + lastTypedInput.value = 'inputAmount'; + focusedInput.value = 'inputAmount'; + + // stop quote fetching interval if it was set + SwapInputsController.quoteFetchingInterval.stop(); + quote.value = null; + isFetching.value = false; + isQuoteStale.value = 0; + + // reset inputValues and method + SwapInputsController.inputMethod.value = 'slider'; + SwapInputsController.inputValues.value.outputAmount = 0; + SwapInputsController.inputValues.value.outputNativeValue = 0; + + // reset slider position to initial position + sliderXPosition.value = SLIDER_WIDTH * INITIAL_SLIDER_POSITION; + sliderPressProgress.value = SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT; + }; console.log('re-rendered swap provider: ', Date.now()); @@ -348,7 +379,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { quote, SwapSettings, - SwapInputController, + SwapInputsController, AnimatedSwapStyles, SwapTextStyles, SwapNavigation, @@ -357,6 +388,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { confirmButtonIcon, confirmButtonLabel, confirmButtonIconStyle, + + reset, }} > {children} diff --git a/src/__swaps__/types/swap.ts b/src/__swaps__/types/swap.ts index 018f0c5f2e2..86ed118e6d1 100644 --- a/src/__swaps__/types/swap.ts +++ b/src/__swaps__/types/swap.ts @@ -1,3 +1,5 @@ +import { UniqueId } from './assets'; + export type inputKeys = 'inputAmount' | 'inputNativeValue' | 'outputAmount' | 'outputNativeValue'; export type settingsKeys = 'swapFee' | 'slippage' | 'flashbots'; export type inputMethods = inputKeys | 'slider'; @@ -10,3 +12,15 @@ export enum SwapAssetType { inputAsset = 'inputAsset', outputAsset = 'outputAsset', } + +export type PrefillAssetData = { + // assets + inputAssetUniqueId?: UniqueId; + outputAssetUniqueId?: UniqueId; + + // amounts + percentageOfInputAssetToSell?: number; + percentageOfOutputAssetToBuy?: number; + amountOfInputAssetToSell?: number; + amountOfOutputAssetToBuy?: number; +}; diff --git a/src/__swaps__/utils/prefillAssets.ts b/src/__swaps__/utils/prefillAssets.ts new file mode 100644 index 00000000000..b6fb84c0294 --- /dev/null +++ b/src/__swaps__/utils/prefillAssets.ts @@ -0,0 +1,43 @@ +import { userAssetsStore } from '@/state/assets/userAssets'; +import { UniqueId } from '../types/assets'; +import { useSwapContext } from '../screens/Swap/providers/swap-provider'; + +export enum EntryPoint { + ProfileActionButtonsRow = 'ProfileActionButtonsRow', + AssetChart = 'AssetChart', +} + +export type EntryPointData = { + inputAssetUniqueId?: UniqueId; + outputAssetUniqueId?: UniqueId; + + // amounts + percentageOfInputAssetToSell?: number; + percentageOfOutputAssetToBuy?: number; + amountOfInputAssetToSell?: number; + amountOfOutputAssetToBuy?: number; +}; + +export const usePrefillAssets = (entryPoint: EntryPoint, data?: EntryPointData) => { + const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); + + // NOTE: User assets are sorted by balance in descending order. + const userAssets = userAssetsStore.getState().userAssets; + + switch (entryPoint) { + /** + * If we're coming to the swap screen from the profile action buttons row, we need to prefill the inputAsset + * to the user asset with the largest balance. We should also open the output token list so that they can + * select an output asset. + */ + default: + case EntryPoint.ProfileActionButtonsRow: + break; + + /** + * If we're coming to the swap screen from an asset chart, we need to + */ + case EntryPoint.AssetChart: + break; + } +}; diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index 80694a8bdbb..0a509da6adb 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -1,7 +1,7 @@ import Clipboard from '@react-native-clipboard/clipboard'; import lang from 'i18n-js'; import * as React from 'react'; -import { PressableProps } from 'react-native'; +import { InteractionManager, PressableProps } from 'react-native'; import Animated, { useAnimatedStyle, useDerivedValue, withSpring } from 'react-native-reanimated'; import { ButtonPressAnimation } from '@/components/animations'; import { CopyFloatingEmojis } from '@/components/floating-emojis'; @@ -180,7 +180,9 @@ function SwapButton() { android && delayNext(); if (swapsV2Enabled) { - navigate(Routes.SWAP_NAVIGATOR); + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); + }); return; } diff --git a/src/components/sheet/sheet-action-buttons/SwapActionButton.js b/src/components/sheet/sheet-action-buttons/SwapActionButton.js index 62b7909292a..130b5a46da6 100644 --- a/src/components/sheet/sheet-action-buttons/SwapActionButton.js +++ b/src/components/sheet/sheet-action-buttons/SwapActionButton.js @@ -1,26 +1,48 @@ import lang from 'i18n-js'; import React, { useCallback } from 'react'; import SheetActionButton from './SheetActionButton'; -import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; -import AssetInputTypes from '@/helpers/assetInputTypes'; -import { useExpandedStateNavigation, useSwapCurrencyHandlers } from '@/hooks'; +import { useExpandedStateNavigation } from '@/hooks'; import Routes from '@/navigation/routesNames'; -import { ethereumUtils } from '@/utils'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { SWAPS_V2, useExperimentalFlag } from '@/config'; +import assetInputTypes from '@/helpers/assetInputTypes'; +import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; +import { SwapAssetType } from '@/__swaps__/types/swap'; function SwapActionButton({ asset, color: givenColor, inputType, label, fromDiscover, weight = 'heavy', ...props }) { + const { setAsset } = useSwapContext(); + const { swaps_v2 } = useRemoteConfig(); + const { navigate } = useNavigation(); + const swapsV2Enabled = useExperimentalFlag(SWAPS_V2); + const { colors } = useTheme(); const color = givenColor || colors.swapPurple; - const { updateInputCurrency, updateOutputCurrency } = useSwapCurrencyHandlers({ - defaultInputAsset: inputType === AssetInputTypes.in ? asset : null, - defaultOutputAsset: inputType === AssetInputTypes.out ? asset : null, - shouldUpdate: true, - type: ExchangeModalTypes.swap, - }); - - const navigate = useExpandedStateNavigation(inputType, fromDiscover, asset); + const old_navigate = useExpandedStateNavigation(inputType, fromDiscover, asset); const goToSwap = useCallback(() => { - navigate(Routes.EXCHANGE_MODAL, params => { + if (swapsV2Enabled || swaps_v2) { + const chainId = ethereumUtils.getChainIdFromNetwork(asset.network); + const userAsset = userAssetsStore.getState().userAssets.get(`${asset.address}_${chainId}`); + const parsedAsset = parseSearchAsset({ + assetWithPrice: asset, + searchAsset: { ...asset, chainId }, + userAsset, + }); + + if (inputType === assetInputTypes.in) { + setAsset({ type: SwapAssetType.inputAsset, asset: parsedAsset }); + } else { + setAsset({ type: SwapAssetType.outputAsset, asset: parsedAsset }); + } + + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); + }); + + return; + } + + old_navigate(Routes.EXCHANGE_MODAL, params => { if (params.outputAsset) { return { params: { @@ -40,7 +62,7 @@ function SwapActionButton({ asset, color: givenColor, inputType, label, fromDisc }; } }); - }, [asset, navigate]); + }, [asset, inputType, navigate, old_navigate, setAsset, swapsV2Enabled, swaps_v2]); return ( ); } +import { useNavigation } from '@/navigation'; +import { InteractionManager } from 'react-native'; +import { parseSearchAsset } from '@/__swaps__/utils/assets'; +import { userAssetsStore } from '@/state/assets/userAssets'; +import { ethereumUtils } from '@/utils'; export default React.memo(SwapActionButton); diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index 70b4b99cf9b..e15bb0e8539 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -140,16 +140,6 @@ function MainOuterNavigator() { ); } -function SwapNavigator() { - return ( - - - - - - ); -} - function BSNavigator() { const remoteConfig = useRemoteConfig(); const profilesEnabled = useExperimentalFlag(PROFILES); @@ -256,7 +246,7 @@ function BSNavigator() { - {swapsV2Enabled && } + {swapsV2Enabled && } ); } diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index ab5a1975dbd..07a51f9bde9 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -141,16 +141,6 @@ function MainStack() { ); } -function SwapsNavigator() { - return ( - - - - - - ); -} - function NativeStackNavigator() { const remoteConfig = useRemoteConfig(); const { colors, isDarkMode } = useTheme(); @@ -306,7 +296,7 @@ function NativeStackNavigator() { - {swapsV2Enabled && } + {swapsV2Enabled && } ); }