Updated on 2026-08-14
This commit is contained in:
commit
86c748685e
18 changed files with 247 additions and 42 deletions
|
|
@ -148,6 +148,10 @@ class TransactionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getBlockchainId(networkId: String): String {
|
||||
return requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }.id
|
||||
}
|
||||
|
||||
private fun handleSendResult(result: SimpleResult): SendTxResult {
|
||||
when (result) {
|
||||
is SimpleResult.Success -> return SendTxResult.Success
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
/**
|
||||
* Formats input [String] for InputField, to remove wrong symbols, letters etc
|
||||
* Use [decimals] for cut this number symbols after floating point
|
||||
*
|
||||
* Example (with 8 decimals):
|
||||
* input string - ab123.46377372ab53
|
||||
* result string 123.46377372
|
||||
*/
|
||||
fun getValidatedNumberWithFixedDecimals(text: String, decimals: Int): String {
|
||||
val filteredChars = text.filterIndexed { index, c ->
|
||||
c.isDigit()
|
||||
|| (c == '.' && index != 0 && text.indexOf('.') == index)
|
||||
|| (c == '.' && index != 0 && text.count { it == '.' } <= 1)
|
||||
}
|
||||
// If dot is present, take first 3 digits before decimal and first decimals digits after decimal
|
||||
return if (filteredChars.count { it == '.' } == 1) {
|
||||
val beforeDecimal = filteredChars.substringBefore('.')
|
||||
val afterDecimal = filteredChars.substringAfter('.')
|
||||
beforeDecimal + "." + afterDecimal.take(decimals)
|
||||
}
|
||||
// If there is no dot, just take all digits
|
||||
else {
|
||||
filteredChars
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
|
||||
interface BlockchainInteractor {
|
||||
|
||||
fun getTokenDecimals(token: Currency): Int
|
||||
|
||||
/**
|
||||
* In app blockchain id, actual in blockchain sdk, not the same as networkId
|
||||
*
|
||||
* workaround till not use backend only and not integrated server vs sdk
|
||||
*/
|
||||
fun getBlockchainId(networkId: String): String
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class BlockchainInteractorImpl @Inject constructor(
|
||||
private val transactionManager: TransactionManager,
|
||||
) : BlockchainInteractor {
|
||||
|
||||
override fun getBlockchainId(networkId: String): String {
|
||||
return transactionManager.getBlockchainId(networkId)
|
||||
}
|
||||
|
||||
override fun getTokenDecimals(token: Currency): Int {
|
||||
return if (token is Currency.NonNativeToken) {
|
||||
token.decimalCount
|
||||
} else {
|
||||
transactionManager.getNativeTokenDecimals(token.networkId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,8 +91,6 @@ interface SwapInteractor {
|
|||
amountToSwap: String,
|
||||
): TxState
|
||||
|
||||
fun getTokenDecimals(token: Currency): Int
|
||||
|
||||
/**
|
||||
* Returns token in wallet balance
|
||||
*
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
userWalletManager.addToken(cryptoCurrencyConverter.convert(currencyToGet))
|
||||
TxState.TxSent(
|
||||
fromAmount = amountFormatter.formatSwapAmountToUI(swapData.fromTokenAmount, currencyToSend.symbol),
|
||||
toAmount = amountFormatter.formatSwapAmountToUI(swapData.toTokenAmount, currencyToSend.symbol),
|
||||
toAmount = amountFormatter.formatSwapAmountToUI(swapData.toTokenAmount, currencyToGet.symbol),
|
||||
)
|
||||
}
|
||||
SendTxResult.UserCancelledError -> TxState.UserCancelled
|
||||
|
|
@ -225,7 +225,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getTokenDecimals(token: Currency): Int {
|
||||
override fun getTokenBalance(token: Currency): SwapAmount {
|
||||
return userWalletManager.getCurrentWalletTokensBalance(token.networkId)[token.symbol]?.let {
|
||||
SwapAmount(it.value, it.decimals)
|
||||
} ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token))
|
||||
}
|
||||
|
||||
fun getTokenDecimals(token: Currency): Int {
|
||||
return if (token is Currency.NonNativeToken) {
|
||||
token.decimalCount
|
||||
} else {
|
||||
|
|
@ -233,12 +239,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getTokenBalance(token: Currency): SwapAmount {
|
||||
return userWalletManager.getCurrentWalletTokensBalance(token.networkId)[token.symbol]?.let {
|
||||
SwapAmount(it.value, it.decimals)
|
||||
} ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token))
|
||||
}
|
||||
|
||||
private fun selectToToken(
|
||||
initialToken: Currency,
|
||||
tokensInWallet: List<Currency>,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.feature.swap.domain.di
|
||||
|
||||
import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl
|
||||
import com.tangem.feature.swap.domain.BlockchainInteractor
|
||||
import com.tangem.feature.swap.domain.BlockchainInteractorImpl
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractorImpl
|
||||
import com.tangem.feature.swap.domain.SwapRepository
|
||||
|
|
@ -32,4 +34,14 @@ class SwapDomainModule {
|
|||
allowPermissionsHandler = AllowPermissionsHandlerImpl(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlockchainInteractor(
|
||||
transactionManager: TransactionManager,
|
||||
): BlockchainInteractor {
|
||||
return BlockchainInteractorImpl(
|
||||
transactionManager = transactionManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,4 +29,8 @@ sealed class Currency {
|
|||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
) : Currency()
|
||||
}
|
||||
|
||||
fun Currency.isNonNative(): Boolean {
|
||||
return this is Currency.NonNativeToken
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(project(":core:analytics"))
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":core:ui"))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.feature.swap.analytics
|
||||
|
||||
import com.tangem.core.analytics.AnalyticsEvent
|
||||
|
||||
sealed class SwapEvents(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(SWAP_CATEGORY, event, params) {
|
||||
|
||||
data class SwapScreenOpened(val token: String) : SwapEvents(
|
||||
event = "Swap Screen Opened",
|
||||
params = mapOf("Token" to token),
|
||||
)
|
||||
|
||||
object SendTokenBalanceClicked : SwapEvents(event = "Send Token Balance Clicked")
|
||||
object ReceiveTokenClicked : SwapEvents(event = "Receive Token Clicked")
|
||||
object ChooseTokenScreenOpened : SwapEvents(event = "Choose Token Screen Opened")
|
||||
object SearchTokenClicked : SwapEvents(event = "Search Token Clicked")
|
||||
data class ButtonSwapClicked(val sendToken: String, val receiveToken: String) : SwapEvents(
|
||||
event = "Button - Swap",
|
||||
params = mapOf("Send Token" to sendToken, "Receive Token" to receiveToken),
|
||||
)
|
||||
|
||||
object ButtonGivePermissionClicked : SwapEvents(event = "Button - Give permission") // ?
|
||||
object ButtonPermissionApproveClicked : SwapEvents(event = "Button - Permission Approve")
|
||||
object ButtonPermissionCancelClicked : SwapEvents(event = "Button - Permission Cancel")
|
||||
object ButtonPermitAndSwapClicked : SwapEvents(event = "Button - Permit and Swap") // ?
|
||||
object ButtonSwipeClicked : SwapEvents(event = "Button - Swipe")
|
||||
object SwapInProgressScreen : SwapEvents(event = "Swap in Progress Screen Opened")
|
||||
}
|
||||
|
||||
private const val SWAP_CATEGORY = "Swap"
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
data class SwapStateHolder(
|
||||
val sendCardData: SwapCardData,
|
||||
val receiveCardData: SwapCardData,
|
||||
val networkCurrency: String,
|
||||
val networkId: String,
|
||||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
val fee: FeeState = FeeState.Empty,
|
||||
val warnings: List<SwapWarning> = emptyList(),
|
||||
val alert: SwapWarning.GenericWarning? = null,
|
||||
|
|
@ -24,6 +23,8 @@ data class SwapStateHolder(
|
|||
val onSelectTokenClick: (() -> Unit)? = null,
|
||||
val onSuccess: (() -> Unit)? = null,
|
||||
val onMaxAmountSelected: (() -> Unit)? = null,
|
||||
val onShowPermissionBottomSheet: () -> Unit = {},
|
||||
val onCancelPermissionBottomSheet: () -> Unit = {},
|
||||
)
|
||||
|
||||
data class SwapCardData(
|
||||
|
|
@ -34,7 +35,7 @@ data class SwapCardData(
|
|||
val tokenIconUrl: String,
|
||||
val tokenCurrency: String,
|
||||
val balance: String,
|
||||
@DrawableRes val networkIconRes: Int? = null,
|
||||
val isNotNativeToken: Boolean,
|
||||
val canSelectAnotherToken: Boolean = false,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@ data class UiActions(
|
|||
val onChangeCardsClicked: () -> Unit,
|
||||
val onBackClicked: () -> Unit,
|
||||
val onMaxAmountSelected: () -> Unit,
|
||||
val openPermissionBottomSheet: () -> Unit,
|
||||
val hidePermissionBottomSheet: () -> Unit,
|
||||
)
|
||||
|
|
@ -34,6 +34,7 @@ class SwapFragment : Fragment() {
|
|||
customTabsManager = CustomTabsManager(WeakReference(activity?.application)),
|
||||
),
|
||||
)
|
||||
viewModel.onScreenOpened()
|
||||
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
viewLifecycleOwner,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui
|
|||
import com.tangem.feature.swap.converters.TokensDataConverter
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.feature.swap.domain.models.domain.isNonNative
|
||||
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.domain.models.ui.FoundTokensState
|
||||
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
|
||||
|
|
@ -27,9 +28,10 @@ class StateBuilder(val actions: UiActions) {
|
|||
|
||||
private val tokensDataConverter = TokensDataConverter(actions.onSearchEntered, actions.onTokenSelected)
|
||||
|
||||
fun createInitialLoadingState(initialCurrency: Currency): SwapStateHolder {
|
||||
fun createInitialLoadingState(initialCurrency: Currency, blockchainId: String): SwapStateHolder {
|
||||
return SwapStateHolder(
|
||||
networkId = initialCurrency.networkId,
|
||||
blockchainId = blockchainId,
|
||||
sendCardData = SwapCardData(
|
||||
type = TransactionCardType.SendCard(actions.onAmountChanged),
|
||||
amount = null,
|
||||
|
|
@ -38,6 +40,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
tokenCurrency = initialCurrency.symbol,
|
||||
coinId = initialCurrency.id,
|
||||
canSelectAnotherToken = false,
|
||||
isNotNativeToken = initialCurrency.isNonNative(),
|
||||
balance = "",
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
|
|
@ -48,6 +51,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
tokenCurrency = "",
|
||||
canSelectAnotherToken = false,
|
||||
balance = "",
|
||||
isNotNativeToken = false,
|
||||
coinId = null,
|
||||
),
|
||||
fee = FeeState.Loading,
|
||||
|
|
@ -58,6 +62,8 @@ class StateBuilder(val actions: UiActions) {
|
|||
onChangeCardsClicked = actions.onChangeCardsClicked,
|
||||
onMaxAmountSelected = actions.onMaxAmountSelected,
|
||||
updateInProgress = true,
|
||||
onShowPermissionBottomSheet = actions.openPermissionBottomSheet,
|
||||
onCancelPermissionBottomSheet = actions.hidePermissionBottomSheet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +81,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
tokenIconUrl = fromToken.logoUrl,
|
||||
tokenCurrency = fromToken.symbol,
|
||||
coinId = fromToken.id,
|
||||
isNotNativeToken = fromToken.isNonNative(),
|
||||
canSelectAnotherToken = mainTokenId != fromToken.id,
|
||||
balance = "",
|
||||
),
|
||||
|
|
@ -85,6 +92,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
tokenIconUrl = toToken.logoUrl,
|
||||
tokenCurrency = toToken.symbol,
|
||||
coinId = toToken.id,
|
||||
isNotNativeToken = toToken.isNonNative(),
|
||||
canSelectAnotherToken = mainTokenId != toToken.id,
|
||||
balance = "",
|
||||
),
|
||||
|
|
@ -119,6 +127,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
amountEquivalent = quoteModel.fromTokenInfo.tokenFiatBalance,
|
||||
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
|
||||
coinId = quoteModel.fromTokenInfo.coinId,
|
||||
isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||
balance = quoteModel.fromTokenInfo.tokenWalletBalance,
|
||||
|
|
@ -129,6 +138,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
amountEquivalent = quoteModel.toTokenInfo.tokenFiatBalance,
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
coinId = quoteModel.toTokenInfo.coinId,
|
||||
isNotNativeToken = uiStateHolder.receiveCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||
balance = quoteModel.toTokenInfo.tokenWalletBalance,
|
||||
|
|
@ -159,6 +169,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
amountEquivalent = "",
|
||||
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
|
||||
coinId = uiStateHolder.sendCardData.coinId,
|
||||
isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||
balance = emptyAmountState.fromTokenWalletBalance,
|
||||
|
|
@ -169,6 +180,7 @@ class StateBuilder(val actions: UiActions) {
|
|||
amountEquivalent = "",
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
coinId = uiStateHolder.receiveCardData.coinId,
|
||||
isNotNativeToken = uiStateHolder.receiveCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||
balance = emptyAmountState.toTokenWalletBalance,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,12 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
if (stateHolder.permissionState is SwapPermissionState.ReadyForRequest) {
|
||||
SwapPermissionBottomSheetContent(
|
||||
data = stateHolder.permissionState,
|
||||
onCancel = { coroutineScope.launch { bottomSheetState.hide() } },
|
||||
onCancel = {
|
||||
coroutineScope.launch {
|
||||
stateHolder.onCancelPermissionBottomSheet.invoke()
|
||||
bottomSheetState.hide()
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// Required "else" block to prevent compose crash
|
||||
|
|
@ -55,7 +60,12 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
val isBottomSheetReady = !bottomSheetState.isVisible &&
|
||||
stateHolder.permissionState is SwapPermissionState.ReadyForRequest
|
||||
coroutineScope.launch {
|
||||
if (isBottomSheetReady) bottomSheetState.show() else bottomSheetState.hide()
|
||||
if (isBottomSheetReady) {
|
||||
bottomSheetState.show()
|
||||
stateHolder.onShowPermissionBottomSheet.invoke()
|
||||
} else {
|
||||
bottomSheetState.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.material.Icon
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -39,6 +40,7 @@ import com.tangem.core.ui.components.SmallInfoCardWithWarning
|
|||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getActiveIconResByCoinId
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.models.FeeState
|
||||
|
|
@ -161,6 +163,9 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val networkIconRes = remember {
|
||||
getActiveIconRes(state.blockchainId)
|
||||
}
|
||||
Column {
|
||||
TransactionCard(
|
||||
type = state.sendCardData.type,
|
||||
|
|
@ -169,7 +174,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
amountEquivalent = state.sendCardData.amountEquivalent,
|
||||
tokenIconUrl = state.sendCardData.tokenIconUrl,
|
||||
tokenCurrency = state.sendCardData.tokenCurrency,
|
||||
networkIconRes = state.sendCardData.networkIconRes,
|
||||
networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = state.sendCardData.coinId?.let {
|
||||
getActiveIconResByCoinId(it, state.networkId)
|
||||
},
|
||||
|
|
@ -183,7 +188,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
amountEquivalent = state.receiveCardData.amountEquivalent,
|
||||
tokenIconUrl = state.receiveCardData.tokenIconUrl,
|
||||
tokenCurrency = state.receiveCardData.tokenCurrency,
|
||||
networkIconRes = state.receiveCardData.networkIconRes,
|
||||
networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = state.receiveCardData.coinId?.let {
|
||||
getActiveIconResByCoinId(it, state.networkId)
|
||||
},
|
||||
|
|
@ -314,7 +319,7 @@ private val sendCard = SwapCardData(
|
|||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
isNotNativeToken = true,
|
||||
canSelectAnotherToken = false,
|
||||
balance = "123",
|
||||
coinId = "",
|
||||
|
|
@ -326,7 +331,7 @@ private val receiveCard = SwapCardData(
|
|||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
isNotNativeToken = true,
|
||||
canSelectAnotherToken = true,
|
||||
balance = "33333",
|
||||
coinId = "",
|
||||
|
|
@ -342,6 +347,7 @@ private val state = SwapStateHolder(
|
|||
swapButton = SwapButton(enabled = true, loading = false, onClick = {}),
|
||||
onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {},
|
||||
permissionState = SwapPermissionState.InProgress,
|
||||
blockchainId = "POLYGON",
|
||||
// alert = SwapWarning.GenericWarning("There was an error. Please try again.") {},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.utils.getValidatedNumberWithFixedDecimals
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.domain.BlockchainInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
|
||||
|
|
@ -24,7 +28,6 @@ import com.tangem.feature.swap.ui.StateBuilder
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import com.tangem.utils.toFormattedString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
|
|
@ -35,7 +38,9 @@ import kotlin.properties.Delegates
|
|||
@HiltViewModel
|
||||
internal class SwapViewModel @Inject constructor(
|
||||
private val swapInteractor: SwapInteractor,
|
||||
private val blockchainInteractor: BlockchainInteractor,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -51,7 +56,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val singleTaskScheduler = SingleTaskScheduler<SwapState>()
|
||||
|
||||
private var dataState by mutableStateOf(SwapProcessDataState(networkId = currency.networkId))
|
||||
var uiState: SwapStateHolder by mutableStateOf(stateBuilder.createInitialLoadingState(currency))
|
||||
var uiState: SwapStateHolder by mutableStateOf(
|
||||
stateBuilder.createInitialLoadingState(
|
||||
initialCurrency = currency,
|
||||
blockchainId = blockchainInteractor.getBlockchainId(currency.networkId),
|
||||
),
|
||||
)
|
||||
private set
|
||||
|
||||
// shows currency order (direct - swap initial to selected, reversed = selected to initial)
|
||||
|
|
@ -70,12 +80,21 @@ internal class SwapViewModel @Inject constructor(
|
|||
super.onCleared()
|
||||
}
|
||||
|
||||
fun onScreenOpened() {
|
||||
analyticsEventHandler.send(SwapEvents.SwapScreenOpened(currency.symbol))
|
||||
}
|
||||
|
||||
fun setRouter(router: SwapRouter) {
|
||||
swapRouter = router
|
||||
uiState = uiState.copy(
|
||||
onBackClicked = router::back,
|
||||
onSelectTokenClick = { router.openScreen(SwapScreen.SelectToken) },
|
||||
onSuccess = { router.openScreen(SwapScreen.Success) },
|
||||
onSelectTokenClick = {
|
||||
router.openScreen(SwapScreen.SelectToken)
|
||||
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenOpened)
|
||||
},
|
||||
onSuccess = {
|
||||
router.openScreen(SwapScreen.Success)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -190,16 +209,17 @@ internal class SwapViewModel @Inject constructor(
|
|||
runCatching(dispatchers.io) {
|
||||
swapInteractor.onSwap(
|
||||
networkId = dataState.networkId,
|
||||
swapData = dataState.swapModel!!,
|
||||
currencyToSend = dataState.fromCurrency!!,
|
||||
currencyToGet = dataState.toCurrency!!,
|
||||
amountToSwap = dataState.amount!!,
|
||||
swapData = requireNotNull(dataState.swapModel),
|
||||
currencyToSend = requireNotNull(dataState.fromCurrency),
|
||||
currencyToGet = requireNotNull(dataState.toCurrency),
|
||||
amountToSwap = requireNotNull(dataState.amount),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
when (it) {
|
||||
is TxState.TxSent -> {
|
||||
uiState = stateBuilder.createSuccessState(uiState, it)
|
||||
analyticsEventHandler.send(SwapEvents.SwapInProgressScreen)
|
||||
swapRouter.openScreen(SwapScreen.Success)
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -209,7 +229,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
.onFailure { }
|
||||
.onFailure { makeDefaultAlert() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,16 +250,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
uiState = stateBuilder.loadingPermissionState(uiState)
|
||||
}
|
||||
else -> {
|
||||
uiState = stateBuilder.addAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
uiState = stateBuilder.addAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -286,7 +302,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
toCurrency = newToToken,
|
||||
)
|
||||
isOrderReversed = !isOrderReversed
|
||||
lastAmount.value = cutAmountWithDecimals(swapInteractor.getTokenDecimals(newFromToken), lastAmount.value)
|
||||
lastAmount.value =
|
||||
cutAmountWithDecimals(blockchainInteractor.getTokenDecimals(newFromToken), lastAmount.value)
|
||||
uiState = stateBuilder.updateSwapAmount(uiState, lastAmount.value)
|
||||
startLoadingQuotes(newFromToken, newToToken, lastAmount.value)
|
||||
}
|
||||
|
|
@ -296,7 +313,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
val fromToken = dataState.fromCurrency
|
||||
val toToken = dataState.toCurrency
|
||||
if (fromToken != null && toToken != null) {
|
||||
val cutValue = cutAmountWithDecimals(swapInteractor.getTokenDecimals(fromToken), value)
|
||||
val cutValue = cutAmountWithDecimals(blockchainInteractor.getTokenDecimals(fromToken), value)
|
||||
uiState = stateBuilder.updateSwapAmount(uiState, cutValue)
|
||||
lastAmount.value = cutValue
|
||||
amountDebouncer.debounce(DEBOUNCE_AMOUNT_DELAY, viewModelScope) {
|
||||
|
|
@ -313,19 +330,52 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun cutAmountWithDecimals(maxDecimals: Int, amount: String): String {
|
||||
return amount.toBigDecimalOrNull()?.toFormattedString(maxDecimals) ?: INITIAL_AMOUNT
|
||||
return getValidatedNumberWithFixedDecimals(amount, maxDecimals)
|
||||
}
|
||||
|
||||
private fun makeDefaultAlert() {
|
||||
uiState = stateBuilder.addAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUiActions(): UiActions {
|
||||
return UiActions(
|
||||
onSearchEntered = { onSearchEntered(it) },
|
||||
onTokenSelected = { onTokenSelect(it) },
|
||||
onTokenSelected = {
|
||||
onTokenSelect(it)
|
||||
analyticsEventHandler.send(SwapEvents.SearchTokenClicked)
|
||||
},
|
||||
onAmountChanged = { onAmountChanged(it) },
|
||||
onSwapClick = { onSwapClick() },
|
||||
onGivePermissionClick = { givePermissionsToSwap() },
|
||||
onChangeCardsClicked = { onChangeCardsClicked() },
|
||||
onSwapClick = {
|
||||
onSwapClick()
|
||||
val sendTokenSymbol = dataState.fromCurrency?.symbol
|
||||
val receiveTokenSymbol = dataState.toCurrency?.symbol
|
||||
if (sendTokenSymbol != null && receiveTokenSymbol != null) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ButtonSwapClicked(
|
||||
sendToken = sendTokenSymbol,
|
||||
receiveToken = receiveTokenSymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
onGivePermissionClick = {
|
||||
givePermissionsToSwap()
|
||||
analyticsEventHandler.send(SwapEvents.ButtonPermissionApproveClicked)
|
||||
},
|
||||
onChangeCardsClicked = {
|
||||
onChangeCardsClicked()
|
||||
analyticsEventHandler.send(SwapEvents.ButtonSwipeClicked)
|
||||
},
|
||||
onBackClicked = { onSearchEntered("") },
|
||||
onMaxAmountSelected = { onMaxAmountClicked() },
|
||||
openPermissionBottomSheet = {
|
||||
analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked)
|
||||
},
|
||||
hidePermissionBottomSheet = {
|
||||
analyticsEventHandler.send(SwapEvents.ButtonPermissionCancelClicked)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,4 +44,12 @@ interface TransactionManager {
|
|||
suspend fun updateWalletManager(networkId: String)
|
||||
|
||||
fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal
|
||||
|
||||
/**
|
||||
* In app blockchain id, actual in blockchain sdk, not the same as networkId
|
||||
*
|
||||
* workaround till not use backend only and not integrated server vs sdk
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getBlockchainId(networkId: String): String
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue