diff --git a/src/__tests__/components/__snapshots__/LineTextDivider.test.tsx.snap b/src/__tests__/components/__snapshots__/LineTextDivider.test.tsx.snap index 0d0d28b9f38..3db3dd53c63 100644 --- a/src/__tests__/components/__snapshots__/LineTextDivider.test.tsx.snap +++ b/src/__tests__/components/__snapshots__/LineTextDivider.test.tsx.snap @@ -6,10 +6,10 @@ exports[`LineTextDivider should render with loading props 1`] = ` { "alignItems": "center", "flexDirection": "row", + "flexGrow": 1, "justifyContent": "space-between", "marginVertical": 11, "paddingHorizontal": 11, - "width": "100%", } } > diff --git a/src/__tests__/scenes/CryptoExchangeQuoteScene.test.tsx b/src/__tests__/scenes/SwapConfirmationScene.test.tsx similarity index 94% rename from src/__tests__/scenes/CryptoExchangeQuoteScene.test.tsx rename to src/__tests__/scenes/SwapConfirmationScene.test.tsx index ef39927fadb..f6488da85c1 100644 --- a/src/__tests__/scenes/CryptoExchangeQuoteScene.test.tsx +++ b/src/__tests__/scenes/SwapConfirmationScene.test.tsx @@ -14,7 +14,7 @@ import process from 'process' import * as React from 'react' import TestRenderer from 'react-test-renderer' -import { CryptoExchangeQuoteScene } from '../../components/scenes/CryptoExchangeQuoteScene' +import { SwapConfirmationScene } from '../../components/scenes/SwapConfirmationScene' import { btcCurrencyInfo } from '../../util/fake/fakeBtcInfo' import { makeFakePlugin } from '../../util/fake/fakeCurrencyPlugin' import { ethCurrencyInfo } from '../../util/fake/fakeEthInfo' @@ -81,7 +81,7 @@ beforeAll(async () => { ethWallet = await account.waitForCurrencyWallet(ethInfo.id) }) -describe('CryptoExchangeQuoteScreenComponent', () => { +describe('SwapConfirmationScene', () => { it('should render with loading props', () => { if (btcWallet == null || ethWallet == null) return const rootState: FakeState = { ...fakeRootState, core: { account } } @@ -133,8 +133,8 @@ describe('CryptoExchangeQuoteScreenComponent', () => { const renderer = TestRenderer.create( - undefined diff --git a/src/__tests__/scenes/CryptoExchangeScene.test.tsx b/src/__tests__/scenes/SwapCreateScene.test.tsx similarity index 76% rename from src/__tests__/scenes/CryptoExchangeScene.test.tsx rename to src/__tests__/scenes/SwapCreateScene.test.tsx index 5243a13db19..3981c385f7b 100644 --- a/src/__tests__/scenes/CryptoExchangeScene.test.tsx +++ b/src/__tests__/scenes/SwapCreateScene.test.tsx @@ -2,18 +2,18 @@ import { describe, expect, it } from '@jest/globals' import * as React from 'react' import TestRenderer from 'react-test-renderer' -import { CryptoExchangeScene } from '../../components/scenes/CryptoExchangeScene' +import { SwapCreateScene } from '../../components/scenes/SwapCreateScene' import { FakeProviders, FakeState } from '../../util/fake/FakeProviders' import { fakeRootState } from '../../util/fake/fakeRootState' import { fakeSceneProps } from '../../util/fake/fakeSceneProps' -describe('CryptoExchangeComponent', () => { +describe('SwapCreateScene', () => { it('should render with loading props', () => { const rootState: FakeState = { ...fakeRootState } const renderer = TestRenderer.create( - + ) diff --git a/src/__tests__/scenes/CryptoExchangeSuccessScene.test.tsx b/src/__tests__/scenes/SwapSuccessScene.test.tsx similarity index 57% rename from src/__tests__/scenes/CryptoExchangeSuccessScene.test.tsx rename to src/__tests__/scenes/SwapSuccessScene.test.tsx index 46647b9383d..b3d44517d5a 100644 --- a/src/__tests__/scenes/CryptoExchangeSuccessScene.test.tsx +++ b/src/__tests__/scenes/SwapSuccessScene.test.tsx @@ -2,19 +2,17 @@ import { describe, expect, it } from '@jest/globals' import * as React from 'react' import { createRenderer } from 'react-test-renderer/shallow' -import { CryptoExchangeSuccessComponent } from '../../components/scenes/CryptoExchangeSuccessScene' +import { SwapSuccessSceneComponent } from '../../components/scenes/SwapSuccessScene' import { getTheme } from '../../components/services/ThemeContext' import { fakeSceneProps } from '../../util/fake/fakeSceneProps' -describe('CryptoExchangeSuccessComponent', () => { +describe('SwapSuccessSceneComponent', () => { it('should render with loading props', () => { const renderer = createRenderer() const fakeDisklet: any = {} - const actual = renderer.render( - - ) + const actual = renderer.render() expect(actual).toMatchSnapshot() }) diff --git a/src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap similarity index 99% rename from src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap rename to src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap index 4d87237c1a5..eb403f59e69 100644 --- a/src/__tests__/scenes/__snapshots__/CryptoExchangeQuoteScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CryptoExchangeQuoteScreenComponent should render with loading props 1`] = ` +exports[`SwapConfirmationScene should render with loading props 1`] = ` [ diff --git a/src/__tests__/scenes/__snapshots__/CryptoExchangeScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SwapCreateScene.test.tsx.snap similarity index 83% rename from src/__tests__/scenes/__snapshots__/CryptoExchangeScene.test.tsx.snap rename to src/__tests__/scenes/__snapshots__/SwapCreateScene.test.tsx.snap index 2ce6bcd4ea1..1f542b5e8b3 100644 --- a/src/__tests__/scenes/__snapshots__/CryptoExchangeScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SwapCreateScene.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CryptoExchangeComponent should render with loading props 1`] = ` +exports[`SwapCreateScene should render with loading props 1`] = ` [ - - - - - Exchange - - - - - @@ -442,7 +351,7 @@ exports[`CryptoExchangeComponent should render with loading props 1`] = ` ] } > - Select Source Wallet + Select Receiving Wallet @@ -455,10 +364,10 @@ exports[`CryptoExchangeComponent should render with loading props 1`] = ` { "alignItems": "center", "flexDirection": "row", + "flexGrow": 1, "justifyContent": "space-between", "marginVertical": 11, "paddingHorizontal": 11, - "width": "100%", } } > @@ -471,35 +380,73 @@ exports[`CryptoExchangeComponent should render with loading props 1`] = ` } } /> - + - To - + { + "fontFamily": "Ionicons", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + @@ -624,7 +573,7 @@ exports[`CryptoExchangeComponent should render with loading props 1`] = ` ] } > - Select Receiving Wallet + Select Source Wallet diff --git a/src/__tests__/scenes/__snapshots__/CryptoExchangeSuccessScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SwapSuccessScene.test.tsx.snap similarity index 94% rename from src/__tests__/scenes/__snapshots__/CryptoExchangeSuccessScene.test.tsx.snap rename to src/__tests__/scenes/__snapshots__/SwapSuccessScene.test.tsx.snap index 53ba50ee48d..ce4cc18f804 100644 --- a/src/__tests__/scenes/__snapshots__/CryptoExchangeSuccessScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SwapSuccessScene.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CryptoExchangeSuccessComponent should render with loading props 1`] = ` +exports[`SwapSuccessSceneComponent should render with loading props 1`] = ` diff --git a/src/actions/DeepLinkingActions.ts b/src/actions/DeepLinkingActions.ts index e3092bc111a..c5a4a6badc4 100644 --- a/src/actions/DeepLinkingActions.ts +++ b/src/actions/DeepLinkingActions.ts @@ -161,7 +161,7 @@ export async function handleLink(navigation: NavigationBase, dispatch: Dispatch, } case 'swap': { - navigation.navigate('exchangeTab', { screen: 'exchange' }) + navigation.navigate('swapTab', { screen: 'swapCreate' }) return true } diff --git a/src/actions/ScanActions.tsx b/src/actions/ScanActions.tsx index d8f1291a761..98e234708c7 100644 --- a/src/actions/ScanActions.tsx +++ b/src/actions/ScanActions.tsx @@ -356,7 +356,7 @@ export function checkAndShowGetCryptoModal(navigation: NavigationBase, wallet: E if (threeButtonModal === 'buy') { navigation.navigate('buyTab', { screen: 'pluginListBuy' }) } else if (threeButtonModal === 'exchange') { - navigation.navigate('exchangeTab', { screen: 'exchange', params: { toWalletId: wallet.id, toTokenId: tokenId } }) + navigation.navigate('swapTab', { screen: 'swapCreate', params: { toWalletId: wallet.id, toTokenId: tokenId } }) } } catch (e: any) { // Don't bother the user with this error, but log it quietly: diff --git a/src/components/Main.tsx b/src/components/Main.tsx index ea274754008..b23019c315d 100644 --- a/src/components/Main.tsx +++ b/src/components/Main.tsx @@ -9,7 +9,7 @@ import { AirshipToast } from 'react-native-airship' import { getDeviceSettings } from '../actions/DeviceSettingsActions' import { logoutRequest } from '../actions/LoginActions' import { checkEnabledExchanges, showReEnableOtpModal } from '../actions/SettingsActions' -import { CryptoExchangeScene as CryptoExchangeSceneComponent } from '../components/scenes/CryptoExchangeScene' +import { SwapCreateScene as SwapCreateSceneComponent } from '../components/scenes/SwapCreateScene' import { HomeSceneUi4 as HomeSceneUi4Component } from '../components/ui4/scenes/HomeSceneUi4' import { ENV } from '../env' import { DEFAULT_EXPERIMENT_CONFIG, ExperimentConfig, getExperimentConfig } from '../experimentConfig' @@ -54,9 +54,6 @@ import { CreateWalletImportOptionsScene as CreateWalletImportOptionsSceneCompone import { CreateWalletImportScene as CreateWalletImportSceneComponent } from './scenes/CreateWalletImportScene' import { CreateWalletSelectCryptoScene as CreateWalletSelectCryptoSceneComponent } from './scenes/CreateWalletSelectCryptoScene' import { CreateWalletSelectFiatScene as CreateWalletSelectFiatSceneComponent } from './scenes/CreateWalletSelectFiatScene' -import { CryptoExchangeQuoteProcessingScene as CryptoExchangeQuoteProcessingSceneComponent } from './scenes/CryptoExchangeQuoteProcessingScene' -import { CryptoExchangeQuoteScene as CryptoExchangeQuoteComponent } from './scenes/CryptoExchangeQuoteScene' -import { CryptoExchangeSuccessScene as CryptoExchangeSuccessSceneComponent } from './scenes/CryptoExchangeSuccessScene' import { CurrencyNotificationScene as CurrencyNotificationSceneComponent } from './scenes/CurrencyNotificationScene' import { CurrencySettingsScene as CurrencySettingsSceneComponent } from './scenes/CurrencySettingsScene' import { DefaultFiatSettingScene as DefaultFiatSettingSceneComponent } from './scenes/DefaultFiatSettingScene' @@ -109,7 +106,10 @@ import { SpendingLimitsScene as SpendingLimitsSceneComponent } from './scenes/Sp import { StakeModifyScene as StakeModifySceneComponent } from './scenes/Staking/StakeModifyScene' import { StakeOptionsScene as StakeOptionsSceneComponent } from './scenes/Staking/StakeOptionsScene' import { StakeOverviewScene as StakeOverviewSceneComponent } from './scenes/Staking/StakeOverviewScene' +import { SwapConfirmationScene as SwapConfirmationSceneComponent } from './scenes/SwapConfirmationScene' +import { SwapProcessingScene as SwapProcessingSceneComponent } from './scenes/SwapProcessingScene' import { SwapSettingsScene as SwapSettingsSceneComponent } from './scenes/SwapSettingsScene' +import { SwapSuccessScene as SwapSuccessSceneComponent } from './scenes/SwapSuccessScene' import { TransactionDetailsScene as TransactionDetailsSceneComponent } from './scenes/TransactionDetailsScene' import { TransactionList as TransactionListComponent } from './scenes/TransactionListScene' import { TransactionsExportScene as TransactionsExportSceneComponent } from './scenes/TransactionsExportScene' @@ -139,10 +139,6 @@ const CreateWalletImportScene = ifLoggedIn(CreateWalletImportSceneComponent) const CreateWalletImportOptionsScene = ifLoggedIn(CreateWalletImportOptionsSceneComponent) const CreateWalletSelectCryptoScene = ifLoggedIn(CreateWalletSelectCryptoSceneComponent) const CreateWalletSelectFiatScene = ifLoggedIn(CreateWalletSelectFiatSceneComponent) -const CryptoExchangeQuote = ifLoggedIn(CryptoExchangeQuoteComponent) -const CryptoExchangeQuoteProcessingScene = ifLoggedIn(CryptoExchangeQuoteProcessingSceneComponent) -const CryptoExchangeScene = ifLoggedIn(CryptoExchangeSceneComponent) -const CryptoExchangeSuccessScene = ifLoggedIn(CryptoExchangeSuccessSceneComponent) const CurrencyNotificationScene = ifLoggedIn(CurrencyNotificationSceneComponent) const AssetSettingsScene = ifLoggedIn(AssetSettingsSceneComponent) const CurrencySettingsScene = ifLoggedIn(CurrencySettingsSceneComponent) @@ -195,7 +191,11 @@ const RewardsCardWelcomeScene = ifLoggedIn(RewardsCardWelcomeSceneComponent) const StakeModifyScene = ifLoggedIn(StakeModifySceneComponent) const StakeOptionsScene = ifLoggedIn(StakeOptionsSceneComponent) const StakeOverviewScene = ifLoggedIn(StakeOverviewSceneComponent) +const SwapProcessingScene = ifLoggedIn(SwapProcessingSceneComponent) +const SwapConfirmationScene = ifLoggedIn(SwapConfirmationSceneComponent) +const SwapCreateScene = ifLoggedIn(SwapCreateSceneComponent) const SwapSettingsScene = ifLoggedIn(SwapSettingsSceneComponent) +const SwapSuccessScene = ifLoggedIn(SwapSuccessSceneComponent) const TransactionDetailsScene = ifLoggedIn(TransactionDetailsSceneComponent) const TransactionList = ifLoggedIn(TransactionListComponent) const TransactionsExportScene = ifLoggedIn(TransactionsExportSceneComponent) @@ -471,7 +471,7 @@ const EdgeAppStack = () => { }} /> { }} /> null }} @@ -720,7 +720,7 @@ const EdgeTabs = () => { - + @@ -797,22 +797,25 @@ const EdgeSellTabScreen = () => { ) } -const EdgeExchangeTabScreen = () => { +const EdgeSwapTabScreen = () => { const dispatch = useDispatch() return ( - + dispatch(checkEnabledExchanges()) }} /> - + null, headerRight: () => null diff --git a/src/components/icons/ThemedIcons.tsx b/src/components/icons/ThemedIcons.tsx index a5610d3789c..9ed09ccb602 100644 --- a/src/components/icons/ThemedIcons.tsx +++ b/src/components/icons/ThemedIcons.tsx @@ -2,7 +2,7 @@ import React from 'react' import Animated, { SharedValue, useAnimatedStyle } from 'react-native-reanimated' import AntDesignIcon from 'react-native-vector-icons/AntDesign' import { type Icon } from 'react-native-vector-icons/Icon' -import IonIcon from 'react-native-vector-icons/Ionicons' +import Ionicons from 'react-native-vector-icons/Ionicons' import { Fontello } from '../../assets/vector' import { useTheme } from '../services/ThemeContext' @@ -95,7 +95,7 @@ export function EyeIconAnimated(props: AnimatedIconProps & { off: boolean }): JS // so we recycle the same component with different props: return AnimatedFontIcon({ ...rest, - IconComponent: IonIcon, + IconComponent: Ionicons, name: off ? 'eye-off-outline' : 'eye-outline' }) } @@ -106,5 +106,7 @@ export const CloseIconAnimated = makeAnimatedFontIcon(AntDesignIcon, 'close') export const FlipIcon = makeFontIcon(Fontello, 'exchange') export const FlipIconAnimated = makeAnimatedFontIcon(Fontello, 'exchange') +export const SwapVerticalIcon = makeFontIcon(Ionicons, 'swap-vertical') + export const SearchIcon = makeFontIcon(AntDesignIcon, 'search1') export const SearchIconAnimated = makeAnimatedFontIcon(AntDesignIcon, 'search1') diff --git a/src/components/modals/InsufficientFeesModal.tsx b/src/components/modals/InsufficientFeesModal.tsx index 4fdf1a99c23..055123d6e64 100644 --- a/src/components/modals/InsufficientFeesModal.tsx +++ b/src/components/modals/InsufficientFeesModal.tsx @@ -44,7 +44,7 @@ export function InsufficientFeesModal(props: Props) { }) const handleSwap = useHandler(async () => { if (onSwap) onSwap() - navigation.navigate('exchangeTab', { screen: 'exchange', params: { toWalletId: wallet.id, toTokenId: tokenId } }) + navigation.navigate('swapTab', { screen: 'swapCreate', params: { toWalletId: wallet.id, toTokenId: tokenId } }) bridge.resolve() }) diff --git a/src/components/modals/PriceChangeBuySellSwapModal.tsx b/src/components/modals/PriceChangeBuySellSwapModal.tsx index 43c142bd1b0..932009b7d32 100644 --- a/src/components/modals/PriceChangeBuySellSwapModal.tsx +++ b/src/components/modals/PriceChangeBuySellSwapModal.tsx @@ -33,7 +33,7 @@ export function launchPriceChangeBuySellSwapModal(navigation: NavigationBase, da } else if (threeButtonModal === 'sell') { navigation.navigate('sellTab', { screen: 'pluginListSell' }) } else if (threeButtonModal === 'exchange') { - navigation.navigate('exchangeTab', { screen: 'exchange' }) + navigation.navigate('swapTab', { screen: 'swapCreate' }) } } } diff --git a/src/components/scenes/SettingsScene.tsx b/src/components/scenes/SettingsScene.tsx index 7a1ac5e4c02..fbb0aaa093a 100644 --- a/src/components/scenes/SettingsScene.tsx +++ b/src/components/scenes/SettingsScene.tsx @@ -205,7 +205,7 @@ export class SettingsSceneComponent extends React.Component { handleExchangeSettings = (): void => { const { navigation } = this.props - navigation.navigate('exchangeSettings', {}) + navigation.navigate('swapSettings', {}) } handleSpendingLimits = (): void => { diff --git a/src/components/scenes/CryptoExchangeQuoteScene.tsx b/src/components/scenes/SwapConfirmationScene.tsx similarity index 97% rename from src/components/scenes/CryptoExchangeQuoteScene.tsx rename to src/components/scenes/SwapConfirmationScene.tsx index 9b1c9f373fa..f803c5f42b2 100644 --- a/src/components/scenes/CryptoExchangeQuoteScene.tsx +++ b/src/components/scenes/SwapConfirmationScene.tsx @@ -43,20 +43,20 @@ import { WalletListSectionHeader } from '../themed/WalletListSectionHeader' import { AlertCardUi4 } from '../ui4/AlertCardUi4' import { ModalUi4 } from '../ui4/ModalUi4' -export interface CryptoExchangeQuoteParams { +export interface SwapConfirmationParams { selectedQuote: EdgeSwapQuote quotes: EdgeSwapQuote[] onApprove: () => void } -interface Props extends EdgeSceneProps<'exchangeQuote'> {} +interface Props extends EdgeSceneProps<'swapConfirmation'> {} interface Section { title: { title: string; rightTitle: string } data: EdgeSwapQuote[] } -export const CryptoExchangeQuoteScene = (props: Props) => { +export const SwapConfirmationScene = (props: Props) => { const { route, navigation } = props const { selectedQuote: initialSelectedQuote, quotes, onApprove } = route.params @@ -108,14 +108,14 @@ export const CryptoExchangeQuoteScene = (props: Props) => { const showFeeWarning = gte(feePercent, '0.05') const handleExchangeTimerExpired = useHandler(() => { - navigation.replace('exchangeQuoteProcessing', { + navigation.replace('swapProcessing', { swapRequest: selectedQuote.request, swapRequestOptions, onCancel: () => { - navigation.navigate('exchangeTab', { screen: 'exchange' }) + navigation.navigate('swapTab', { screen: 'swapCreate' }) }, onDone: quotes => { - navigation.replace('exchangeQuote', { + navigation.replace('swapConfirmation', { selectedQuote: quotes[0], quotes, onApprove @@ -171,7 +171,7 @@ export const CryptoExchangeQuoteScene = (props: Props) => { nativeAmount ${networkFee.nativeAmount} `) - navigation.push('exchangeSuccess', {}) + navigation.push('swapSuccess', {}) // Dispatch the success action and callback onApprove() diff --git a/src/components/scenes/CryptoExchangeScene.tsx b/src/components/scenes/SwapCreateScene.tsx similarity index 68% rename from src/components/scenes/CryptoExchangeScene.tsx rename to src/components/scenes/SwapCreateScene.tsx index 8b67347da8c..9d5472a7f44 100644 --- a/src/components/scenes/CryptoExchangeScene.tsx +++ b/src/components/scenes/SwapCreateScene.tsx @@ -12,27 +12,27 @@ import { useSwapRequestOptions } from '../../hooks/swap/useSwapRequestOptions' import { useHandler } from '../../hooks/useHandler' import { useWatch } from '../../hooks/useWatch' import { lstrings } from '../../locales/strings' -import { selectDisplayDenom } from '../../selectors/DenominationSelectors' import { useDispatch, useSelector } from '../../types/reactRedux' import { EdgeSceneProps } from '../../types/routerTypes' import { getCurrencyCode } from '../../util/CurrencyInfoHelpers' import { getWalletName } from '../../util/CurrencyWalletHelpers' import { zeroString } from '../../util/utils' -import { EdgeAnim, fadeInDown30, fadeInDown60, fadeInDown90, fadeInUp60, fadeInUp90 } from '../common/EdgeAnim' +import { EdgeAnim, fadeInDown30, fadeInDown60, fadeInDown90, fadeInUp60 } from '../common/EdgeAnim' import { SceneWrapper } from '../common/SceneWrapper' +import { SwapVerticalIcon } from '../icons/ThemedIcons' import { WalletListModal, WalletListResult } from '../modals/WalletListModal' import { Airship, showError, showWarning } from '../services/AirshipInstance' -import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' -import { CryptoExchangeFlipInput } from '../themed/CryptoExchangeFlipInput' -import { ExchangedFlipInputAmounts } from '../themed/ExchangedFlipInput2' +import { useTheme } from '../services/ThemeContext' +import { ExchangedFlipInputAmounts, ExchangedFlipInputRef } from '../themed/ExchangedFlipInput2' import { LineTextDivider } from '../themed/LineTextDivider' -import { MiniButton } from '../themed/MiniButton' -import { SceneHeader } from '../themed/SceneHeader' +import { SwapInput } from '../themed/SwapInput' +import { ButtonBox } from '../themed/ThemedButtons' import { AlertCardUi4 } from '../ui4/AlertCardUi4' import { ButtonsViewUi4 } from '../ui4/ButtonsViewUi4' +import { ButtonUi4 } from '../ui4/ButtonUi4' -export interface ExchangeParams { - // The following props are used to populate the CryptoExchangeFlipInputs +export interface SwapCreateParams { + // The following props are used to populate the flip inputs fromWalletId?: string | undefined fromTokenId?: EdgeTokenId toWalletId?: string | undefined @@ -47,38 +47,31 @@ export interface SwapErrorDisplayInfo { title: string } -interface Props extends EdgeSceneProps<'exchange'> {} +interface Props extends EdgeSceneProps<'swapCreate'> {} interface State { - whichWalletFocus: 'from' | 'to' // Which wallet FlipInput2 was last focused and edited - fromAmountNative: string - toAmountNative: string - paddingBottom: number + nativeAmount: string + nativeAmountFor: 'from' | 'to' } const defaultState: State = { - whichWalletFocus: 'from', - fromAmountNative: '', - toAmountNative: '', - paddingBottom: 0 + nativeAmount: '0', + nativeAmountFor: 'from' } -const emptyDenomnination = { - name: '', - multiplier: '1' -} - -export const CryptoExchangeScene = (props: Props) => { +export const SwapCreateScene = (props: Props) => { const { navigation, route } = props const { fromWalletId, fromTokenId = null, toWalletId, toTokenId = null, errorDisplayInfo } = route.params ?? {} const theme = useTheme() - const styles = getStyles(theme) const dispatch = useDispatch() const [state, setState] = useState({ ...defaultState }) + const fromInputRef = React.useRef(null) + const toInputRef = React.useRef(null) + const swapRequestOptions = useSwapRequestOptions() const account = useSelector(state => state.core.account) @@ -93,19 +86,11 @@ export const CryptoExchangeScene = (props: Props) => { const fromCurrencyCode = fromWallet == null ? '' : getCurrencyCode(fromWallet, fromTokenId) const toCurrencyCode = toWallet == null ? '' : getCurrencyCode(toWallet, toTokenId) - const toWalletDisplayDenomination = useSelector(state => - toWallet == null ? emptyDenomnination : selectDisplayDenom(state, toWallet.currencyConfig, toTokenId) - ) - const fromWalletDisplayDenomination = useSelector(state => - fromWallet == null ? emptyDenomnination : selectDisplayDenom(state, fromWallet.currencyConfig, fromTokenId) - ) const fromWalletSpecialCurrencyInfo = getSpecialCurrencyInfo(fromWallet?.currencyInfo.pluginId ?? '') const fromWalletBalanceMap = fromWallet?.balanceMap ?? new Map() - const isFromFocused = state.whichWalletFocus === 'from' - const isToFocused = state.whichWalletFocus === 'to' - const fromHeaderText = sprintf(lstrings.exchange_from_wallet, fromWalletName) - const toHeaderText = sprintf(lstrings.exchange_to_wallet, toWalletName) + const fromHeaderText = fromWallet == null ? lstrings.select_src_wallet : fromWalletName + const toHeaderText = toWallet == null ? lstrings.select_recv_wallet : toWalletName // Determines if a coin can have Exchange Max option const hasMaxSpend = fromWalletSpecialCurrencyInfo.noMaxSpend !== true @@ -128,10 +113,9 @@ export const CryptoExchangeScene = (props: Props) => { } const checkExceedsAmount = (): boolean => { - const { fromAmountNative, whichWalletFocus } = state const fromNativeBalance = fromWalletBalanceMap.get(fromTokenId) ?? '0' - return whichWalletFocus === 'from' && gte(fromNativeBalance, '0') && gt(fromAmountNative, fromNativeBalance) + return state.nativeAmountFor === 'from' && gte(fromNativeBalance, '0') && gt(state.nativeAmount, fromNativeBalance) } const getQuote = (swapRequest: EdgeSwapRequest) => { @@ -155,14 +139,14 @@ export const CryptoExchangeScene = (props: Props) => { }) // Start request for quote: - navigation.navigate('exchangeQuoteProcessing', { + navigation.navigate('swapProcessing', { swapRequest, swapRequestOptions, onCancel: () => { navigation.goBack() }, onDone: quotes => { - navigation.replace('exchangeQuote', { + navigation.replace('swapConfirmation', { selectedQuote: quotes[0], quotes, onApprove: resetState @@ -200,6 +184,25 @@ export const CryptoExchangeScene = (props: Props) => { // Handlers // + const handleFlipWalletPress = useHandler(() => { + // Flip params: + navigation.setParams({ + fromWalletId: toWalletId, + fromTokenId: toTokenId, + toWalletId: fromWalletId, + toTokenId: fromTokenId, + errorDisplayInfo + }) + // Clear amount input state: + setState({ + ...state, + nativeAmount: '0' + }) + // Clear all input amounts: + toInputRef.current?.setAmount('crypto', '0') + fromInputRef.current?.setAmount('crypto', '0') + }) + const handleSelectWallet = useHandler(async (walletId: string, tokenId: EdgeTokenId, direction: 'from' | 'to') => { const params = { ...route.params, @@ -217,7 +220,7 @@ export const CryptoExchangeScene = (props: Props) => { dispatch(updateMostRecentWalletsSelected(walletId, tokenId)) }) - const handleMax = useHandler(() => { + const handleMaxPress = useHandler(() => { if (toWallet == null) { showWarning(`${lstrings.exchange_select_receiving_wallet}`) Keyboard.dismiss() @@ -247,20 +250,20 @@ export const CryptoExchangeScene = (props: Props) => { // Should only happen if the user initiated the swap from the keyboard if (fromWallet == null || toWallet == null) return + if (zeroString(state.nativeAmount)) { + showError(`${lstrings.no_exchange_amount}. ${lstrings.select_exchange_amount}.`) + return + } + const request: EdgeSwapRequest = { fromTokenId: fromTokenId, fromWallet: fromWallet, - nativeAmount: state.whichWalletFocus === 'from' ? state.fromAmountNative : state.toAmountNative, - quoteFor: state.whichWalletFocus, + nativeAmount: state.nativeAmount, + quoteFor: state.nativeAmountFor, toTokenId: toTokenId, toWallet: toWallet } - if (zeroString(request.nativeAmount)) { - showError(`${lstrings.no_exchange_amount}. ${lstrings.select_exchange_amount}.`) - return - } - if (checkExceedsAmount()) return getQuote(request) @@ -274,32 +277,24 @@ export const CryptoExchangeScene = (props: Props) => { showWalletListModal('to') }) - const handleFromFocusWallet = useHandler(() => { - setState({ - ...state, - whichWalletFocus: 'from' - }) - }) - - const handleToFocusWallet = useHandler(() => { - setState({ - ...state, - whichWalletFocus: 'to' - }) - }) - const handleFromAmountChange = useHandler((amounts: ExchangedFlipInputAmounts) => { setState({ ...state, - fromAmountNative: amounts.nativeAmount + nativeAmount: amounts.nativeAmount, + nativeAmountFor: 'from' }) + // Clear other input's amount: + toInputRef.current?.setAmount('crypto', '0') }) const handleToAmountChange = useHandler((amounts: ExchangedFlipInputAmounts) => { setState({ ...state, - toAmountNative: amounts.nativeAmount + nativeAmount: amounts.nativeAmount, + nativeAmountFor: 'to' }) + // Clear other input's amount: + fromInputRef.current?.setAmount('crypto', '0') }) // @@ -307,8 +302,7 @@ export const CryptoExchangeScene = (props: Props) => { // const renderButton = () => { - const primaryNativeAmount = state.whichWalletFocus === 'from' ? state.fromAmountNative : state.toAmountNative - const showNext = fromCurrencyCode !== '' && toCurrencyCode !== '' && !!parseFloat(primaryNativeAmount) + const showNext = fromCurrencyCode !== '' && toCurrencyCode !== '' && parseFloat(state.nativeAmount) > 0 if (!showNext) return null if (checkExceedsAmount()) return null return @@ -335,61 +329,52 @@ export const CryptoExchangeScene = (props: Props) => { return ( - - - - - {hasMaxSpend ? : null} - + {fromWallet == null ? ( + + ) : ( + + )} - + + + + + - + {toWallet == null ? ( + + ) : ( + + )} {renderAlert()} {renderButton()} ) } - -const getStyles = cacheStyles((theme: Theme) => ({ - mainScrollView: { - flex: 1 - }, - header: { - marginLeft: -theme.rem(0.5), - width: '100%', - marginVertical: theme.rem(1) - }, - scrollViewContentContainer: { - alignItems: 'center', - marginHorizontal: theme.rem(0.5) - } -})) diff --git a/src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx b/src/components/scenes/SwapProcessingScene.tsx similarity index 95% rename from src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx rename to src/components/scenes/SwapProcessingScene.tsx index cdb082bea58..e994bd9c472 100644 --- a/src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx +++ b/src/components/scenes/SwapProcessingScene.tsx @@ -27,20 +27,20 @@ import { Airship } from '../services/AirshipInstance' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' import { EdgeText } from '../themed/EdgeText' import { ButtonsViewUi4 } from '../ui4/ButtonsViewUi4' -import { SwapErrorDisplayInfo } from './CryptoExchangeScene' +import { SwapErrorDisplayInfo } from './SwapCreateScene' -export interface ExchangeQuoteProcessingParams { +export interface SwapProcessingParams { swapRequest: EdgeSwapRequest swapRequestOptions: EdgeSwapRequestOptions onCancel: () => void onDone: (quotes: EdgeSwapQuote[]) => void } -interface Props extends EdgeSceneProps<'exchangeQuoteProcessing'> {} +interface Props extends EdgeSceneProps<'swapProcessing'> {} const ANIM_DURATION = 5000 -export function CryptoExchangeQuoteProcessingScene(props: Props) { +export function SwapProcessingScene(props: Props) { const theme = useTheme() const styles = getStyles(theme) const { route, navigation } = props @@ -81,8 +81,8 @@ export function CryptoExchangeQuoteProcessingScene(props: Props) { toDenomination }) - navigation.navigate('exchangeTab', { - screen: 'exchange', + navigation.navigate('swapTab', { + screen: 'swapCreate', params: { fromWalletId: swapRequest.fromWallet.id, fromTokenId: swapRequest.fromTokenId, @@ -105,7 +105,7 @@ export function CryptoExchangeQuoteProcessingScene(props: Props) { } }, [swapRequest, swapRequestOptions, onDone], - 'CryptoExchangeQuoteProcessingScene' + 'SwapProcessingScene' ) return ( diff --git a/src/components/scenes/SwapSettingsScene.tsx b/src/components/scenes/SwapSettingsScene.tsx index c7d405a841b..a50fb9c55c8 100644 --- a/src/components/scenes/SwapSettingsScene.tsx +++ b/src/components/scenes/SwapSettingsScene.tsx @@ -26,7 +26,7 @@ import { SettingsSubHeader } from '../settings/SettingsSubHeader' import { SettingsSwitchRow } from '../settings/SettingsSwitchRow' import { SettingsTappableRow } from '../settings/SettingsTappableRow' -interface OwnProps extends EdgeSceneProps<'exchangeSettings'> {} +interface OwnProps extends EdgeSceneProps<'swapSettings'> {} interface DispatchProps { changePreferredSwapPlugin: (pluginId: string | undefined) => void diff --git a/src/components/scenes/CryptoExchangeSuccessScene.tsx b/src/components/scenes/SwapSuccessScene.tsx similarity index 90% rename from src/components/scenes/CryptoExchangeSuccessScene.tsx rename to src/components/scenes/SwapSuccessScene.tsx index f8665a39f83..12f3732a893 100644 --- a/src/components/scenes/CryptoExchangeSuccessScene.tsx +++ b/src/components/scenes/SwapSuccessScene.tsx @@ -14,7 +14,7 @@ import { EdgeText } from '../themed/EdgeText' import { Fade } from '../themed/Fade' import { MainButton } from '../themed/MainButton' -interface OwnProps extends EdgeSceneProps<'exchangeSuccess'> {} +interface OwnProps extends EdgeSceneProps<'swapSuccess'> {} interface StateProps { userId: string @@ -34,7 +34,7 @@ const confettiProps = { fallSpeed: 4000 } -export class CryptoExchangeSuccessComponent extends React.PureComponent { +export class SwapSuccessSceneComponent extends React.PureComponent { constructor() { // @ts-expect-error super() @@ -48,7 +48,7 @@ export class CryptoExchangeSuccessComponent extends React.PureComponent { const { navigation } = this.props this.setState({ showButton: false }) - navigation.navigate('exchangeTab', { screen: 'exchange' }) + navigation.navigate('swapTab', { screen: 'swapCreate' }) } showConfetti = async () => { @@ -119,10 +119,10 @@ const getStyles = cacheStyles((theme: Theme) => ({ } })) -export const CryptoExchangeSuccessScene = connect( +export const SwapSuccessScene = connect( state => ({ userId: state.core.account.id, disklet: state.core.disklet }), dispatch => ({}) -)(withTheme(CryptoExchangeSuccessComponent)) +)(withTheme(SwapSuccessSceneComponent)) diff --git a/src/components/themed/CryptoExchangeFlipInput.tsx b/src/components/themed/CryptoExchangeFlipInput.tsx deleted file mode 100644 index 2550fd1d7c7..00000000000 --- a/src/components/themed/CryptoExchangeFlipInput.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { add } from 'biggystring' -import { EdgeCurrencyWallet, EdgeDenomination } from 'edge-core-js' -import * as React from 'react' -import { useMemo, useState } from 'react' -import { ActivityIndicator, View } from 'react-native' - -import { formatNumber } from '../../locales/intl' -import { lstrings } from '../../locales/strings' -import { useSelector } from '../../types/reactRedux' -import { getTokenIdForced } from '../../util/CurrencyInfoHelpers' -import { getWalletName } from '../../util/CurrencyWalletHelpers' -import { convertNativeToDenomination } from '../../util/utils' -import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' -import { CardUi4 } from '../ui4/CardUi4' -import { CryptoIconUi4 } from '../ui4/CryptoIconUi4' -import { RowUi4 } from '../ui4/RowUi4' -import { EdgeText } from './EdgeText' -import { ExchangedFlipInput2, ExchangedFlipInputAmounts } from './ExchangedFlipInput2' -import { MainButton } from './MainButton' - -interface Props { - wallet?: EdgeCurrencyWallet - buttonText: string - headerText: string - currencyCode: string - displayDenomination: EdgeDenomination - overridePrimaryNativeAmount: string - isFocused: boolean - isThinking?: boolean - onFocuseWallet: () => void - onSelectWallet: () => void - onAmountChanged: (amounts: ExchangedFlipInputAmounts) => void - onNext: () => void - onFocus?: () => void - onBlur?: () => void - children?: React.ReactNode -} - -export const CryptoExchangeFlipInput = (props: Props) => { - const { children, currencyCode, displayDenomination, onNext, overridePrimaryNativeAmount, wallet } = props - - const theme = useTheme() - const styles = getStyles(theme) - - // - // State - // - - const [errorMessage, setErrorMessage] = useState('') - - // - // Derived State - // - - const account = useSelector(state => state.core.account) - - const tokenId = useMemo(() => { - if (wallet == null) return null - // This will error if wallet is undefined - return getTokenIdForced(account, wallet.currencyInfo.pluginId, currencyCode) - }, [account, currencyCode, wallet]) - - const cryptoAmount = useMemo(() => { - if (wallet == null || tokenId === undefined) return - const balance = wallet.balanceMap.get(tokenId) ?? '0' - const cryptoAmountRaw: string = convertNativeToDenomination(displayDenomination.multiplier)(balance) - return formatNumber(add(cryptoAmountRaw, '0')) - }, [displayDenomination.multiplier, tokenId, wallet]) - - const guiWalletName = wallet == null ? undefined : getWalletName(wallet) - - // - // Handlers - // - - const handleAmountsChanged = (amounts: ExchangedFlipInputAmounts) => { - props.onAmountChanged(amounts) - } - - const launchSelector = () => { - setErrorMessage('') - props.onSelectWallet() - } - - const focusMe = () => { - setErrorMessage('') - props.onFocuseWallet() - } - - // - // Render - // - - const renderBalance = () => { - if (cryptoAmount == null) { - return null - } - - return {lstrings.string_wallet_balance + ': ' + cryptoAmount + ' ' + displayDenomination.name} - } - - if (props.isThinking) { - return ( - - - - - - ) - } - - if (wallet == null) { - return - } - - if (!props.isFocused) { - return ( - - } onPress={focusMe}> - {guiWalletName + ': ' + currencyCode} - - - ) - } - - return ( - <> - {errorMessage} - {renderBalance()} - - - {children} - - - ) -} - -const getStyles = cacheStyles((theme: Theme) => ({ - container: { - width: '100%' - }, - containerNoFee: { - backgroundColor: theme.tileBackground, - borderRadius: 3 - }, - containerNoWalletSelected: { - paddingVertical: theme.rem(0.75), - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center' - }, - text: { - fontFamily: theme.fontFaceMedium, - fontSize: theme.rem(1), - marginLeft: theme.rem(0.5) - }, - topRow: { - height: theme.rem(2), - flexDirection: 'column', - justifyContent: 'space-around', - alignItems: 'center' - }, - iconContainer: { - top: theme.rem(0.125), - borderRadius: theme.rem(1) - }, - balanceText: { - alignSelf: 'flex-start', - marginLeft: theme.rem(1), - color: theme.secondaryText - }, - errorText: { - alignSelf: 'flex-start', - marginLeft: theme.rem(0.5), - marginBottom: theme.rem(0.75), - color: theme.dangerText - } -})) diff --git a/src/components/themed/ExchangedFlipInput2.tsx b/src/components/themed/ExchangedFlipInput2.tsx index 3d19717fe41..e77898aa2a4 100644 --- a/src/components/themed/ExchangedFlipInput2.tsx +++ b/src/components/themed/ExchangedFlipInput2.tsx @@ -136,7 +136,7 @@ const ExchangedFlipInput2Component = React.forwardRef { + const convertValue = useHandler(async (fieldNum: number, amount: string): Promise => { if (amount === '') { onAmountChanged({ exchangeAmount: '', @@ -169,8 +169,6 @@ const ExchangedFlipInput2Component = React.forwardRef => convertValueSync(fieldNum, amount) - React.useEffect(() => { const { exchangeAmount, displayAmount } = convertFromCryptoNative(startNativeAmount ?? '') const initFiat = convertCurrency(exchangeAmount, cryptoCurrencyCode, fiatCurrencyCode) @@ -214,7 +212,7 @@ const ExchangedFlipInput2Component = React.forwardRef void - onFocus?: () => void - onNext?: () => void convertValue: (sourceFieldNum: FieldNum, value: string) => Promise - startAmounts: [string, string] + disabled?: boolean + fieldInfos: FlipInputFieldInfos forceFieldNum?: FieldNum - keyboardVisible?: boolean inputAccessoryViewID?: string - fieldInfos: FlipInputFieldInfos + keyboardVisible?: boolean + placeholders?: [string, string] returnKeyType?: ReturnKeyType - editable?: boolean + startAmounts: [string, string] + + // Renders: + renderFooter?: () => React.ReactNode + renderHeader?: () => React.ReactNode + renderIcon?: () => React.ReactNode + + // Events: + onBlur?: () => void + onFocus?: () => void + onNext?: () => void } const FLIP_DURATION = 300 @@ -62,32 +72,56 @@ const flipField = (fieldNum: FieldNum): FieldNum => { export const FlipInput2 = React.forwardRef((props: Props, ref) => { const theme = useTheme() + const themeRem = theme.rem(1) const inputRefs = [React.useRef(null), React.useRef(null)] const { - startAmounts, + convertValue, + disabled = false, fieldInfos, + forceFieldNum = 0, + inputAccessoryViewID, keyboardVisible, + placeholders = [lstrings.string_tap_to_edit, ''], returnKeyType = 'done', + startAmounts, + + // Renders: + renderFooter, + renderHeader, + renderIcon, + + // Events: onBlur, onFocus, - onNext, - inputAccessoryViewID, - convertValue, - forceFieldNum = 0, - editable + onNext } = props const animatedValue = useSharedValue(forceFieldNum) // `amounts` is always a 2-tuple const [amounts, setAmounts] = useState<[string, string]>(startAmounts) + const hasAmount = !zeroString(amounts[0]) + // primaryField is the index into the 2-tuple, 0 or 1 const [primaryField, setPrimaryField] = useState(forceFieldNum) + // Animates between 0 and 1 based our disabled state: + const disableAnimation = useSharedValue(0) + React.useEffect(() => { + disableAnimation.value = withTiming(disabled ? 1 : 0) + }, [disableAnimation, disabled]) + const [amountFocused, setAmountFocused] = useState(false) const focusAnimation = useSharedValue(0) + const interpolateIconColor = useAnimatedColorInterpolateFn(theme.textInputIconColor, theme.textInputIconColorFocused, theme.textInputIconColorDisabled) + const clearIconColor = useDerivedValue(() => interpolateIconColor(focusAnimation, disableAnimation)) + const clearIconScale = useDerivedValue(() => (hasAmount ? 1 : focusAnimation.value), [hasAmount]) + // We have to use a SharedValue for the icon size event though it's not animated, + // just because that is the expected type + const clearIconSize = useSharedValue(themeRem) + const onToggleFlipInput = useHandler(() => { const otherField = primaryField === 1 ? 0 : 1 inputRefs[otherField]?.current?.focus() @@ -133,10 +167,16 @@ export const FlipInput2 = React.forwardRef((props: Props, r if (onBlur != null) onBlur() }) + const handleClearPress = useHandler(() => { + const newAmounts: [string, string] = ['', ''] + setAmounts(newAmounts) + }) + const renderBottomRow = (fieldNum: FieldNum) => { const zeroAmount = zeroString(amounts[fieldNum]) const primaryAmount = zeroAmount && !amountFocused ? '' : amounts[fieldNum] + const placeholder = placeholders[0] const isEnterTextMode = amountFocused || !zeroAmount const currencyName = fieldInfos[fieldNum].currencyName @@ -144,11 +184,12 @@ export const FlipInput2 = React.forwardRef((props: Props, r ((props: Props, r onFocus={handleBottomFocus} onBlur={handleBottomBlur} /> - {!isEnterTextMode ? {lstrings.string_tap_to_edit} : null} - {isEnterTextMode ? {' ' + currencyName} : null} + {!isEnterTextMode && placeholder !== '' ? {placeholder} : null} + {isEnterTextMode ? ( + + {' ' + currencyName} + + ) : null} ) } @@ -169,12 +214,17 @@ export const FlipInput2 = React.forwardRef((props: Props, r topText = formatNumberInput(topText, { minDecimals: 0, maxDecimals: fieldInfos[fieldNum].maxEntryDecimals }) } + const placeholder = placeholders[1] + const zeroAmount = zeroString(amounts[fieldNum]) + const isEnterTextMode = amountFocused || !zeroAmount + const fieldInfo = fieldInfos[fieldNum] topText = `${topText} ${fieldInfo.currencyName}` + return ( - + - {topText} + {isEnterTextMode || placeholder === '' ? topText : placeholder} ) @@ -187,10 +237,16 @@ export const FlipInput2 = React.forwardRef((props: Props, r })) return ( - <> - + + {renderHeader != null ? renderHeader() : null} + + + + {renderIcon ? renderIcon() : } + + inputRefs[primaryField].current?.focus()}> - + {renderTopRow(1)} {renderBottomRow(0)} @@ -199,50 +255,76 @@ export const FlipInput2 = React.forwardRef((props: Props, r {renderTopRow(0)} {renderBottomRow(1)} - + - - - - - - + + + + + + + + {renderFooter != null ? renderFooter() : null} + ) }) const AnimatedNumericInput = Animated.createAnimatedComponent(NumericInput) -const ContainerView = styled(View)(theme => ({ - flexDirection: 'row', - alignItems: 'center', - margin: theme.rem(0.5) -})) - -const InnerView = styled(Animated.View)<{ +const ContainerView = styled(Animated.View)<{ + disableAnimation: SharedValue focusAnimation: SharedValue -}>(theme => ({ focusAnimation }) => { - const interpolateInputBackgroundColor = useAnimatedColorInterpolateFn(theme.textInputBackgroundColor, theme.textInputBackgroundColorFocused) - const interpolateOutlineColor = useAnimatedColorInterpolateFn(theme.textInputBorderColor, theme.textInputBorderColorFocused) +}>(theme => ({ disableAnimation, focusAnimation }) => { + const interpolateInputBackgroundColor = useAnimatedColorInterpolateFn( + theme.textInputBackgroundColor, + theme.textInputBackgroundColorFocused, + theme.textInputBackgroundColorDisabled + ) + const interpolateOutlineColor = useAnimatedColorInterpolateFn( + theme.textInputBorderColor, + theme.textInputBorderColorFocused, + theme.textInputBorderColorDisabled + ) + return [ { - alignItems: 'center', + flexDirection: 'column', + alignItems: 'stretch', + margin: theme.rem(0.5), + borderWidth: theme.textInputBorderWidth, borderRadius: theme.rem(0.5), - flex: 1, - flexDirection: 'row', overflow: 'hidden' }, + useAnimatedStyle(() => ({ - backgroundColor: interpolateInputBackgroundColor(focusAnimation), - borderColor: interpolateOutlineColor(focusAnimation) + backgroundColor: interpolateInputBackgroundColor(focusAnimation, disableAnimation), + borderColor: interpolateOutlineColor(focusAnimation, disableAnimation) })) ] }) +const InputContainerView = styled(View)(theme => ({ + flexDirection: 'row', + alignItems: 'center' +})) + +const InputTextView = styled(Animated.View)<{ + disableAnimation: SharedValue + focusAnimation: SharedValue +}>(theme => { + return { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + overflow: 'hidden' + } +}) + const FrontAnimatedView = styled(Animated.View)<{ animatedValue: SharedValue }>(theme => ({ animatedValue }) => [ { backfaceVisibility: 'hidden', - paddingHorizontal: theme.rem(1), + paddingRight: theme.rem(1), paddingVertical: theme.rem(0.5) }, useAnimatedStyle(() => { @@ -256,7 +338,7 @@ const FrontAnimatedView = styled(Animated.View)<{ animatedValue: SharedValue }>(theme => ({ animatedValue }) => [ { backfaceVisibility: 'hidden', - paddingHorizontal: theme.rem(1), + paddingRight: theme.rem(1), paddingVertical: theme.rem(0.5), position: 'absolute', top: 0, @@ -282,31 +364,32 @@ const TopAmountText = styled(Text)(theme => () => [ } ]) -const AmountAnimatedNumericInput = styledWithRef(AnimatedNumericInput)<{ focusAnimation: SharedValue; value: string }>( - theme => - ({ focusAnimation, value }) => { - const isAndroid = Platform.OS === 'android' - const interpolateTextColor = useAnimatedColorInterpolateFn(theme.textInputTextColor, theme.textInputTextColorFocused) - const characterLength = value.length - return [ - { - includeFontPadding: false, - fontFamily: theme.fontFaceMedium, - fontSize: theme.rem(1.5), - // Android has more space added to the width of the input - // after the last character in the input. It seems to be - // setting a min-width to the input to roughly 2 characters in size. - // We can compensate for this with a negative margin when the character length - // is less then 2 characters. - marginRight: isAndroid ? -theme.rem(Math.max(0, 2 - characterLength) * 0.4) : 0, - padding: 0 - }, - useAnimatedStyle(() => ({ - color: interpolateTextColor(focusAnimation) - })) - ] - } -) +const AmountAnimatedNumericInput = styledWithRef(AnimatedNumericInput)<{ + disableAnimation: SharedValue + focusAnimation: SharedValue + value: string +}>(theme => ({ disableAnimation, focusAnimation, value }) => { + const isAndroid = Platform.OS === 'android' + const interpolateTextColor = useAnimatedColorInterpolateFn(theme.textInputTextColor, theme.textInputTextColorFocused, theme.textInputTextColorDisabled) + const characterLength = value.length + return [ + { + includeFontPadding: false, + fontFamily: theme.fontFaceMedium, + fontSize: theme.rem(1.5), + // Android has more space added to the width of the input + // after the last character in the input. It seems to be + // setting a min-width to the input to roughly 2 characters in size. + // We can compensate for this with a negative margin when the character length + // is less then 2 characters. + marginRight: isAndroid ? -theme.rem(Math.max(0, 2 - characterLength) * 0.4) : 0, + padding: 0 + }, + useAnimatedStyle(() => ({ + color: interpolateTextColor(focusAnimation, disableAnimation) + })) + ] +}) const PlaceholderAnimatedText = styled(Animated.Text)(theme => ({ position: 'absolute', @@ -318,8 +401,11 @@ const PlaceholderAnimatedText = styled(Animated.Text)(theme => ({ fontSize: theme.rem(1.5) })) -const CurrencySymbolAnimatedText = styled(Animated.Text)<{ focusAnimation: SharedValue }>(theme => ({ focusAnimation }) => { - const interpolateTextColor = useAnimatedColorInterpolateFn(theme.textInputTextColor, theme.textInputTextColorFocused) +const CurrencySymbolAnimatedText = styled(Animated.Text)<{ + disableAnimation: SharedValue + focusAnimation: SharedValue +}>(theme => ({ disableAnimation, focusAnimation }) => { + const interpolateTextColor = useAnimatedColorInterpolateFn(theme.textInputTextColor, theme.textInputTextColorFocused, theme.textInputTextColorDisabled) return [ { fontFamily: theme.fontFaceMedium, @@ -327,7 +413,7 @@ const CurrencySymbolAnimatedText = styled(Animated.Text)<{ focusAnimation: Share includeFontPadding: false }, useAnimatedStyle(() => ({ - color: interpolateTextColor(focusAnimation) + color: interpolateTextColor(focusAnimation, disableAnimation) })) ] }) @@ -344,13 +430,27 @@ const BottomContainerView = styled(View)({ alignItems: 'center' }) -function useAnimatedColorInterpolateFn(fromColor: string, toColor: string) { +const SideContainer = styled(Animated.View)<{ scale: SharedValue }>(theme => ({ scale }) => { + return [ + { + alignSelf: 'stretch', + justifyContent: 'center', + paddingHorizontal: theme.rem(1) + }, + useAnimatedStyle(() => ({ + transform: [{ scale: scale.value }] + })) + ] +}) + +function useAnimatedColorInterpolateFn(defaultColor: string, focusColor: string, disableColor: string) { const interpolateFn = useMemo(() => { - return (focusValue: SharedValue) => { + return (focusValue: SharedValue, disabledValue: SharedValue) => { 'worklet' - return interpolateColor(focusValue.value, [0, 1], [fromColor, toColor]) + const interFocusColor = interpolateColor(focusValue.value, [0, 1], [defaultColor, focusColor]) + return interpolateColor(disabledValue.value, [0, 1], [interFocusColor, disableColor]) } - }, [fromColor, toColor]) + }, [defaultColor, focusColor, disableColor]) return interpolateFn } diff --git a/src/components/themed/LineTextDivider.tsx b/src/components/themed/LineTextDivider.tsx index 877220ebc4f..7aa2760bed0 100644 --- a/src/components/themed/LineTextDivider.tsx +++ b/src/components/themed/LineTextDivider.tsx @@ -26,7 +26,7 @@ export const LineTextDividerComponent = (props: Props) => { const getStyles = cacheStyles((theme: Theme) => ({ container: { - width: '100%', + flexGrow: 1, paddingHorizontal: theme.rem(0.5), flexDirection: 'row', justifyContent: 'space-between', diff --git a/src/components/themed/MenuTabs.tsx b/src/components/themed/MenuTabs.tsx index 3f19d8c6022..b0e83ce3a8e 100644 --- a/src/components/themed/MenuTabs.tsx +++ b/src/components/themed/MenuTabs.tsx @@ -41,7 +41,7 @@ const title: { readonly [key: string]: string } = { walletsTab: lstrings.title_assets, buyTab: lstrings.title_buy, sellTab: lstrings.title_sell, - exchangeTab: lstrings.title_exchange, + swapTab: lstrings.title_exchange, extraTab: lstrings[extraTabString], devTab: lstrings.title_dev_tab } @@ -59,7 +59,7 @@ export const MenuTabs = (props: BottomTabBarProps) => { if (!ENV.DEV_TAB && route.name === 'devTab') { return false } - if (config.disableSwaps === true && route.name === 'exchangeTab') { + if (config.disableSwaps === true && route.name === 'swapTab') { return false } return true @@ -185,7 +185,7 @@ const Tab = ({ walletsTab: , buyTab: , sellTab: , - exchangeTab: , + swapTab: , extraTab: , devTab: } @@ -200,8 +200,8 @@ const Tab = ({ return navigation.navigate('buyTab', currentName === 'buyTab' ? { screen: 'pluginListBuy' } : {}) case 'sellTab': return navigation.navigate('sellTab', currentName === 'sellTab' ? { screen: 'pluginListSell' } : {}) - case 'exchangeTab': - return navigation.navigate('exchangeTab', currentName === 'exchangeTab' ? { screen: 'exchange' } : {}) + case 'swapTab': + return navigation.navigate('swapTab', currentName === 'swapTab' ? { screen: 'swapCreate' } : {}) case 'extraTab': return navigation.navigate('extraTab') case 'devTab': diff --git a/src/components/themed/SwapInput.tsx b/src/components/themed/SwapInput.tsx new file mode 100644 index 00000000000..a3ae2b25dd0 --- /dev/null +++ b/src/components/themed/SwapInput.tsx @@ -0,0 +1,305 @@ +import { div, log10, mul, round } from 'biggystring' +import { EdgeCurrencyWallet, EdgeTokenId } from 'edge-core-js' +import React, { useMemo } from 'react' +import { ReturnKeyType, Text, TouchableOpacity, View } from 'react-native' + +import { useHandler } from '../../hooks/useHandler' +import { useWatch } from '../../hooks/useWatch' +import { lstrings } from '../../locales/strings' +import { getExchangeDenom, selectDisplayDenom } from '../../selectors/DenominationSelectors' +import { useSelector } from '../../types/reactRedux' +import { getCurrencyCode } from '../../util/CurrencyInfoHelpers' +import { DECIMAL_PRECISION, getDenomFromIsoCode, maxPrimaryCurrencyConversionDecimals, precisionAdjust } from '../../util/utils' +import { styled } from '../hoc/styled' +import { Space } from '../layout/Space' +import { CryptoIconUi4 } from '../ui4/CryptoIconUi4' +import { EdgeText } from './EdgeText' +import { FieldNum, FlipInput2, FlipInputFieldInfos, FlipInputRef } from './FlipInput2' +import { ButtonBox } from './ThemedButtons' + +export type ExchangeFlipInputFields = 'fiat' | 'crypto' + +export interface SwapInputCardInputRef { + setAmount: (field: ExchangeFlipInputFields, value: string) => void +} + +export interface SwapInputCardAmounts { + exchangeAmount: string + nativeAmount: string + fiatAmount: string + fieldChanged: 'fiat' | 'crypto' +} + +export interface Props { + disabled?: boolean + heading: string + forceField?: 'fiat' | 'crypto' + inputAccessoryViewID?: string + keyboardVisible?: boolean + returnKeyType?: ReturnKeyType + startNativeAmount?: string + tokenId: EdgeTokenId + wallet: EdgeCurrencyWallet + walletPlaceholderText: string + // Events: + onAmountChanged: (amounts: SwapInputCardAmounts) => unknown + onBlur?: () => void + onFocus?: () => void + onMaxPress?: () => void + onNext?: () => void + onSelectWallet: () => void +} + +const forceFieldMap: { crypto: FieldNum; fiat: FieldNum } = { + crypto: 0, + fiat: 1 +} + +const SwapInputComponent = React.forwardRef((props: Props, ref) => { + const { + disabled, + forceField = 'crypto', + heading, + inputAccessoryViewID, + keyboardVisible = true, + startNativeAmount, + returnKeyType, + tokenId, + wallet, + walletPlaceholderText, + // Events: + onAmountChanged, + onBlur, + onFocus, + onMaxPress, + onNext + } = props + + const exchangeRates = useSelector(state => state.exchangeRates) + const fiatCurrencyCode = useWatch(wallet, 'fiatCurrencyCode') + const flipInputRef = React.useRef(null) + + const cryptoDisplayDenom = useSelector(state => selectDisplayDenom(state, wallet.currencyConfig, tokenId)) + const fiatDenom = getDenomFromIsoCode(fiatCurrencyCode) + + const fieldInfos: FlipInputFieldInfos = [ + { currencyName: cryptoDisplayDenom.name, maxEntryDecimals: log10(cryptoDisplayDenom.multiplier) }, + { currencyName: fiatDenom.name.replace('iso:', ''), maxEntryDecimals: log10(fiatDenom.multiplier) } + ] + + const convertCurrency = useHandler((amount: string, fromCurrencyCode: string, toCurrencyCode: string): string => { + const rateKey = `${fromCurrencyCode}_${toCurrencyCode}` + const rate = exchangeRates[rateKey] ?? '0' + return mul(amount, rate) + }) + + const convertFromCryptoNative = useHandler((nativeAmount: string) => { + if (nativeAmount === '') return { fiatAmount: '', exchangeAmount: '', displayAmount: '' } + + const cryptoCurrencyCode = getCurrencyCode(wallet, tokenId) + const cryptoExchangeDenom = getExchangeDenom(wallet.currencyConfig, tokenId) + const exchangeAmount = div(nativeAmount, cryptoExchangeDenom.multiplier, DECIMAL_PRECISION) + const displayAmount = div(nativeAmount, cryptoDisplayDenom.multiplier, DECIMAL_PRECISION) + const fiatAmountLong = convertCurrency(exchangeAmount, cryptoCurrencyCode, wallet.fiatCurrencyCode) + const fiatAmount = round(fiatAmountLong, -2) + return { fiatAmount, exchangeAmount, displayAmount } + }) + + const convertFromFiat = useHandler((fiatAmount: string) => { + if (fiatAmount === '') return { nativeAmount: '', exchangeAmount: '', displayAmount: '' } + + const cryptoCurrencyCode = getCurrencyCode(wallet, tokenId) + const cryptoExchangeDenom = getExchangeDenom(wallet.currencyConfig, tokenId) + const exchangeAmountLong = convertCurrency(fiatAmount, wallet.fiatCurrencyCode, cryptoCurrencyCode) + const nativeAmountLong = mul(exchangeAmountLong, cryptoExchangeDenom.multiplier) + const displayAmountLong = div(nativeAmountLong, cryptoDisplayDenom.multiplier, DECIMAL_PRECISION) + + const precisionAdjustVal = precisionAdjust({ + primaryExchangeMultiplier: cryptoExchangeDenom.multiplier, + secondaryExchangeMultiplier: fiatDenom.multiplier, + exchangeSecondaryToPrimaryRatio: exchangeRates[`${cryptoCurrencyCode}_${fiatCurrencyCode}`] + }) + const cryptoMaxPrecision = maxPrimaryCurrencyConversionDecimals(log10(cryptoDisplayDenom.multiplier), precisionAdjustVal) + + // Apply cryptoMaxPrecision to remove extraneous sub-penny precision + const displayAmount = round(displayAmountLong, -cryptoMaxPrecision) + + // Convert back to native and exchange amounts after cryptoMaxPrecision has been applied + const nativeAmount = mul(displayAmount, cryptoDisplayDenom.multiplier) + const exchangeAmount = div(nativeAmount, cryptoExchangeDenom.multiplier, DECIMAL_PRECISION) + return { displayAmount, nativeAmount, exchangeAmount } + }) + + const convertValue = useHandler(async (fieldNum: number, amount: string): Promise => { + if (amount === '') { + onAmountChanged({ + exchangeAmount: '', + nativeAmount: '', + fiatAmount: '', + fieldChanged: fieldNum ? 'fiat' : 'crypto' + }) + return '' + } + if (fieldNum === 0) { + const nativeAmount = mul(amount, cryptoDisplayDenom.multiplier) + const { fiatAmount, exchangeAmount } = convertFromCryptoNative(nativeAmount) + onAmountChanged({ + exchangeAmount, + nativeAmount, + fiatAmount, + fieldChanged: 'crypto' + }) + + return fiatAmount + } else { + const { nativeAmount, exchangeAmount, displayAmount } = convertFromFiat(amount) + onAmountChanged({ + exchangeAmount, + nativeAmount, + fiatAmount: amount, + fieldChanged: 'fiat' + }) + return displayAmount + } + }) + + const handleWalletPlaceholderPress = () => { + props.onSelectWallet() + } + + const { initialExchangeAmount, initialDisplayAmount } = React.useMemo(() => { + const { exchangeAmount, displayAmount } = convertFromCryptoNative(startNativeAmount ?? '') + return { initialExchangeAmount: exchangeAmount, initialDisplayAmount: displayAmount } + }, [convertFromCryptoNative, startNativeAmount]) + + const initialFiatAmount = React.useMemo(() => { + const cryptoCurrencyCode = getCurrencyCode(wallet, tokenId) + const fiatAmount = convertCurrency(initialExchangeAmount, cryptoCurrencyCode, wallet.fiatCurrencyCode) + return fiatAmount + }, [convertCurrency, initialExchangeAmount, tokenId, wallet]) + + React.useImperativeHandle(ref, () => ({ + setAmount: (field, value) => { + if (field === 'crypto') { + const { displayAmount, fiatAmount } = convertFromCryptoNative(value) + flipInputRef.current?.setAmounts([displayAmount, fiatAmount]) + } else if (field === 'fiat') { + const { displayAmount } = convertFromFiat(value) + flipInputRef.current?.setAmounts([displayAmount, value]) + } + } + })) + + /** + * Override the 'forceField' prop in some cases. + * If we set 'forceField' to fiat and we don't yet have exchange rates, ensure + * that we force the user to input a crypto amount, even if the caller wanted + * to initialize the focused flip input field with fiat. + */ + const overrideForceField = useMemo(() => { + const cryptoCurrencyCode = getCurrencyCode(wallet, tokenId) + const fiatValue = convertCurrency('100', cryptoCurrencyCode, wallet.fiatCurrencyCode) + return fiatValue === '0' ? 'crypto' : forceField + }, [convertCurrency, forceField, tokenId, wallet]) + + const renderHeader = () => { + return ( +
+ {heading} + + + {walletPlaceholderText} + + +
+ ) + } + + const renderFooter = () => { + return + } + + const renderIcon = () => { + return + } + + return ( + <> + + {onMaxPress == null ? null : ( + + + {lstrings.string_max_cap} + + + )} + + ) +}) + +export const SwapInput = React.memo(SwapInputComponent) + +const Header = styled(View)(theme => ({ + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + margin: theme.rem(1), + marginBottom: theme.rem(0.25) +})) + +const CardHeading = styled(EdgeText)(theme => ({ + color: theme.secondaryText +})) + +const WalletPlaceHolder = styled(TouchableOpacity)(theme => ({ + alignItems: 'center', + backgroundColor: theme.cardBaseColor, + borderRadius: 100, + flexDirection: 'row', + paddingHorizontal: theme.rem(0.75), + paddingVertical: theme.rem(0.25) +})) + +const WalletPlaceHolderText = styled(EdgeText)(theme => ({ + fontSize: theme.rem(0.75), + lineHeight: theme.rem(1.5) +})) + +const MaxButtonContainerView = styled(View)<{ danger?: boolean }>(theme => ({ + flexDirection: 'row', + height: 0, + justifyContent: 'flex-end', + overflow: 'visible' +})) + +const MaxButtonText = styled(Text)<{ danger?: boolean }>(theme => ({ + color: theme.escapeButtonText, + fontFamily: theme.fontFaceDefault, + fontSize: theme.rem(0.75), + height: theme.rem(1), + includeFontPadding: false +})) + +// This space is used to give the FlipInput2 roughly 1 rem bottom padding to +// match the top padding from the header. +const FooterSpace = styled(View)(theme => ({ + height: theme.rem(0.5) +})) diff --git a/src/components/ui4/scenes/HomeSceneUi4.tsx b/src/components/ui4/scenes/HomeSceneUi4.tsx index 4db9bc73142..03ded128230 100644 --- a/src/components/ui4/scenes/HomeSceneUi4.tsx +++ b/src/components/ui4/scenes/HomeSceneUi4.tsx @@ -55,7 +55,7 @@ export const HomeSceneUi4 = (props: Props) => { navigation.navigate('fioAddressList', {}) }) const handleSwapPress = useHandler(() => { - navigation.navigate('exchangeTab', {}) + navigation.navigate('swapTab', {}) }) const handleViewAssetsPress = useHandler(() => { navigation.navigate('walletsTab', { screen: 'walletList' }) diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index a8f07131740..cad43e0974d 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -591,6 +591,7 @@ const strings = { string_to_capitalize: 'To', string_show_balance: 'Show Balance', string_amount: 'Amount', + string_tap_next_for_quote: 'Tap "Next" for Quote', string_tap_to_edit: 'Tap to edit', string_rate: 'Rate', string_got_it: 'Got it!', @@ -921,8 +922,6 @@ const strings = { tap_to_change_provider: 'Tap to Change Provider', send_from_wallet: 'Send from %s', send_to_wallet: 'Receive to %s', - exchange_from_wallet: 'Exchange from %s', - exchange_to_wallet: 'Exchange to %s', exchange_slow: 'Locating a swap is taking longer than usual.\nPlease wait...', wallet_list_modal_header_mru: 'Most Recent Wallets', wallet_list_modal_header_all: 'All Wallets', @@ -1283,6 +1282,8 @@ const strings = { exchange_insufficient_funds_below_balance: 'Wallet balance is below the amount entered.', exchange_select_sending_wallet: 'Please select a wallet to swap from', exchange_select_receiving_wallet: 'Please select a wallet to swap to', + exchange_title_receiving: 'I want', + exchange_title_sending: 'I have', // WalletConnect Scenes/Modals wc_walletconnect_title: 'WalletConnect', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index d21bd58eb97..e4fbe8eb95d 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -523,6 +523,7 @@ "string_to_capitalize": "To", "string_show_balance": "Show Balance", "string_amount": "Amount", + "string_tap_next_for_quote": "Tap \"Next\" for Quote", "string_tap_to_edit": "Tap to edit", "string_rate": "Rate", "string_got_it": "Got it!", @@ -819,8 +820,6 @@ "tap_to_change_provider": "Tap to Change Provider", "send_from_wallet": "Send from %s", "send_to_wallet": "Receive to %s", - "exchange_from_wallet": "Exchange from %s", - "exchange_to_wallet": "Exchange to %s", "exchange_slow": "Locating a swap is taking longer than usual.\nPlease wait...", "wallet_list_modal_header_mru": "Most Recent Wallets", "wallet_list_modal_header_all": "All Wallets", @@ -1134,6 +1133,8 @@ "exchange_insufficient_funds_below_balance": "Wallet balance is below the amount entered.", "exchange_select_sending_wallet": "Please select a wallet to swap from", "exchange_select_receiving_wallet": "Please select a wallet to swap to", + "exchange_title_receiving": "I want", + "exchange_title_sending": "I have", "wc_walletconnect_title": "WalletConnect", "wc_walletconnect_subtitle": "Connect your wallet to make transactions.", "wc_walletconnect_new_connection_button": "New Connection", diff --git a/src/theme/variables/edgeDark.ts b/src/theme/variables/edgeDark.ts index 1a8f7076c4d..343a0ff18f9 100644 --- a/src/theme/variables/edgeDark.ts +++ b/src/theme/variables/edgeDark.ts @@ -410,10 +410,10 @@ export const edgeDark: Theme = { textInputTextColorDisabled: palette.gray, textInputTextColorFocused: palette.white, textInputBackgroundColor: palette.graySecondary, - textInputBackgroundColorDisabled: palette.graySecondary, + textInputBackgroundColorDisabled: palette.transparent, textInputBackgroundColorFocused: palette.graySecondary, textInputBorderColor: `${palette.edgeMint}00`, - textInputBorderColorDisabled: palette.gray, + textInputBorderColorDisabled: palette.graySecondary, textInputBorderColorFocused: palette.edgeMint, textInputBorderRadius: 100, textInputBorderWidth: 1, diff --git a/src/types/routerTypes.tsx b/src/types/routerTypes.tsx index ff2ed81206b..fd57b9012bb 100644 --- a/src/types/routerTypes.tsx +++ b/src/types/routerTypes.tsx @@ -12,9 +12,6 @@ import type { CreateWalletImportOptionsParams } from '../components/scenes/Creat import type { CreateWalletImportParams } from '../components/scenes/CreateWalletImportScene' import type { CreateWalletSelectCryptoParams } from '../components/scenes/CreateWalletSelectCryptoScene' import type { CreateWalletSelectFiatParams } from '../components/scenes/CreateWalletSelectFiatScene' -import type { ExchangeQuoteProcessingParams } from '../components/scenes/CryptoExchangeQuoteProcessingScene' -import type { CryptoExchangeQuoteParams } from '../components/scenes/CryptoExchangeQuoteScene' -import { ExchangeParams } from '../components/scenes/CryptoExchangeScene' import type { CurrencyNotificationParams } from '../components/scenes/CurrencyNotificationScene' import type { CurrencySettingsParams } from '../components/scenes/CurrencySettingsScene' import type { EdgeLoginParams } from '../components/scenes/EdgeLoginScene' @@ -40,6 +37,9 @@ import type { SendScene2Params } from '../components/scenes/SendScene2' import type { StakeModifyParams } from '../components/scenes/Staking/StakeModifyScene' import type { StakeOptionsParams } from '../components/scenes/Staking/StakeOptionsScene' import type { StakeOverviewParams } from '../components/scenes/Staking/StakeOverviewScene' +import type { SwapConfirmationParams } from '../components/scenes/SwapConfirmationScene' +import { SwapCreateParams } from '../components/scenes/SwapCreateScene' +import type { SwapProcessingParams } from '../components/scenes/SwapProcessingScene' import type { TransactionDetailsParams } from '../components/scenes/TransactionDetailsScene' import type { TransactionListParams } from '../components/scenes/TransactionListScene' import type { TransactionsExportParams } from '../components/scenes/TransactionsExportScene' @@ -71,7 +71,7 @@ export interface RouteParamList { walletsTab: {} buyTab: {} sellTab: {} - exchangeTab: {} + swapTab: {} extraTab: undefined devTab: undefined @@ -105,11 +105,6 @@ export interface RouteParamList { defaultFiatSetting: {} edgeLogin: EdgeLoginParams editToken: EditTokenParams - exchange: ExchangeParams - exchangeQuote: CryptoExchangeQuoteParams - exchangeQuoteProcessing: ExchangeQuoteProcessingParams - exchangeSettings: {} - exchangeSuccess: {} fioCreateHandle: FioCreateHandleParams fioAddressDetails: { fioAddressName: string @@ -213,6 +208,11 @@ export interface RouteParamList { stakeModify: StakeModifyParams stakeOptions: StakeOptionsParams stakeOverview: StakeOverviewParams + swapCreate: SwapCreateParams + swapConfirmation: SwapConfirmationParams + swapProcessing: SwapProcessingParams + swapSettings: {} + swapSuccess: {} testScene: {} transactionDetails: TransactionDetailsParams transactionList: TransactionListParams