Updated on 2026-08-14
This commit is contained in:
parent
7de53d6b0c
commit
3425df0826
6 changed files with 89 additions and 13 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue