Updated on 2026-08-14
This commit is contained in:
commit
d4b01f6065
27 changed files with 1945 additions and 4 deletions
|
|
@ -10,6 +10,7 @@ import com.tangem.feature.usedesk.api.UsedeskComponent
|
|||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
|
|
@ -78,6 +79,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val nftComponentFactory: NFTComponent.Factory,
|
||||
private val nftSendComponentFactory: NFTSendComponent.Factory,
|
||||
private val usedeskComponentFactory: UsedeskComponent.Factory,
|
||||
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
|
||||
private val testerRouter: TesterRouter,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
) {
|
||||
|
|
@ -407,6 +409,17 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = usedeskComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.ChooseManagedTokens -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = ChooseManagedTokensComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
initialCurrency = route.initialCurrency,
|
||||
source = ChooseManagedTokensComponent.Source.valueOf(route.source.name),
|
||||
),
|
||||
componentFactory = chooseManagedTokensComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,6 +127,16 @@ sealed class AppRoute(val path: String) : Route {
|
|||
}
|
||||
}
|
||||
|
||||
data class ChooseManagedTokens(
|
||||
val userWalletId: UserWalletId,
|
||||
val initialCurrency: CryptoCurrency,
|
||||
val source: Source,
|
||||
) : AppRoute(path = "/$source/choose_managed_tokens/$userWalletId/${initialCurrency.id.value}") {
|
||||
enum class Source {
|
||||
SendViaSwap,
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class WalletConnectSessions(val userWalletId: UserWalletId) : AppRoute(path = "/wallet_connect_sessions")
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,14 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
data class SwapCurrencies(
|
||||
val fromGroup: SwapCurrenciesGroup,
|
||||
val toGroup: SwapCurrenciesGroup,
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = SwapCurrencies(
|
||||
fromGroup = SwapCurrenciesGroup(emptyList(), emptyList(), false),
|
||||
toGroup = SwapCurrenciesGroup(emptyList(), emptyList(), false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return swap group depending on [swapDirection]
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ dependencies {
|
|||
implementation(projects.domain.manageTokens)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.foundation)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.swap.v2.impl.amount
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountModel
|
||||
import com.tangem.features.swap.v2.impl.amount.ui.SwapAmountContent
|
||||
import com.tangem.features.swap.v2.impl.common.SwapNavigationModelCallback
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class SwapAmountComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: SwapAmountComponentParams.AmountParams,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SwapAmountModel = getOrCreateModel(params = params)
|
||||
|
||||
fun updateState(amountUM: SwapAmountUM) = model.updateState(amountUM)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val amountUM by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
SwapAmountContent(
|
||||
amountUM = amountUM,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
|
||||
clickIntents = model,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallback : SwapNavigationModelCallback {
|
||||
fun onAmountResult(amountUM: SwapAmountUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.swap.v2.impl.amount
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.swap.SwapRoute
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal sealed class SwapAmountComponentParams {
|
||||
|
||||
abstract val amountUM: SwapAmountUM
|
||||
abstract val analyticsCategoryName: String
|
||||
abstract val userWallet: UserWallet
|
||||
abstract val appCurrency: AppCurrency
|
||||
abstract val swapDirection: SwapDirection
|
||||
abstract val isBalanceHidingFlow: StateFlow<Boolean>
|
||||
|
||||
data class AmountParams(
|
||||
override val amountUM: SwapAmountUM,
|
||||
override val analyticsCategoryName: String,
|
||||
override val userWallet: UserWallet,
|
||||
override val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
override val appCurrency: AppCurrency,
|
||||
override val swapDirection: SwapDirection,
|
||||
val primaryCryptoCurrency: CryptoCurrency,
|
||||
val secondaryCryptoCurrency: CryptoCurrency?,
|
||||
val callback: SwapAmountComponent.ModelCallback,
|
||||
val currentRoute: Flow<SwapRoute.Amount>,
|
||||
) : SwapAmountComponentParams()
|
||||
|
||||
data class AmountBlockParams(
|
||||
override val amountUM: SwapAmountUM,
|
||||
override val analyticsCategoryName: String,
|
||||
override val userWallet: UserWallet,
|
||||
override val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
override val appCurrency: AppCurrency,
|
||||
override val swapDirection: SwapDirection,
|
||||
val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
val blockClickEnableFlow: StateFlow<Boolean>,
|
||||
) : SwapAmountComponentParams()
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@InstallIn(ModelComponent::class)
|
||||
@Module
|
||||
internal interface SwapAmountModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SwapAmountModel::class)
|
||||
fun provideSwapAmountModel(impl: SwapAmountModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class SwapAmountUM {
|
||||
|
||||
abstract val isPrimaryButtonEnabled: Boolean
|
||||
abstract val primaryAmount: SwapAmountFieldUM
|
||||
abstract val secondaryAmount: SwapAmountFieldUM
|
||||
abstract val selectedAmountType: SwapAmountType
|
||||
|
||||
data object Empty : SwapAmountUM() {
|
||||
override val isPrimaryButtonEnabled = false
|
||||
override val selectedAmountType = SwapAmountType.From
|
||||
override val primaryAmount = SwapAmountFieldUM.Empty(SwapAmountType.From)
|
||||
override val secondaryAmount = SwapAmountFieldUM.Empty(SwapAmountType.To)
|
||||
}
|
||||
|
||||
data class Content(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
|
||||
// two amount fields
|
||||
override val primaryAmount: SwapAmountFieldUM,
|
||||
override val secondaryAmount: SwapAmountFieldUM,
|
||||
override val selectedAmountType: SwapAmountType,
|
||||
val primaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
|
||||
// selected swap route
|
||||
val swapDirection: SwapDirection,
|
||||
val swapRateType: ExpressRateType,
|
||||
|
||||
// swap models
|
||||
val swapCurrencies: SwapCurrencies,
|
||||
val swapQuotes: ImmutableList<SwapQuoteUM>,
|
||||
val selectedQuote: SwapQuoteUM,
|
||||
|
||||
// extra data
|
||||
val appCurrency: AppCurrency?,
|
||||
) : SwapAmountUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class SwapAmountFieldUM {
|
||||
abstract val amountType: SwapAmountType
|
||||
abstract val amountField: AmountState
|
||||
|
||||
data class Empty(
|
||||
override val amountType: SwapAmountType,
|
||||
) : SwapAmountFieldUM() {
|
||||
override val amountField: AmountState = AmountState.Empty(isPrimaryButtonEnabled = false)
|
||||
}
|
||||
|
||||
data class Loading(
|
||||
override val amountType: SwapAmountType,
|
||||
) : SwapAmountFieldUM() {
|
||||
override val amountField: AmountState = AmountState.Empty(isPrimaryButtonEnabled = false)
|
||||
}
|
||||
|
||||
data class Content(
|
||||
override val amountType: SwapAmountType,
|
||||
override val amountField: AmountState,
|
||||
val priceImpact: TextReference?,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
val subtitleEllipsis: TextEllipsis,
|
||||
val isClickEnabled: Boolean,
|
||||
) : SwapAmountFieldUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class PriceImpactUM {
|
||||
|
||||
data object Empty : PriceImpactUM()
|
||||
|
||||
data class Value(val value: Float) : PriceImpactUM()
|
||||
}
|
||||
|
||||
enum class SwapAmountType {
|
||||
From,
|
||||
To,
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
|
||||
internal interface SwapAmountClickIntents : AmountScreenClickIntents {
|
||||
|
||||
fun onExpandEditField(selectedAmountType: SwapAmountType)
|
||||
fun onSelectTokenClick()
|
||||
}
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
||||
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
|
||||
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
|
||||
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
|
||||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountReadyStateConverter
|
||||
import com.tangem.features.swap.v2.impl.amount.model.transformers.*
|
||||
import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.SwapChooseTokenNetworkListener
|
||||
import com.tangem.features.swap.v2.impl.common.entity.NavigationUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
import com.tangem.utils.transformer.update as transformerUpdate
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@ModelScoped
|
||||
internal class SwapAmountModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSwapPairsUseCase: GetSwapPairsUseCase,
|
||||
private val getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
|
||||
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
|
||||
private val selectInitialPairUseCase: SelectInitialPairUseCase,
|
||||
private val getSwapQuoteUseCase: GetSwapQuoteUseCase,
|
||||
private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
|
||||
private val getAllowanceUseCase: GetAllowanceUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {
|
||||
|
||||
private val params: SwapAmountComponentParams = paramsContainer.require()
|
||||
private val swapDirection = params.swapDirection
|
||||
private val appCurrency = params.appCurrency
|
||||
private var userWallet = params.userWallet
|
||||
|
||||
private var primaryCryptoCurrency: CryptoCurrency by Delegates.notNull()
|
||||
private var secondaryCryptoCurrency: CryptoCurrency? = null
|
||||
|
||||
private var primaryCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
private var secondaryCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
|
||||
private var primaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
|
||||
private var secondaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
|
||||
private var primaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
|
||||
private var secondaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
|
||||
|
||||
val uiState: StateFlow<SwapAmountUM>
|
||||
field = MutableStateFlow(params.amountUM)
|
||||
|
||||
init {
|
||||
when (params) {
|
||||
is SwapAmountComponentParams.AmountParams -> {
|
||||
primaryCryptoCurrency = params.primaryCryptoCurrency
|
||||
secondaryCryptoCurrency = params.secondaryCryptoCurrency
|
||||
}
|
||||
is SwapAmountComponentParams.AmountBlockParams -> {
|
||||
primaryCryptoCurrency = params.primaryCryptoCurrencyStatus.currency
|
||||
secondaryCryptoCurrency = params.secondaryCryptoCurrencyStatus?.currency
|
||||
primaryCryptoCurrencyStatus = params.primaryCryptoCurrencyStatus
|
||||
params.secondaryCryptoCurrencyStatus?.let {
|
||||
secondaryCryptoCurrencyStatus = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialState()
|
||||
configAmountNavigation()
|
||||
|
||||
observeChooseSelectToken()
|
||||
// todo observe balance hiding flow
|
||||
}
|
||||
|
||||
fun updateState(amountUM: SwapAmountUM) {
|
||||
uiState.update { amountUM }
|
||||
}
|
||||
|
||||
override fun onProviderResult(quoteUM: SwapQuoteUM) {
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountSelectQuoteTransformer(
|
||||
quoteUM = quoteUM,
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
|
||||
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onExpandEditField(selectedAmountType: SwapAmountType) {
|
||||
uiState.update { amountUM ->
|
||||
if (amountUM !is SwapAmountUM.Content) return
|
||||
amountUM.copy(
|
||||
selectedAmountType = selectedAmountType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAmountValueChange(value: String) {
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountValueChangeTransformer(
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
|
||||
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
|
||||
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
|
||||
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
|
||||
value = value,
|
||||
),
|
||||
)
|
||||
loadQuotes()
|
||||
}
|
||||
|
||||
override fun onAmountPasteTriggerDismiss() {
|
||||
uiState.transformerUpdate(SwapAmountPasteTransformer)
|
||||
}
|
||||
|
||||
override fun onMaxValueClick() {
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountValueMaxTransformer(
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
|
||||
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
|
||||
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
|
||||
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
|
||||
),
|
||||
)
|
||||
loadQuotes()
|
||||
}
|
||||
|
||||
override fun onCurrencyChangeClick(isFiat: Boolean) {
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountChangeCurrencyTransformer(
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
isFiatSelected = isFiat,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onAmountNext() {
|
||||
saveResult()
|
||||
}
|
||||
|
||||
override fun onSelectTokenClick() {
|
||||
appRouter.push(
|
||||
AppRoute.ChooseManagedTokens(
|
||||
userWalletId = userWallet.walletId,
|
||||
initialCurrency = primaryCryptoCurrency,
|
||||
source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeChooseSelectToken() {
|
||||
swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
|
||||
.onEach { currency ->
|
||||
uiState.update { amountUM ->
|
||||
if (amountUM is SwapAmountUM.Content) {
|
||||
amountUM.copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
secondaryAmount = SwapAmountFieldUM.Loading(
|
||||
amountType = SwapAmountType.To,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
amountUM
|
||||
}
|
||||
}
|
||||
initPairs(primaryCryptoCurrency, currency)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun initialState() {
|
||||
if (uiState.value is SwapAmountUM.Empty) {
|
||||
initPairs(
|
||||
primaryCryptoCurrency = primaryCryptoCurrency,
|
||||
secondaryCryptoCurrency = secondaryCryptoCurrency,
|
||||
)
|
||||
uiState.update {
|
||||
SwapAmountUM.Content(
|
||||
isPrimaryButtonEnabled = it.isPrimaryButtonEnabled,
|
||||
primaryAmount = it.primaryAmount,
|
||||
secondaryAmount = it.secondaryAmount,
|
||||
swapDirection = swapDirection,
|
||||
swapCurrencies = SwapCurrencies.EMPTY,
|
||||
appCurrency = appCurrency,
|
||||
swapQuotes = persistentListOf(),
|
||||
swapRateType = ExpressRateType.Float,
|
||||
selectedAmountType = SwapAmountType.From,
|
||||
selectedQuote = SwapQuoteUM.Empty,
|
||||
primaryCryptoCurrencyStatus = null,
|
||||
secondaryCryptoCurrencyStatus = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initPairs(primaryCryptoCurrency: CryptoCurrency, secondaryCryptoCurrency: CryptoCurrency?) {
|
||||
modelScope.launch {
|
||||
val cryptoCurrencyStatusList = getMultiCryptoCurrencyStatusUseCase
|
||||
.invokeMultiWalletSync(userWallet.walletId)
|
||||
.getOrElse { emptyList() }
|
||||
|
||||
val primaryStatus = cryptoCurrencyStatusList.firstOrNull {
|
||||
it.currency.id == primaryCryptoCurrency.id
|
||||
}
|
||||
if (primaryStatus == null) {
|
||||
Timber.e("Failed to get crypto currency status")
|
||||
// todo error
|
||||
return@launch
|
||||
}
|
||||
|
||||
val cryptoCurrencyStatusListExceptPrimary = cryptoCurrencyStatusList.filter {
|
||||
val statusFilter = it.value is CryptoCurrencyStatus.Loaded || it.value is CryptoCurrencyStatus.NoAccount
|
||||
val notCustomTokenFilter = !it.currency.isCustom
|
||||
statusFilter && notCustomTokenFilter
|
||||
}
|
||||
|
||||
getSwapPairsUseCase(
|
||||
userWallet = userWallet,
|
||||
initialCurrency = primaryCryptoCurrency,
|
||||
cryptoCurrencyStatusList = cryptoCurrencyStatusListExceptPrimary,
|
||||
).fold(
|
||||
ifRight = { swapCurrencies ->
|
||||
val secondaryStatus = selectInitialPairUseCase(
|
||||
primaryCryptoCurrency = primaryCryptoCurrency,
|
||||
secondaryCryptoCurrency = secondaryCryptoCurrency,
|
||||
userWallet = userWallet,
|
||||
swapCurrencies = swapCurrencies,
|
||||
swapDirection = params.swapDirection,
|
||||
)
|
||||
|
||||
if (secondaryStatus != null) {
|
||||
initCurrencies(primaryStatus, secondaryStatus)
|
||||
uiState.update {
|
||||
SwapAmountReadyStateConverter(
|
||||
swapCurrencies = swapCurrencies,
|
||||
userWallet = userWallet,
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
swapDirection = swapDirection,
|
||||
clickIntents = this@SwapAmountModel,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
).convert(Unit)
|
||||
}
|
||||
} else {
|
||||
// todo not available currency to swap
|
||||
}
|
||||
},
|
||||
ifLeft = {
|
||||
// todo error
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun initCurrencies(primaryStatus: CryptoCurrencyStatus, secondaryStatus: CryptoCurrencyStatus) {
|
||||
primaryCryptoCurrencyStatus = primaryStatus
|
||||
secondaryCryptoCurrencyStatus = secondaryStatus
|
||||
|
||||
primaryMinimumAmountBoundary = EnterAmountBoundary(
|
||||
amount = getMinimumTransactionAmountSyncUseCase
|
||||
.invoke(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrencyStatus = primaryStatus,
|
||||
).getOrNull().orZero(),
|
||||
fiatRate = primaryStatus.value.fiatRate,
|
||||
fiatAmount = primaryStatus.value.fiatAmount,
|
||||
)
|
||||
secondaryMinimumAmountBoundary = EnterAmountBoundary(
|
||||
amount = getMinimumTransactionAmountSyncUseCase
|
||||
.invoke(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrencyStatus = secondaryStatus,
|
||||
).getOrNull().orZero(),
|
||||
fiatRate = secondaryStatus.value.fiatRate,
|
||||
fiatAmount = secondaryStatus.value.fiatAmount,
|
||||
)
|
||||
primaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(primaryStatus)
|
||||
secondaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(secondaryStatus)
|
||||
}
|
||||
|
||||
private fun loadQuotes() {
|
||||
val state = uiState.value as? SwapAmountUM.Content ?: return
|
||||
|
||||
val (fromCryptoCurrency, toCryptoCurrency) = when (state.swapDirection) {
|
||||
SwapDirection.Direct -> primaryCryptoCurrencyStatus.currency to secondaryCryptoCurrencyStatus.currency
|
||||
SwapDirection.Reverse -> secondaryCryptoCurrencyStatus.currency to primaryCryptoCurrencyStatus.currency
|
||||
}
|
||||
|
||||
val fromAmount = when (state.swapDirection) {
|
||||
SwapDirection.Direct -> state.primaryAmount.amountField
|
||||
SwapDirection.Reverse -> state.secondaryAmount.amountField
|
||||
} as? AmountState.Data
|
||||
|
||||
if (fromAmount?.amountTextField?.isError == true) return
|
||||
|
||||
val fromAmountValue = fromAmount?.amountTextField?.cryptoAmount?.value ?: return
|
||||
|
||||
val swapGroups = when (state.swapDirection) {
|
||||
SwapDirection.Direct -> state.swapCurrencies.toGroup.available
|
||||
SwapDirection.Reverse -> state.swapCurrencies.fromGroup.available
|
||||
}
|
||||
|
||||
uiState.transformerUpdate(SwapQuoteLoadingStateTransformer)
|
||||
|
||||
modelScope.launch {
|
||||
val quotes = swapGroups.firstOrNull {
|
||||
it.currencyStatus.currency.id == toCryptoCurrency.id
|
||||
}?.providers?.map { provider ->
|
||||
async {
|
||||
getSwapQuoteUseCase(
|
||||
userWallet = userWallet,
|
||||
fromCryptoCurrency = fromCryptoCurrency,
|
||||
toCryptoCurrency = toCryptoCurrency,
|
||||
fromAmount = fromAmountValue,
|
||||
provider = provider,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
SwapQuoteUM.Error(
|
||||
provider = provider,
|
||||
expressError = error,
|
||||
)
|
||||
},
|
||||
ifRight = { quote: SwapQuoteModel ->
|
||||
convertToSwapProviderUM(
|
||||
quote = quote,
|
||||
provider = provider,
|
||||
differencePercent = DifferencePercent.Empty,
|
||||
swapDirection = swapDirection,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}?.awaitAll().orEmpty()
|
||||
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountSetQuotesTransformer(
|
||||
quotes = quotes,
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
|
||||
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun convertToSwapProviderUM(
|
||||
quote: SwapQuoteModel,
|
||||
provider: ExpressProvider,
|
||||
differencePercent: DifferencePercent,
|
||||
swapDirection: SwapDirection,
|
||||
): SwapQuoteUM {
|
||||
// todo swap allowance
|
||||
val allowanceContract = quote.allowanceContract
|
||||
return if (allowanceContract != null) {
|
||||
val allowance = getAllowanceUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = primaryCryptoCurrencyStatus.currency,
|
||||
spenderAddress = allowanceContract,
|
||||
).getOrNull().orZero()
|
||||
val isApprovalNeeded = allowance < primaryCryptoCurrencyStatus.value.amount.orZero()
|
||||
|
||||
if (isApprovalNeeded) {
|
||||
SwapQuoteUM.Allowance(
|
||||
provider = provider,
|
||||
allowanceContract = allowanceContract,
|
||||
)
|
||||
} else {
|
||||
SwapQuoteUM.Content(
|
||||
provider = provider,
|
||||
quoteAmount = quote.toTokenAmount,
|
||||
diffPercent = differencePercent,
|
||||
quoteAmountValue = stringReference(
|
||||
quote.toTokenAmount.format {
|
||||
crypto(
|
||||
when (swapDirection) {
|
||||
SwapDirection.Direct -> secondaryCryptoCurrencyStatus.currency
|
||||
SwapDirection.Reverse -> primaryCryptoCurrencyStatus.currency
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
SwapQuoteUM.Content(
|
||||
provider = provider,
|
||||
quoteAmount = quote.toTokenAmount,
|
||||
diffPercent = differencePercent,
|
||||
quoteAmountValue = stringReference(
|
||||
quote.toTokenAmount.format {
|
||||
crypto(
|
||||
when (swapDirection) {
|
||||
SwapDirection.Direct -> secondaryCryptoCurrencyStatus.currency
|
||||
SwapDirection.Reverse -> primaryCryptoCurrencyStatus.currency
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveResult() {
|
||||
val params = params as? SwapAmountComponentParams.AmountParams ?: return
|
||||
params.callback.onAmountResult(uiState.value)
|
||||
}
|
||||
|
||||
private fun configAmountNavigation() {
|
||||
val params = params as? SwapAmountComponentParams.AmountParams ?: return
|
||||
combine(
|
||||
flow = uiState,
|
||||
flow2 = params.currentRoute,
|
||||
transform = { state, route -> state to route },
|
||||
).onEach { (state, route) ->
|
||||
params.callback.onNavigationResult(
|
||||
NavigationUM.Content(
|
||||
title = resourceReference(R.string.common_swap),
|
||||
subtitle = null,
|
||||
backIconRes = if (route.isEditMode) {
|
||||
R.drawable.ic_back_24
|
||||
} else {
|
||||
R.drawable.ic_close_24
|
||||
},
|
||||
backIconClick = {
|
||||
// if (!route.isEditMode) {
|
||||
// todo analytics
|
||||
// analyticsEventHandler.send(
|
||||
// CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
// categoryName = params.analyticsCategoryName,
|
||||
// source = SendScreenSource.Address,
|
||||
// isFromSummary = false,
|
||||
// isValid = state.isPrimaryButtonEnabled,
|
||||
// ),
|
||||
// )
|
||||
// }
|
||||
params.callback.onBackClick()
|
||||
},
|
||||
primaryButton = NavigationButton(
|
||||
textReference = if (route.isEditMode) {
|
||||
resourceReference(R.string.common_continue)
|
||||
} else {
|
||||
resourceReference(R.string.common_next)
|
||||
},
|
||||
isEnabled = state.isPrimaryButtonEnabled,
|
||||
onClick = {
|
||||
saveResult()
|
||||
params.callback.onNextClick()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
internal object SwapAmountQuoteUtils {
|
||||
|
||||
fun calculatePriceImpact(
|
||||
fromTokenAmount: BigDecimal,
|
||||
toTokenAmount: BigDecimal,
|
||||
swapDirection: SwapDirection,
|
||||
primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): TextReference? {
|
||||
val (fromRate, toRate) = if (swapDirection == SwapDirection.Direct) {
|
||||
primaryCryptoCurrencyStatus.value.fiatRate to secondaryCryptoCurrencyStatus.value.fiatRate
|
||||
} else {
|
||||
secondaryCryptoCurrencyStatus.value.fiatRate to primaryCryptoCurrencyStatus.value.fiatRate
|
||||
}
|
||||
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromRate)
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate)
|
||||
|
||||
// Check for zero division
|
||||
if (fromTokenFiatValue.isZero() || toTokenFiatValue.isZero()) return null
|
||||
|
||||
val value = BigDecimal.ONE - toTokenFiatValue.divide(fromTokenFiatValue, 2, RoundingMode.HALF_UP)
|
||||
|
||||
return stringReference("$(-${value.format { percent(withoutSign = false) }})").takeIf {
|
||||
value > 0.1.toBigDecimal()
|
||||
}
|
||||
}
|
||||
|
||||
fun SwapAmountUM.updateAmount(
|
||||
onPrimaryAmount: SwapAmountFieldUM.Content.() -> SwapAmountFieldUM,
|
||||
onSecondaryAmount: SwapAmountFieldUM.Content.() -> SwapAmountFieldUM,
|
||||
): SwapAmountUM {
|
||||
if (this !is SwapAmountUM.Content) return this
|
||||
|
||||
return if (
|
||||
selectedAmountType == SwapAmountType.From && swapDirection == SwapDirection.Direct
|
||||
) {
|
||||
val amountFieldUM = primaryAmount as? SwapAmountFieldUM.Content ?: return this
|
||||
copy(primaryAmount = amountFieldUM.onPrimaryAmount())
|
||||
} else {
|
||||
val amountFieldUM = secondaryAmount as? SwapAmountFieldUM.Content ?: return this
|
||||
copy(secondaryAmount = amountFieldUM.onSecondaryAmount())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.converter
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountStateConverterV2
|
||||
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
|
||||
import com.tangem.common.ui.amountScreen.models.AmountParameters
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
|
||||
internal class SwapAmountFieldConverter(
|
||||
private val swapDirection: SwapDirection,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
) {
|
||||
|
||||
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
|
||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||
|
||||
fun convert(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus): SwapAmountFieldUM {
|
||||
return SwapAmountFieldUM.Content(
|
||||
amountType = selectedType,
|
||||
title = stringReference(cryptoCurrencyStatus.currency.name),
|
||||
subtitle = getSubtitle(selectedType = selectedType, cryptoCurrencyStatus = cryptoCurrencyStatus),
|
||||
subtitleEllipsis = getSubtitleEllipsis(
|
||||
selectedType = selectedType,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
priceImpact = null,
|
||||
isClickEnabled = selectedType.isViewingField(),
|
||||
amountField = AmountStateConverterV2(
|
||||
clickIntents = clickIntents,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus),
|
||||
iconStateConverter = iconStateConverter,
|
||||
isRedesignEnabled = true,
|
||||
).convert(
|
||||
AmountParameters(
|
||||
title = combinedReference(
|
||||
resourceReference(R.string.send_from_wallet_android),
|
||||
stringReference(" "),
|
||||
stringReference(userWallet.name),
|
||||
),
|
||||
value = "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSubtitle(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus) = when {
|
||||
selectedType.isEnteringField() -> resourceReference(
|
||||
R.string.common_balance,
|
||||
wrappedList(
|
||||
cryptoCurrencyStatus.value.amount.format {
|
||||
crypto(cryptoCurrency = cryptoCurrencyStatus.currency)
|
||||
},
|
||||
),
|
||||
).orMaskWithStars(isBalanceHidden)
|
||||
selectedType.isViewingField() -> resourceReference(R.string.send_with_swap_recipient_amount_text)
|
||||
else -> TextReference.Companion.EMPTY
|
||||
}
|
||||
|
||||
private fun getSubtitleEllipsis(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus) = when {
|
||||
selectedType.isEnteringField() -> TextEllipsis.OffsetEnd(cryptoCurrencyStatus.currency.symbol.length)
|
||||
selectedType.isViewingField() -> TextEllipsis.End
|
||||
else -> TextEllipsis.End
|
||||
}
|
||||
|
||||
private fun SwapAmountType.isEnteringField(): Boolean {
|
||||
return this == SwapAmountType.From && swapDirection == SwapDirection.Direct ||
|
||||
this == SwapAmountType.To && swapDirection == SwapDirection.Reverse
|
||||
}
|
||||
|
||||
private fun SwapAmountType.isViewingField(): Boolean {
|
||||
return this == SwapAmountType.From && swapDirection == SwapDirection.Reverse ||
|
||||
this == SwapAmountType.To && swapDirection == SwapDirection.Direct
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.converter
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SwapAmountReadyStateConverter(
|
||||
private val userWallet: UserWallet,
|
||||
private val swapCurrencies: SwapCurrencies,
|
||||
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val swapDirection: SwapDirection,
|
||||
private val isBalanceHidden: Boolean,
|
||||
) : Converter<Unit, SwapAmountUM> {
|
||||
|
||||
private val amountFieldConverter = SwapAmountFieldConverter(
|
||||
swapDirection = swapDirection,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
override fun convert(value: Unit): SwapAmountUM {
|
||||
return SwapAmountUM.Content(
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
primaryAmount = amountFieldConverter.convert(
|
||||
selectedType = SwapAmountType.From,
|
||||
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
),
|
||||
secondaryAmount = amountFieldConverter.convert(
|
||||
selectedType = SwapAmountType.To,
|
||||
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
),
|
||||
swapCurrencies = swapCurrencies,
|
||||
swapDirection = swapDirection,
|
||||
swapQuotes = persistentListOf(),
|
||||
selectedQuote = SwapQuoteUM.Empty,
|
||||
selectedAmountType = SwapAmountType.From,
|
||||
appCurrency = appCurrency,
|
||||
swapRateType = ExpressRateType.Float,
|
||||
isPrimaryButtonEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountCurrencyTransformer
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SwapAmountChangeCurrencyTransformer(
|
||||
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val isFiatSelected: Boolean,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
return prevState.updateAmount(
|
||||
onPrimaryAmount = {
|
||||
copy(
|
||||
amountField = AmountCurrencyTransformer(
|
||||
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
value = isFiatSelected,
|
||||
).transform(prevState.primaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
onSecondaryAmount = {
|
||||
copy(
|
||||
amountField = AmountCurrencyTransformer(
|
||||
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
value = isFiatSelected,
|
||||
).transform(prevState.secondaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountPastedTriggerDismissTransformer
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal object SwapAmountPasteTransformer : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
return prevState.updateAmount(
|
||||
onPrimaryAmount = {
|
||||
copy(
|
||||
amountField = AmountPastedTriggerDismissTransformer.transform(
|
||||
prevState = prevState.primaryAmount.amountField,
|
||||
),
|
||||
)
|
||||
},
|
||||
onSecondaryAmount = {
|
||||
copy(
|
||||
amountField = AmountPastedTriggerDismissTransformer.transform(
|
||||
prevState = prevState.secondaryAmount.amountField,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculatePriceImpact
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SwapAmountSelectQuoteTransformer(
|
||||
private val quoteUM: SwapQuoteUM,
|
||||
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
|
||||
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
return prevState.copy(
|
||||
isPrimaryButtonEnabled = true,
|
||||
selectedQuote = quoteUM,
|
||||
secondaryAmount = if (
|
||||
prevState.selectedAmountType == SwapAmountType.From && prevState.swapDirection == SwapDirection.Direct
|
||||
) {
|
||||
val secondaryAmountField = prevState.secondaryAmount as? SwapAmountFieldUM.Content
|
||||
val fromAmount = (prevState.primaryAmount.amountField as? AmountState.Data)
|
||||
?.amountTextField?.cryptoAmount?.value.orZero()
|
||||
val toAmount = (quoteUM as? SwapQuoteUM.Content)?.quoteAmount
|
||||
val priceImpact = calculatePriceImpact(
|
||||
swapDirection = prevState.swapDirection,
|
||||
fromTokenAmount = fromAmount,
|
||||
toTokenAmount = toAmount.orZero(),
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
secondaryAmountField?.copy(
|
||||
priceImpact = priceImpact,
|
||||
amountField = AmountFieldChangeTransformer(
|
||||
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
maxEnterAmount = secondaryMaximumAmountBoundary,
|
||||
minimumTransactionAmount = secondaryMinimumAmountBoundary,
|
||||
value = toAmount?.parseBigDecimal(secondaryCryptoCurrencyStatus.currency.decimals)
|
||||
.orEmpty(),
|
||||
).transform(secondaryAmountField.amountField),
|
||||
) ?: prevState.secondaryAmount
|
||||
} else {
|
||||
prevState.secondaryAmount
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculatePriceImpact
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SwapAmountSetQuotesTransformer(
|
||||
private val quotes: List<SwapQuoteUM>,
|
||||
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
|
||||
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
val fromAmount = when (prevState.swapDirection) {
|
||||
SwapDirection.Direct -> prevState.primaryAmount.amountField
|
||||
SwapDirection.Reverse -> prevState.secondaryAmount.amountField
|
||||
} as? AmountState.Data
|
||||
val fromAmountValue = fromAmount?.amountTextField?.cryptoAmount?.value.orZero()
|
||||
|
||||
val sortedQuotes = quotes.sortedWith(SwapQuotesComparator)
|
||||
val bestQuote = findBestQuote(quotes) ?: SwapQuoteUM.Empty
|
||||
|
||||
return prevState.copy(
|
||||
isPrimaryButtonEnabled = bestQuote is SwapQuoteUM.Content,
|
||||
swapQuotes = getQuotesWithDiff(sortedQuotes, bestQuote),
|
||||
selectedQuote = bestQuote,
|
||||
primaryAmount = if (
|
||||
prevState.selectedAmountType == SwapAmountType.From && prevState.swapDirection == SwapDirection.Direct
|
||||
) {
|
||||
val swapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content
|
||||
val amountField = swapAmountField?.amountField as? AmountState.Data
|
||||
|
||||
val amountError = (bestQuote as? SwapQuoteUM.Error)?.expressError.getAmountError()
|
||||
|
||||
if (amountField?.amountTextField?.isError == true) {
|
||||
prevState.primaryAmount
|
||||
} else {
|
||||
swapAmountField?.copy(
|
||||
amountField = amountField?.copy(
|
||||
amountTextField = amountField.amountTextField.copy(
|
||||
error = amountError ?: TextReference.EMPTY,
|
||||
isError = amountError != null,
|
||||
),
|
||||
) ?: swapAmountField.amountField,
|
||||
) ?: prevState.primaryAmount
|
||||
}
|
||||
} else {
|
||||
prevState.primaryAmount
|
||||
},
|
||||
secondaryAmount = if (
|
||||
prevState.selectedAmountType == SwapAmountType.From && prevState.swapDirection == SwapDirection.Direct
|
||||
) {
|
||||
val amountField = prevState.secondaryAmount as? SwapAmountFieldUM.Content
|
||||
val toAmount = (bestQuote as? SwapQuoteUM.Content)?.quoteAmount
|
||||
|
||||
val priceImpact = calculatePriceImpact(
|
||||
swapDirection = prevState.swapDirection,
|
||||
fromTokenAmount = fromAmountValue,
|
||||
toTokenAmount = toAmount.orZero(),
|
||||
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
amountField?.copy(
|
||||
priceImpact = priceImpact,
|
||||
amountField = AmountFieldChangeTransformer(
|
||||
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
maxEnterAmount = secondaryMaximumAmountBoundary,
|
||||
minimumTransactionAmount = secondaryMinimumAmountBoundary,
|
||||
value = toAmount?.parseBigDecimal(secondaryCryptoCurrencyStatus.currency.decimals).orEmpty(),
|
||||
).transform(amountField.amountField),
|
||||
) ?: prevState.secondaryAmount
|
||||
} else {
|
||||
prevState.secondaryAmount
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun ExpressError?.getAmountError(): TextReference? = when (this) {
|
||||
is ExpressError.AmountError.TooSmallError -> resourceReference(
|
||||
R.string.express_provider_min_amount,
|
||||
wrappedList(amount.format { crypto(cryptoCurrency = primaryCryptoCurrencyStatus.currency) }),
|
||||
)
|
||||
is ExpressError.AmountError.TooBigError -> resourceReference(
|
||||
R.string.express_provider_max_amount,
|
||||
wrappedList(amount.format { crypto(cryptoCurrency = primaryCryptoCurrencyStatus.currency) }),
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun getQuotesWithDiff(sortedQuotes: List<SwapQuoteUM>, bestQuote: SwapQuoteUM): ImmutableList<SwapQuoteUM> {
|
||||
return sortedQuotes.sortedWith(SwapQuotesComparator)
|
||||
.map { quote ->
|
||||
if (quote is SwapQuoteUM.Content && bestQuote is SwapQuoteUM.Content) {
|
||||
if (quote.provider.providerId == bestQuote.provider.providerId) {
|
||||
quote.copy(diffPercent = DifferencePercent.Best)
|
||||
} else {
|
||||
// current / selected - 1
|
||||
val percent = quote.quoteAmount / bestQuote.quoteAmount - BigDecimal.ONE
|
||||
quote.copy(
|
||||
diffPercent = DifferencePercent.Diff(
|
||||
percent = stringReference(
|
||||
if (percent.isPositive()) {
|
||||
"${StringsSigns.PLUS}${percent.format { percent() }}"
|
||||
} else {
|
||||
"${StringsSigns.DASH_SIGN}${percent.format { percent() }}"
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
private fun findBestQuote(quotes: List<SwapQuoteUM>): SwapQuoteUM? {
|
||||
return quotes
|
||||
.sortedWith(SwapQuotesComparator)
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
private object SwapQuotesComparator : Comparator<SwapQuoteUM> {
|
||||
override fun compare(p0: SwapQuoteUM?, p1: SwapQuoteUM?): Int {
|
||||
return when {
|
||||
p0 is SwapQuoteUM.Content && p1 !is SwapQuoteUM.Content -> -1
|
||||
p0 !is SwapQuoteUM.Content && p1 is SwapQuoteUM.Content -> 1
|
||||
p0 is SwapQuoteUM.Error && p1 is SwapQuoteUM.Error -> compareErrorQuote(p0 = p0, p1 = p1)
|
||||
p0 is SwapQuoteUM.Content && p1 is SwapQuoteUM.Content -> p1.quoteAmount.compareTo(p0.quoteAmount)
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareErrorQuote(p0: SwapQuoteUM.Error, p1: SwapQuoteUM.Error): Int = when {
|
||||
p0.expressError is ExpressError.AmountError && p1.expressError !is ExpressError.AmountError -> -1
|
||||
p0.expressError !is ExpressError.AmountError && p1.expressError is ExpressError.AmountError -> 1
|
||||
p0.expressError is ExpressError.AmountError && p1.expressError is ExpressError.AmountError -> {
|
||||
p0.expressError.amount.compareTo(p1.expressError.amount)
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SwapAmountValueChangeTransformer(
|
||||
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
|
||||
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
|
||||
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
|
||||
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
|
||||
private val value: String,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
return prevState
|
||||
.updateAmount(
|
||||
onPrimaryAmount = {
|
||||
copy(
|
||||
amountField = AmountFieldChangeTransformer(
|
||||
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
maxEnterAmount = primaryMaximumAmountBoundary,
|
||||
minimumTransactionAmount = primaryMinimumAmountBoundary,
|
||||
value = value,
|
||||
).transform(prevState.primaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
onSecondaryAmount = {
|
||||
copy(
|
||||
amountField = AmountFieldChangeTransformer(
|
||||
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
maxEnterAmount = secondaryMaximumAmountBoundary,
|
||||
minimumTransactionAmount = secondaryMinimumAmountBoundary,
|
||||
value = value,
|
||||
).transform(prevState.secondaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SwapAmountValueMaxTransformer(
|
||||
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
|
||||
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
|
||||
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
|
||||
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
return prevState
|
||||
.copy(selectedQuote = SwapQuoteUM.Loading)
|
||||
.updateAmount(
|
||||
onPrimaryAmount = {
|
||||
copy(
|
||||
amountField = AmountFieldSetMaxAmountTransformer(
|
||||
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
|
||||
maxAmount = primaryMaximumAmountBoundary,
|
||||
minAmount = primaryMinimumAmountBoundary,
|
||||
).transform(prevState.primaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
onSecondaryAmount = {
|
||||
copy(
|
||||
amountField = AmountFieldSetMaxAmountTransformer(
|
||||
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
|
||||
maxAmount = secondaryMaximumAmountBoundary,
|
||||
minAmount = secondaryMinimumAmountBoundary,
|
||||
).transform(prevState.secondaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal object SwapQuoteLoadingStateTransformer : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
return prevState.copy(
|
||||
selectedQuote = if (prevState.selectedQuote is SwapQuoteUM.Empty) {
|
||||
SwapQuoteUM.Loading
|
||||
} else {
|
||||
prevState.selectedQuote
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountFieldV2
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountClickIntents
|
||||
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountClickIntentsStub
|
||||
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
|
||||
@Composable
|
||||
internal fun SwapAmountContent(
|
||||
amountUM: SwapAmountUM,
|
||||
clickIntents: SwapAmountClickIntents,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ConstraintLayout(
|
||||
modifier = modifier,
|
||||
) {
|
||||
val (amountFromRef, amountToRef, middleButtonRef) = createRefs()
|
||||
SwapAmountBlock(
|
||||
amountUM = amountUM,
|
||||
amountFieldUM = amountUM.primaryAmount,
|
||||
clickIntents = clickIntents,
|
||||
modifier = Modifier.constrainAs(amountFromRef) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
)
|
||||
SwapAmountBlock(
|
||||
amountUM = amountUM,
|
||||
amountFieldUM = amountUM.secondaryAmount,
|
||||
clickIntents = clickIntents,
|
||||
modifier = Modifier.constrainAs(amountToRef) {
|
||||
top.linkTo(amountFromRef.bottom, 8.dp)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
)
|
||||
SwapAmountBlockSeparator(
|
||||
modifier = Modifier
|
||||
.constrainAs(middleButtonRef) {
|
||||
top.linkTo(amountFromRef.bottom)
|
||||
bottom.linkTo(amountToRef.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountBlockSeparator(modifier: Modifier = Modifier) {
|
||||
// todo add swap type like [Swap, SendViaSwap, SendIncognito]
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier
|
||||
.heightIn(max = 28.dp)
|
||||
.clip(RoundedCornerShape(32.dp))
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(
|
||||
vertical = 6.dp,
|
||||
horizontal = 12.dp,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = "Convert",
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
VerticalDivider(
|
||||
thickness = 1.dp,
|
||||
color = TangemTheme.colors.icon.inactive,
|
||||
)
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_close_24),
|
||||
),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountBlock(
|
||||
amountUM: SwapAmountUM,
|
||||
amountFieldUM: SwapAmountFieldUM,
|
||||
clickIntents: SwapAmountClickIntents,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
AnimatedVisibility(amountUM.selectedAmountType == amountFieldUM.amountType) {
|
||||
Box {
|
||||
SwapAmountEditBlock(
|
||||
amountFieldUM = amountFieldUM,
|
||||
modifier = Modifier,
|
||||
onValueChange = clickIntents::onAmountValueChange,
|
||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||
onCurrencyChange = clickIntents::onCurrencyChangeClick,
|
||||
)
|
||||
HorizontalDivider(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = 16.dp),
|
||||
thickness = 0.5.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
SwapAmountInfo(
|
||||
amountUM = amountUM,
|
||||
amountFieldUM = amountFieldUM,
|
||||
onExpandEditField = clickIntents::onExpandEditField,
|
||||
onSelectTokenClick = clickIntents::onSelectTokenClick,
|
||||
onMaxAmountClick = clickIntents::onMaxValueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountEditBlock(
|
||||
amountFieldUM: SwapAmountFieldUM,
|
||||
onValueChange: (String) -> Unit,
|
||||
onValuePastedTriggerDismiss: () -> Unit,
|
||||
onCurrencyChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = modifier
|
||||
.padding(vertical = 48.dp),
|
||||
) {
|
||||
if (amountFieldUM.amountField !is AmountState.Data) {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.caption2,
|
||||
modifier = Modifier.width(60.dp),
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = (amountFieldUM.amountField as AmountState.Data).title.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
AmountFieldV2(
|
||||
amountUM = amountFieldUM.amountField,
|
||||
onValueChange = onValueChange,
|
||||
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
||||
onCurrencyChange = onCurrencyChange,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountInfo(
|
||||
amountUM: SwapAmountUM,
|
||||
amountFieldUM: SwapAmountFieldUM,
|
||||
onExpandEditField: (SwapAmountType) -> Unit,
|
||||
onMaxAmountClick: () -> Unit,
|
||||
onSelectTokenClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokenIconState = (amountFieldUM.amountField as? AmountState.Data)?.tokenIconState ?: CurrencyIconState.Loading
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(),
|
||||
enabled = (amountFieldUM as? SwapAmountFieldUM.Content)?.isClickEnabled == true,
|
||||
onClick = {
|
||||
if ((amountUM as? SwapAmountUM.Content)?.swapRateType == ExpressRateType.Fixed) {
|
||||
onExpandEditField(amountFieldUM.amountType)
|
||||
} else {
|
||||
onSelectTokenClick()
|
||||
}
|
||||
},
|
||||
),
|
||||
) {
|
||||
CurrencyIcon(
|
||||
state = tokenIconState,
|
||||
shouldDisplayNetwork = true,
|
||||
modifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
)
|
||||
SwapAmountInfoMain(amountFieldUM = amountFieldUM)
|
||||
SpacerWMax()
|
||||
AnimatedContent(
|
||||
amountUM,
|
||||
) { wrappedAmountUM ->
|
||||
if (wrappedAmountUM is SwapAmountUM.Content) {
|
||||
SwapAmountInfoExtra(
|
||||
amountUM = wrappedAmountUM,
|
||||
amountFieldUM = amountFieldUM,
|
||||
onMaxAmountClick = onMaxAmountClick,
|
||||
onSelectTokenClick = onSelectTokenClick,
|
||||
)
|
||||
} else {
|
||||
RectangleShimmer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountInfoMain(amountFieldUM: SwapAmountFieldUM, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = amountFieldUM !is SwapAmountFieldUM.Content,
|
||||
modifier = modifier,
|
||||
) { isContent ->
|
||||
if (isContent) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
modifier = Modifier.width(56.dp),
|
||||
)
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.caption2,
|
||||
modifier = Modifier.width(72.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val amountFieldUM = amountFieldUM as SwapAmountFieldUM.Content
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = amountFieldUM.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
)
|
||||
EllipsisText(
|
||||
text = amountFieldUM.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
ellipsis = amountFieldUM.subtitleEllipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountInfoExtra(
|
||||
amountUM: SwapAmountUM.Content,
|
||||
amountFieldUM: SwapAmountFieldUM,
|
||||
onMaxAmountClick: () -> Unit,
|
||||
onSelectTokenClick: () -> Unit,
|
||||
) {
|
||||
when (amountFieldUM.amountType) {
|
||||
SwapAmountType.From -> when (amountUM.swapDirection) {
|
||||
SwapDirection.Direct -> {
|
||||
AnimatedVisibility(
|
||||
visible = amountUM.selectedAmountType == amountFieldUM.amountType,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
AmountMaxButton(onMaxAmountClick)
|
||||
}
|
||||
}
|
||||
SwapDirection.Reverse -> {
|
||||
SwapAmountInfoQuote(
|
||||
quoteUM = amountUM.selectedQuote,
|
||||
swapRateType = amountUM.swapRateType,
|
||||
onSelectTokenClick =
|
||||
onSelectTokenClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SwapAmountType.To -> when (amountUM.swapDirection) {
|
||||
SwapDirection.Direct -> {
|
||||
SwapAmountInfoQuote(
|
||||
quoteUM = amountUM.selectedQuote,
|
||||
swapRateType = amountUM.swapRateType,
|
||||
onSelectTokenClick = onSelectTokenClick,
|
||||
)
|
||||
}
|
||||
SwapDirection.Reverse -> {
|
||||
AnimatedVisibility(
|
||||
visible = amountUM.selectedAmountType == amountFieldUM.amountType,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
AmountMaxButton(onMaxAmountClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountMaxButton(onMaxAmountClick: () -> Unit) {
|
||||
SecondaryButton(
|
||||
text = stringResourceSafe(R.string.send_max_amount),
|
||||
size = TangemButtonSize.Small,
|
||||
onClick = onMaxAmountClick,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapAmountInfoQuote(quoteUM: SwapQuoteUM, swapRateType: ExpressRateType, onSelectTokenClick: () -> Unit) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.conditionalCompose(
|
||||
condition = swapRateType == ExpressRateType.Fixed,
|
||||
modifier = {
|
||||
clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(),
|
||||
onClick = onSelectTokenClick,
|
||||
)
|
||||
},
|
||||
),
|
||||
) {
|
||||
when (quoteUM) {
|
||||
is SwapQuoteUM.Content -> EllipsisText(
|
||||
text = quoteUM.quoteAmountValue.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.padding(end = 2.dp),
|
||||
)
|
||||
|
||||
is SwapQuoteUM.Error,
|
||||
is SwapQuoteUM.Empty,
|
||||
-> Box(modifier = Modifier.padding(start = 16.dp))
|
||||
|
||||
is SwapQuoteUM.Loading -> CircularProgressIndicator(
|
||||
color = TangemTheme.colors.icon.inactive,
|
||||
modifier = Modifier
|
||||
.padding(end = 4.dp)
|
||||
.size(20.dp),
|
||||
)
|
||||
|
||||
is SwapQuoteUM.Allowance -> Text(
|
||||
text = "ALLOWANCE NOT IMPLEMENTED",
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_chevron_24),
|
||||
),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
end = 16.dp,
|
||||
top = 24.dp,
|
||||
bottom = 24.dp,
|
||||
)
|
||||
.size(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun SwapAmountContent_Preview(
|
||||
@PreviewParameter(SwapAmountContentPreviewProvider::class) params: SwapAmountUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
SwapAmountContent(
|
||||
amountUM = params,
|
||||
modifier = Modifier,
|
||||
clickIntents = SwapAmountClickIntentsStub,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SwapAmountContentPreviewProvider : PreviewParameterProvider<SwapAmountUM> {
|
||||
override val values: Sequence<SwapAmountUM>
|
||||
get() = sequenceOf(
|
||||
SwapAmountContentPreview.emptyState,
|
||||
SwapAmountContentPreview.defaultState,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.ui.preview
|
||||
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountClickIntents
|
||||
|
||||
internal object SwapAmountClickIntentsStub : SwapAmountClickIntents {
|
||||
override fun onExpandEditField(selectedAmountType: SwapAmountType) {}
|
||||
|
||||
override fun onSelectTokenClick() {}
|
||||
|
||||
override fun onAmountValueChange(value: String) {}
|
||||
|
||||
override fun onAmountPasteTriggerDismiss() {}
|
||||
|
||||
override fun onMaxValueClick() {}
|
||||
|
||||
override fun onCurrencyChangeClick(isFiat: Boolean) {}
|
||||
|
||||
override fun onAmountNext() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.ui.preview
|
||||
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data object SwapAmountContentPreview {
|
||||
|
||||
val emptyState = SwapAmountUM.Content(
|
||||
isPrimaryButtonEnabled = false,
|
||||
primaryAmount = SwapAmountFieldUM.Empty(
|
||||
amountType = SwapAmountType.From,
|
||||
),
|
||||
secondaryAmount = SwapAmountFieldUM.Empty(
|
||||
amountType = SwapAmountType.To,
|
||||
),
|
||||
swapDirection = SwapDirection.Direct,
|
||||
selectedAmountType = SwapAmountType.From,
|
||||
swapCurrencies = SwapCurrencies.EMPTY,
|
||||
swapQuotes = persistentListOf(),
|
||||
selectedQuote = SwapQuoteUM.Empty,
|
||||
primaryCryptoCurrencyStatus = null,
|
||||
secondaryCryptoCurrencyStatus = null,
|
||||
swapRateType = ExpressRateType.Float,
|
||||
appCurrency = AppCurrency.Default,
|
||||
)
|
||||
|
||||
val defaultState = SwapAmountUM.Content(
|
||||
primaryAmount = SwapAmountFieldUM.Content(
|
||||
amountType = SwapAmountType.From,
|
||||
amountField = AmountStatePreviewData.amountState.copy(
|
||||
availableBalance = stringReference("Balance: 100 BTC"),
|
||||
),
|
||||
title = stringReference("Tether"),
|
||||
subtitle = stringReference("Balance: 100 BTC"),
|
||||
priceImpact = null,
|
||||
isClickEnabled = false,
|
||||
subtitleEllipsis = TextEllipsis.OffsetEnd(3),
|
||||
),
|
||||
secondaryAmount = SwapAmountFieldUM.Content(
|
||||
amountType = SwapAmountType.To,
|
||||
amountField = AmountStatePreviewData.amountState.copy(
|
||||
title = stringReference("Amount to receive"),
|
||||
availableBalance = TextReference.EMPTY,
|
||||
),
|
||||
title = stringReference("Shiba Inu"),
|
||||
priceImpact = stringReference("(-10%)"),
|
||||
subtitle = TextReference.EMPTY,
|
||||
isClickEnabled = false,
|
||||
subtitleEllipsis = TextEllipsis.OffsetEnd(3),
|
||||
),
|
||||
appCurrency = AppCurrency.Default,
|
||||
swapDirection = SwapDirection.Direct,
|
||||
selectedAmountType = SwapAmountType.From,
|
||||
swapCurrencies = SwapCurrencies.EMPTY,
|
||||
swapQuotes = persistentListOf(),
|
||||
selectedQuote = SwapQuoteUM.Empty,
|
||||
primaryCryptoCurrencyStatus = null,
|
||||
secondaryCryptoCurrencyStatus = null,
|
||||
swapRateType = ExpressRateType.Float,
|
||||
isPrimaryButtonEnabled = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -21,9 +21,6 @@ internal class SwapChooseProviderModel @Inject constructor(
|
|||
|
||||
private val params: SwapChooseProviderComponent.Params = paramsContainer.require()
|
||||
|
||||
val uiState: StateFlow<SwapChooseProviderBottomSheetContent>
|
||||
field: MutableStateFlow<SwapChooseProviderBottomSheetContent> = MutableStateFlow(getInitialState())
|
||||
|
||||
private val swapProviderListItemConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SwapProviderListItemConverter(
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
|
|
@ -31,6 +28,9 @@ internal class SwapChooseProviderModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val uiState: StateFlow<SwapChooseProviderBottomSheetContent>
|
||||
field: MutableStateFlow<SwapChooseProviderBottomSheetContent> = MutableStateFlow(getInitialState())
|
||||
|
||||
fun onProviderClick(quoteUM: SwapQuoteUM) {
|
||||
params.callback.onProviderResult(quoteUM)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.swap.v2.impl.common
|
||||
|
||||
import com.tangem.features.swap.v2.impl.common.entity.NavigationUM
|
||||
|
||||
internal interface SwapNavigationModelCallback {
|
||||
fun onNavigationResult(navigationUM: NavigationUM)
|
||||
fun onBackClick()
|
||||
fun onNextClick()
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.swap.v2.impl.common.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
internal sealed class NavigationUM {
|
||||
data class Content(
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference?,
|
||||
@DrawableRes val backIconRes: Int,
|
||||
val backIconClick: () -> Unit,
|
||||
@DrawableRes val additionalIconRes: Int? = null,
|
||||
val additionalIconClick: (() -> Unit)? = null,
|
||||
val primaryButton: NavigationButton,
|
||||
val secondaryPairButtonsUM: Pair<NavigationButton, NavigationButton>? = null,
|
||||
) : NavigationUM()
|
||||
|
||||
data object Empty : NavigationUM()
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.swap.v2.impl.swap
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal sealed class SwapRoute : Route {
|
||||
|
||||
abstract val isEditMode: Boolean
|
||||
|
||||
data object Empty : SwapRoute() {
|
||||
override val isEditMode: Boolean = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Confirm : SwapRoute() {
|
||||
override val isEditMode: Boolean = true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Amount(
|
||||
override val isEditMode: Boolean,
|
||||
) : SwapRoute()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue