Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-13 18:23:24 +03:00
parent 82ae40d9f7
commit b95730613d
4 changed files with 56 additions and 10 deletions

View file

@ -20,7 +20,9 @@ import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMark
import com.tangem.features.commonfeatures.impl.choosetoken.ui.ChooseTokenFullUM
import com.tangem.features.commonfeatures.impl.choosetoken.ui.ChooseTokenInitialUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@ -77,6 +79,13 @@ internal class ChooseTokenModel @Inject constructor(
)
init {
if (bridge.settings.isShowMarketBlock) {
modelScope.launch {
delay(MARKETS_INITIAL_LOAD_DELAY)
marketBlockDelegate.loadDefaultMarkets()
}
}
addToPortfolioManager.onDismiss.receiveAsFlow()
.onEach { marketBlockDelegate.addToPortfolioSlot.dismiss() }
.launchIn(modelScope)
@ -128,5 +137,8 @@ internal class ChooseTokenModel @Inject constructor(
companion object {
const val DEBOUNCE_SEARCH_DELAY = 500L
/** Roughly the bottom sheet entrance animation duration. */
private const val MARKETS_INITIAL_LOAD_DELAY = 400L
}
}

View file

@ -126,9 +126,6 @@ internal class MarketBlockDelegate @AssistedInject constructor(
}
.launchIn(modelScope)
// Initial load of default markets
defaultMarketsListManager.reload()
visibleMarketItemIds
.mapNotNull { rawIDS ->
if (rawIDS.isNotEmpty()) {
@ -157,6 +154,15 @@ internal class MarketBlockDelegate @AssistedInject constructor(
.launchIn(modelScope)
}
/**
* Starts the initial load of the default markets list. Not invoked in [init] on purpose:
* the caller decides *if* (market block may be disabled entirely, e.g. Transfer flow) and
* *when* (deferred past the bottom sheet entrance animation, [REDACTED_TASK_KEY]) to trigger it.
*/
fun loadDefaultMarkets() {
defaultMarketsListManager.reload()
}
private fun createDefaultMarketsFlow(): Flow<SwapMarketState> {
val marketsTitle = TextReference.Res(R.string.markets_pulse_common_title)
return combine(

View file

@ -19,12 +19,15 @@ import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
import com.tangem.features.commonfeatures.api.choosetoken.model.TokenListUMData
import com.tangem.features.commonfeatures.impl.choosetoken.SettingContextUseCase
import com.tangem.features.commonfeatures.impl.choosetoken.converter.ChooseTokenListItemConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.mapNotNullValues
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
@ -33,6 +36,7 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
private val settingContext: SettingContextUseCase,
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
private val getWalletsUseCase: GetWalletsUseCase,
dispatchers: CoroutineDispatcherProvider,
@Assisted private val modelScope: CoroutineScope,
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
@Assisted private val featureSettings: ChooseTokenBridge.Settings,
@ -48,6 +52,8 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
val portfolioList: SharedFlow<Map<UserWalletId, TokenListUMData>> = buildDataFlow()
.distinctUntilChanged()
.throttleLatest(windowMs = UM_UPDATES_THROTTLE_MS)
.flowOn(dispatchers.default)
.shareIn(modelScope, SharingStarted.Eagerly, replay = 1)
private fun buildDataFlow(): Flow<Map<UserWalletId, TokenListUMData>> = channelFlow {
@ -153,4 +159,13 @@ internal interface ClickIntents {
fun onAccountExpandClick(account: Account)
fun onAccountCollapseClick(account: Account)
}
private const val UM_UPDATES_THROTTLE_MS = 100L
/** Emits the first value immediately, then at most one (latest) value per [windowMs]. */
@OptIn(ExperimentalCoroutinesApi::class)
private fun <T> Flow<T>.throttleLatest(windowMs: Long): Flow<T> = conflate().transformLatest { value ->
emit(value)
delay(windowMs)
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.commonfeatures.impl.choosetoken.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.BoundsTransform
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.SharedTransitionScope.ResizeMode.Companion.scaleToBounds
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
@ -160,7 +161,7 @@ private fun Content(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
val nestedScrollConnection = rememberHideKeyboardNestedScrollConnection()
val lazyListState = rememberLazyListState()
TangemSharedTransitionLayout(modifier) {
Box(modifier) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
@ -228,10 +229,13 @@ private fun SetupMarketScrollTracker(marketsState: SwapMarketState, lazyListStat
@Composable
private fun VisibleItemsTracker(lazyListState: LazyListState, marketState: SwapMarketState.Content) {
val visibleItems by remember {
val keyToId = remember(marketState.items) {
marketState.items.associateBy({ it.getComposeKey() }, { it.id })
}
val visibleItems by remember(keyToId) {
derivedStateOf {
lazyListState.layoutInfo.visibleItemsInfo.mapNotNull { itemInfo ->
marketState.items.find { it.getComposeKey() == itemInfo.key }?.id
(itemInfo.key as? String)?.let(keyToId::get)
}
}
}
@ -476,7 +480,7 @@ private fun AccountRow(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
ProvideSharedTransitionScope(Modifier.weight(1f)) {
AccountRowSharedTransitionLayout(Modifier.weight(1f)) {
val iconSharedContentState = rememberSharedContentState(key = "account-icon-${portfolio.id}")
val titleSharedContentState = rememberSharedContentState(key = "account-title-${portfolio.id}")
val boundsTransform = BoundsTransform { _, _ -> tween(ACCOUNT_BOUNDS_ANIM_MS) }
@ -528,9 +532,7 @@ private fun AccountRow(
)
val startStyle = TangemTheme.typography2.captionSemibold12
val stopStyle = TangemTheme.typography2.bodySemibold16
val textStyle by remember(animationFraction.value) {
derivedStateOf { lerp(startStyle, stopStyle, animationFraction.value) }
}
val textStyle = lerp(startStyle, stopStyle, animationFraction.value)
val resizedTitle = when (val titleUM = tokenRowUM.titleUM) {
is TangemTokenRowUM.TitleUM.Content -> titleUM.copy(
text = styledStringReference(
@ -579,6 +581,17 @@ private fun AccountRow(
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun AccountRowSharedTransitionLayout(
modifier: Modifier = Modifier,
content: @Composable SharedTransitionScope.() -> Unit,
) {
TangemSharedTransitionLayout(modifier) {
ProvideSharedTransitionScope(content = content)
}
}
@Composable
private fun AccountTail(isExpanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
Box(