Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-28 16:25:16 +05:00
commit d781d345f1
6 changed files with 89 additions and 13 deletions

View file

@ -82,7 +82,9 @@ internal class FeeSelectorModel @Inject constructor(
}
private fun loadFee() {
uiState.update(FeeSelectorLoadingTransformer)
if (uiState.value !is FeeSelectorUM.Content) {
uiState.update(FeeSelectorLoadingTransformer)
}
modelScope.launch {
params.onLoadFee()
.fold(

View file

@ -3,12 +3,14 @@ package com.tangem.features.send.v2.feeselector.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
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.res.painterResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.style.TextAlign
@ -119,7 +121,9 @@ private fun FeeSelectorStaticPart(onReadMoreClick: () -> Unit, modifier: Modifie
.size(TangemTheme.dimens.size16),
content = { contentModifier ->
Icon(
modifier = contentModifier.size(TangemTheme.dimens.size16),
modifier = contentModifier
.size(TangemTheme.dimens.size16)
.clip(CircleShape),
painter = painterResource(id = R.drawable.ic_token_info_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,

View file

@ -9,6 +9,7 @@ import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
@ -43,6 +44,10 @@ internal class SwapAmountBlockComponent(
)
init {
lifecycle.subscribe(
onStart = model::onStart,
onStop = model::onStop,
)
model.uiState.onEach {
onResult(it)
}.launchIn(componentScope)

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.common.ui.navigationButtons.NavigationModelCallback
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
@ -23,6 +24,13 @@ internal class SwapAmountComponent @AssistedInject constructor(
private val model: SwapAmountModel = getOrCreateModel(params = params)
init {
lifecycle.subscribe(
onStart = model::onStart,
onStop = model::onStop,
)
}
fun updateState(amountUM: SwapAmountUM) = model.updateState(amountUM)
@Composable

View file

@ -43,6 +43,9 @@ import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderCompone
import com.tangem.features.swap.v2.impl.common.SwapAlertFactory
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.coroutines.PeriodicTask
import com.tangem.utils.coroutines.SingleTaskScheduler
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.update
import kotlinx.coroutines.async
@ -91,6 +94,9 @@ internal class SwapAmountModel @Inject constructor(
val uiState: StateFlow<SwapAmountUM>
field = MutableStateFlow(params.amountUM)
private val amountDebouncer = Debouncer()
private val quoteTaskScheduler = SingleTaskScheduler<Unit>()
init {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
@ -105,6 +111,19 @@ internal class SwapAmountModel @Inject constructor(
subscribeOnReloadQuotesTriggerUpdates()
}
fun onStart() {
startLoadingQuotesTask(isSilentReload = false)
}
fun onStop() {
quoteTaskScheduler.cancelTask()
}
override fun onDestroy() {
quoteTaskScheduler.cancelTask()
super.onDestroy()
}
fun updateState(amountUM: SwapAmountUM) {
uiState.update { amountUM }
}
@ -149,7 +168,14 @@ internal class SwapAmountModel @Inject constructor(
value = value,
),
)
loadQuotes()
amountDebouncer.debounce(
coroutineScope = modelScope,
waitMs = DEBOUNCE_AMOUNT_DELAY,
forceUpdate = true,
destinationFunction = {
startLoadingQuotesTask(isSilentReload = false)
},
)
}
override fun onAmountPasteTriggerDismiss() {
@ -165,7 +191,7 @@ internal class SwapAmountModel @Inject constructor(
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
),
)
loadQuotes()
startLoadingQuotesTask(isSilentReload = false)
}
override fun onCurrencyChangeClick(isFiat: Boolean) {
@ -287,7 +313,7 @@ internal class SwapAmountModel @Inject constructor(
),
)
feeSelectorReloadTrigger.triggerLoadingState()
loadQuotes(reloadFees = true)
startLoadingQuotesTask(isSilentReload = false)
}
.launchIn(modelScope)
}
@ -303,7 +329,7 @@ internal class SwapAmountModel @Inject constructor(
),
)
feeSelectorReloadTrigger.triggerLoadingState()
loadQuotes(reloadFees = true)
startLoadingQuotesTask(isSilentReload = false)
}
.launchIn(modelScope)
}
@ -316,7 +342,7 @@ internal class SwapAmountModel @Inject constructor(
private fun subscribeOnReloadQuotesTriggerUpdates() {
swapAmountUpdateListener.reloadQuotesTriggerFlow
.onEach { loadQuotes(reloadFees = true) }
.onEach { startLoadingQuotesTask(isSilentReload = true) }
.launchIn(modelScope)
}
@ -368,7 +394,7 @@ internal class SwapAmountModel @Inject constructor(
isBalanceHidden = params.isBalanceHidingFlow.value,
),
)
loadQuotes()
startLoadingQuotesTask(isSilentReload = false)
} else {
Timber.e(
"""
@ -408,7 +434,7 @@ internal class SwapAmountModel @Inject constructor(
}
}
private fun loadQuotes(reloadFees: Boolean = false) {
private fun loadQuotes(isSilentReload: Boolean) {
val state = uiState.value as? SwapAmountUM.Content ?: return
if (state.secondaryCryptoCurrencyStatus == null) return
@ -474,15 +500,35 @@ internal class SwapAmountModel @Inject constructor(
quotes = quotes,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
isSilentReload = isSilentReload,
),
)
if (reloadFees) {
feeSelectorReloadTrigger.triggerUpdate()
}
feeSelectorReloadTrigger.triggerUpdate()
}
}
private fun startLoadingQuotesTask(isSilentReload: Boolean) {
quoteTaskScheduler.cancelTask()
loadQuotes(isSilentReload = isSilentReload)
quoteTaskScheduler.scheduleTask(
scope = modelScope,
task = loadQuotesTask(),
)
}
private fun loadQuotesTask(): PeriodicTask<Unit> {
return PeriodicTask(
delay = QUOTES_UPDATE_DELAY,
isDelayFirst = true,
task = {
runCatching { loadQuotes(isSilentReload = true) }
},
onSuccess = { /* no-op */ },
onError = { /* no-op */ },
)
}
private suspend fun checkAllowance(state: SwapAmountUM.Content, quote: SwapQuoteModel): Boolean {
val allowanceContract = quote.allowanceContract
val allowance = if (allowanceContract != null) {
@ -552,4 +598,9 @@ internal class SwapAmountModel @Inject constructor(
)
}.launchIn(modelScope)
}
private companion object {
const val DEBOUNCE_AMOUNT_DELAY = 1000L
const val QUOTES_UPDATE_DELAY = 10000L
}
}

View file

@ -19,15 +19,21 @@ internal class SwapAmountSetQuotesTransformer(
private val quotes: List<SwapQuoteUM>,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val isSilentReload: Boolean,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
val sortedQuotes = quotes.sortedWith(SwapQuotesComparator)
val bestQuote = findBestQuote(quotes) ?: SwapQuoteUM.Empty
val selectedQuote = if (isSilentReload) {
prevState.selectedQuote
} else {
bestQuote
}
val selectQuoteTransformer = SwapAmountSelectQuoteTransformer(
quoteUM = bestQuote,
quoteUM = selectedQuote,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
)