Updated on 2026-08-14
This commit is contained in:
parent
4df71e284c
commit
08f011c46e
9 changed files with 237 additions and 13 deletions
|
|
@ -38,9 +38,12 @@ private const val ACTION_BACKGROUND_ALPHA = .1f
|
||||||
* @param iconRes leading 20dp icon drawn over an accent-colored circle
|
* @param iconRes leading 20dp icon drawn over an accent-colored circle
|
||||||
* @param title row primary text
|
* @param title row primary text
|
||||||
* @param description row secondary text
|
* @param description row secondary text
|
||||||
* @param onClick single-click callback; row is non-interactive if `null`
|
* @param onClick single-click callback; row is non-interactive if `null`. Fires regardless
|
||||||
* @param onLongClick long-press callback; pass `null` to disable long-press
|
* of [isEnabled] — gating is the caller's responsibility (pass `null` to
|
||||||
* @param isEnabled when `false`, the row uses disabled-tier colors and ignores clicks
|
* make the row non-interactive)
|
||||||
|
* @param onLongClick long-press callback; pass `null` to disable long-press. Fires regardless
|
||||||
|
* of [isEnabled]
|
||||||
|
* @param isEnabled controls visual styling only (disabled-tier colors when `false`)
|
||||||
* @param tailContent content placed at the row's end. Defaults to a chevron-right icon.
|
* @param tailContent content placed at the row's end. Defaults to a chevron-right icon.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -63,8 +66,8 @@ fun TokenActionRow(
|
||||||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||||
)
|
)
|
||||||
.clickableWithHaptic(
|
.clickableWithHaptic(
|
||||||
onClick = onClick.takeIf { isEnabled },
|
onClick = onClick,
|
||||||
onLongClick = onLongClick.takeIf { isEnabled },
|
onLongClick = onLongClick,
|
||||||
hapticManager = hapticManager,
|
hapticManager = hapticManager,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.token.block.impl.ui
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
|
@ -51,7 +52,8 @@ internal fun TokenMarketBlock(tokenMarketBlockUM: TokenMarketBlockUM, modifier:
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
|
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
|
||||||
.background(TangemTheme.colors2.surface.level3),
|
.background(TangemTheme.colors2.surface.level3)
|
||||||
|
.clickable(onClick = tokenMarketBlockUM.onClick),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResourceSafe(id = R.string.markets_common_market_price),
|
text = stringResourceSafe(id = R.string.markets_common_market_price),
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,7 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
sendButtonsEvents(state.states)
|
sendButtonsEvents(state.states)
|
||||||
uiState.value = stateFactory.getManageButtonsState(actions = state.states)
|
uiState.value = stateFactory.getManageButtonsState(actions = state.states)
|
||||||
if (designFeatureToggles.isRedesignEnabled) {
|
if (designFeatureToggles.isRedesignEnabled) {
|
||||||
|
val networkSource = currencyStatus.value.sources.networkSource
|
||||||
redesignStateController.update(
|
redesignStateController.update(
|
||||||
UpdateActionButtonsTransformer(
|
UpdateActionButtonsTransformer(
|
||||||
actions = state.states,
|
actions = state.states,
|
||||||
|
|
@ -349,6 +350,7 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
redesignStateController.update(
|
redesignStateController.update(
|
||||||
UpdateAddFundsTransformer(
|
UpdateAddFundsTransformer(
|
||||||
actions = state.states,
|
actions = state.states,
|
||||||
|
networkSource = networkSource,
|
||||||
clickIntents = this@TokenDetailsModel,
|
clickIntents = this@TokenDetailsModel,
|
||||||
onActionDispatched = bottomSheetNavigation::dismiss,
|
onActionDispatched = bottomSheetNavigation::dismiss,
|
||||||
),
|
),
|
||||||
|
|
@ -356,6 +358,7 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
redesignStateController.update(
|
redesignStateController.update(
|
||||||
UpdateTransferTransformer(
|
UpdateTransferTransformer(
|
||||||
actions = state.states,
|
actions = state.states,
|
||||||
|
networkSource = networkSource,
|
||||||
clickIntents = this@TokenDetailsModel,
|
clickIntents = this@TokenDetailsModel,
|
||||||
onActionDispatched = bottomSheetNavigation::dismiss,
|
onActionDispatched = bottomSheetNavigation::dismiss,
|
||||||
),
|
),
|
||||||
|
|
@ -363,6 +366,7 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
redesignStateController.update(
|
redesignStateController.update(
|
||||||
UpdateZeroBalanceActionsTransformer(
|
UpdateZeroBalanceActionsTransformer(
|
||||||
actions = state.states,
|
actions = state.states,
|
||||||
|
networkSource = networkSource,
|
||||||
clickIntents = this@TokenDetailsModel,
|
clickIntents = this@TokenDetailsModel,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer
|
package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer
|
||||||
|
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.domain.tokens.model.isLoading
|
import com.tangem.domain.tokens.model.isLoading
|
||||||
|
|
@ -10,6 +11,7 @@ import com.tangem.utils.transformer.Transformer
|
||||||
|
|
||||||
internal class UpdateAddFundsTransformer(
|
internal class UpdateAddFundsTransformer(
|
||||||
private val actions: List<TokenActionsState.ActionState>,
|
private val actions: List<TokenActionsState.ActionState>,
|
||||||
|
private val networkSource: StatusSource,
|
||||||
private val clickIntents: TokenDetailsClickIntents,
|
private val clickIntents: TokenDetailsClickIntents,
|
||||||
private val onActionDispatched: () -> Unit,
|
private val onActionDispatched: () -> Unit,
|
||||||
) : Transformer<TokenDetailsUM> {
|
) : Transformer<TokenDetailsUM> {
|
||||||
|
|
@ -43,7 +45,7 @@ internal class UpdateAddFundsTransformer(
|
||||||
}
|
}
|
||||||
val receiveRow = receiveAction?.let { action ->
|
val receiveRow = receiveAction?.let { action ->
|
||||||
AddFundsUM.Row(
|
AddFundsUM.Row(
|
||||||
isLoading = action.unavailabilityReason.isLoading,
|
isLoading = action.unavailabilityReason.isLoading || networkSource == StatusSource.CACHE,
|
||||||
isEnabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
isEnabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||||
onClick = {
|
onClick = {
|
||||||
onActionDispatched()
|
onActionDispatched()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer
|
package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer
|
||||||
|
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.domain.tokens.model.isLoading
|
import com.tangem.domain.tokens.model.isLoading
|
||||||
|
|
@ -10,6 +11,7 @@ import com.tangem.utils.transformer.Transformer
|
||||||
|
|
||||||
internal class UpdateTransferTransformer(
|
internal class UpdateTransferTransformer(
|
||||||
private val actions: List<TokenActionsState.ActionState>,
|
private val actions: List<TokenActionsState.ActionState>,
|
||||||
|
private val networkSource: StatusSource,
|
||||||
private val clickIntents: TokenDetailsClickIntents,
|
private val clickIntents: TokenDetailsClickIntents,
|
||||||
private val onActionDispatched: () -> Unit,
|
private val onActionDispatched: () -> Unit,
|
||||||
) : Transformer<TokenDetailsUM> {
|
) : Transformer<TokenDetailsUM> {
|
||||||
|
|
@ -23,7 +25,7 @@ internal class UpdateTransferTransformer(
|
||||||
|
|
||||||
val sendRow = sendAction?.let { action ->
|
val sendRow = sendAction?.let { action ->
|
||||||
TransferUM.Row(
|
TransferUM.Row(
|
||||||
isLoading = action.unavailabilityReason.isLoading,
|
isLoading = action.unavailabilityReason.isOutdatedLoading(),
|
||||||
isEnabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
isEnabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||||
onClick = {
|
onClick = {
|
||||||
onActionDispatched()
|
onActionDispatched()
|
||||||
|
|
@ -43,7 +45,7 @@ internal class UpdateTransferTransformer(
|
||||||
}
|
}
|
||||||
val sellRow = sellAction?.let { action ->
|
val sellRow = sellAction?.let { action ->
|
||||||
TransferUM.Row(
|
TransferUM.Row(
|
||||||
isLoading = action.unavailabilityReason.isLoading,
|
isLoading = action.unavailabilityReason.isOutdatedLoading(),
|
||||||
isEnabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
isEnabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||||
onClick = {
|
onClick = {
|
||||||
onActionDispatched()
|
onActionDispatched()
|
||||||
|
|
@ -56,4 +58,7 @@ internal class UpdateTransferTransformer(
|
||||||
transferUM = TransferUM.Content(send = sendRow, swap = swapRow, sell = sellRow),
|
transferUM = TransferUM.Content(send = sendRow, swap = swapRow, sell = sellRow),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ScenarioUnavailabilityReason.isOutdatedLoading(): Boolean =
|
||||||
|
isLoading || this == ScenarioUnavailabilityReason.UsedOutdatedData && networkSource == StatusSource.CACHE
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer
|
package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer
|
||||||
|
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.domain.tokens.model.isLoading
|
import com.tangem.domain.tokens.model.isLoading
|
||||||
|
|
@ -10,6 +11,7 @@ import com.tangem.utils.transformer.Transformer
|
||||||
|
|
||||||
internal class UpdateZeroBalanceActionsTransformer(
|
internal class UpdateZeroBalanceActionsTransformer(
|
||||||
private val actions: List<TokenActionsState.ActionState>,
|
private val actions: List<TokenActionsState.ActionState>,
|
||||||
|
private val networkSource: StatusSource,
|
||||||
private val clickIntents: TokenDetailsClickIntents,
|
private val clickIntents: TokenDetailsClickIntents,
|
||||||
) : Transformer<TokenDetailsUM> {
|
) : Transformer<TokenDetailsUM> {
|
||||||
|
|
||||||
|
|
@ -27,6 +29,7 @@ internal class UpdateZeroBalanceActionsTransformer(
|
||||||
receive = receiveAction?.toRow(
|
receive = receiveAction?.toRow(
|
||||||
onClick = clickIntents::onReceiveClick,
|
onClick = clickIntents::onReceiveClick,
|
||||||
onLongClick = { clickIntents.onCopyAddress() },
|
onLongClick = { clickIntents.onCopyAddress() },
|
||||||
|
forceLoading = networkSource == StatusSource.CACHE,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -35,10 +38,11 @@ internal class UpdateZeroBalanceActionsTransformer(
|
||||||
private fun TokenActionsState.ActionState.toRow(
|
private fun TokenActionsState.ActionState.toRow(
|
||||||
onClick: (ScenarioUnavailabilityReason) -> Unit,
|
onClick: (ScenarioUnavailabilityReason) -> Unit,
|
||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
forceLoading: Boolean = false,
|
||||||
): ZeroBalanceActionsUM.Row {
|
): ZeroBalanceActionsUM.Row {
|
||||||
val reason = unavailabilityReason
|
val reason = unavailabilityReason
|
||||||
return ZeroBalanceActionsUM.Row(
|
return ZeroBalanceActionsUM.Row(
|
||||||
isLoading = reason.isLoading,
|
isLoading = reason.isLoading || forceLoading,
|
||||||
isEnabled = reason == ScenarioUnavailabilityReason.None,
|
isEnabled = reason == ScenarioUnavailabilityReason.None,
|
||||||
onClick = { onClick(reason) },
|
onClick = { onClick(reason) },
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||||
|
|
@ -246,6 +247,63 @@ class UpdateAddFundsTransformerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Receive with None reason AND networkSource is CACHE WHEN transform THEN Receive row is marked isLoading`() {
|
||||||
|
// GIVEN — Receive never carries a Loading reason of its own; networkSource=CACHE is the
|
||||||
|
// signal that the initial data fetch is still in flight, so the row keeps the spinner.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)),
|
||||||
|
networkSource = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.addFundsUM as AddFundsUM.Content
|
||||||
|
assertThat(content.receive?.isLoading).isTrue()
|
||||||
|
assertThat(content.receive?.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Receive with None reason AND networkSource is ONLY_CACHE WHEN transform THEN Receive row is not loading`() {
|
||||||
|
// GIVEN — ONLY_CACHE means the refresh failed (terminal state). Receive should drop the
|
||||||
|
// spinner and render as a normal enabled row using the cached address.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)),
|
||||||
|
networkSource = StatusSource.ONLY_CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.addFundsUM as AddFundsUM.Content
|
||||||
|
assertThat(content.receive?.isLoading).isFalse()
|
||||||
|
assertThat(content.receive?.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Buy AND Swap WHEN networkSource is CACHE THEN their loading stays driven by reason only`() {
|
||||||
|
// GIVEN — CACHE only opens the Loading branch for Receive; Buy/Swap rely on their own
|
||||||
|
// reasons (ExpressLoading / DataLoading). Buy(None)+CACHE must NOT show a spinner.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None),
|
||||||
|
TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None, false),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.addFundsUM as AddFundsUM.Content
|
||||||
|
assertThat(content.buy?.isLoading).isFalse()
|
||||||
|
assertThat(content.swap?.isLoading).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN row WHEN not clicked THEN no callbacks fire`() {
|
fun `GIVEN row WHEN not clicked THEN no callbacks fire`() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
@ -261,8 +319,12 @@ class UpdateAddFundsTransformerTest {
|
||||||
verify(exactly = 0) { clickIntents.onBuyClick(any()) }
|
verify(exactly = 0) { clickIntents.onBuyClick(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTransformer(actions: List<TokenActionsState.ActionState>) = UpdateAddFundsTransformer(
|
private fun createTransformer(
|
||||||
|
actions: List<TokenActionsState.ActionState>,
|
||||||
|
networkSource: StatusSource = StatusSource.ACTUAL,
|
||||||
|
) = UpdateAddFundsTransformer(
|
||||||
actions = actions,
|
actions = actions,
|
||||||
|
networkSource = networkSource,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
onActionDispatched = onActionDispatched,
|
onActionDispatched = onActionDispatched,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||||
|
|
@ -229,6 +230,95 @@ class UpdateTransferTransformerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Send AND Sell carry UsedOutdatedData AND networkSource is CACHE WHEN transform THEN both rows are marked isLoading`() {
|
||||||
|
// GIVEN — OutdatedDataActionsFactory emits UsedOutdatedData for Send/Sell when the network
|
||||||
|
// source is not ACTUAL. CACHE specifically means "still loading", so the row UM upgrades
|
||||||
|
// that pair into a Loading row (preserving disabled state for clicks).
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.UsedOutdatedData),
|
||||||
|
TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.UsedOutdatedData),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.transferUM as TransferUM.Content
|
||||||
|
assertThat(content.send?.isLoading).isTrue()
|
||||||
|
assertThat(content.send?.isEnabled).isFalse()
|
||||||
|
assertThat(content.sell?.isLoading).isTrue()
|
||||||
|
assertThat(content.sell?.isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Send AND Sell carry UsedOutdatedData AND networkSource is ONLY_CACHE WHEN transform THEN rows stay disabled but not loading`() {
|
||||||
|
// GIVEN — ONLY_CACHE is the terminal "refresh failed" state. UsedOutdatedData remains the
|
||||||
|
// reason but the spinner must drop so the UI signals "stale, not loading".
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.UsedOutdatedData),
|
||||||
|
TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.UsedOutdatedData),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.ONLY_CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.transferUM as TransferUM.Content
|
||||||
|
assertThat(content.send?.isLoading).isFalse()
|
||||||
|
assertThat(content.send?.isEnabled).isFalse()
|
||||||
|
assertThat(content.sell?.isLoading).isFalse()
|
||||||
|
assertThat(content.sell?.isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Send AND Sell carry non-Outdated disabled reason AND networkSource is CACHE WHEN transform THEN rows are not loading`() {
|
||||||
|
// GIVEN — the CACHE branch upgrades ONLY UsedOutdatedData to Loading. Any other disabled
|
||||||
|
// reason (e.g. EmptyBalance from a fully resolved status, or Unreachable) keeps the
|
||||||
|
// ordinary disabled-row rendering even if networkSource somehow says CACHE.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.Unreachable),
|
||||||
|
TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.NotSupportedBySellService("USDT")),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.transferUM as TransferUM.Content
|
||||||
|
assertThat(content.send?.isLoading).isFalse()
|
||||||
|
assertThat(content.sell?.isLoading).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Swap available WHEN networkSource is CACHE THEN Swap loading stays driven by reason only`() {
|
||||||
|
// GIVEN — Swap has its own DataLoading reason for CACHE produced by OutdatedDataActionsFactory.
|
||||||
|
// The transformer must not double-mark via networkSource for non-Outdated reasons.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None, false),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.transferUM as TransferUM.Content
|
||||||
|
assertThat(content.swap?.isLoading).isFalse()
|
||||||
|
assertThat(content.swap?.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN row WHEN not clicked THEN no callbacks fire`() {
|
fun `GIVEN row WHEN not clicked THEN no callbacks fire`() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
@ -244,8 +334,12 @@ class UpdateTransferTransformerTest {
|
||||||
verify(exactly = 0) { clickIntents.onSendClick(any()) }
|
verify(exactly = 0) { clickIntents.onSendClick(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTransformer(actions: List<TokenActionsState.ActionState>) = UpdateTransferTransformer(
|
private fun createTransformer(
|
||||||
|
actions: List<TokenActionsState.ActionState>,
|
||||||
|
networkSource: StatusSource = StatusSource.ACTUAL,
|
||||||
|
) = UpdateTransferTransformer(
|
||||||
actions = actions,
|
actions = actions,
|
||||||
|
networkSource = networkSource,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
onActionDispatched = onActionDispatched,
|
onActionDispatched = onActionDispatched,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.model.TokenActionsState
|
import com.tangem.domain.tokens.model.TokenActionsState
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||||
|
|
@ -191,6 +192,49 @@ class UpdateZeroBalanceActionsTransformerTest {
|
||||||
assertThat(content.receive?.isLoading).isFalse()
|
assertThat(content.receive?.isLoading).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Receive with None reason AND networkSource is CACHE WHEN transform THEN only Receive is marked isLoading`() {
|
||||||
|
// GIVEN — networkSource=CACHE is the "still loading" signal for Receive (which has no
|
||||||
|
// Loading reason of its own). Buy/Swap rely on their own reasons and must not be flipped.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None),
|
||||||
|
TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None, false),
|
||||||
|
TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.zeroBalanceActionsUM as ZeroBalanceActionsUM.Content
|
||||||
|
assertThat(content.receive?.isLoading).isTrue()
|
||||||
|
assertThat(content.receive?.isEnabled).isTrue()
|
||||||
|
assertThat(content.buy?.isLoading).isFalse()
|
||||||
|
assertThat(content.swap?.isLoading).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN Receive with None reason AND networkSource is ONLY_CACHE WHEN transform THEN Receive is not loading`() {
|
||||||
|
// GIVEN — ONLY_CACHE is the terminal "refresh failed" state; Receive drops the spinner.
|
||||||
|
val transformer = createTransformer(
|
||||||
|
actions = listOf(
|
||||||
|
TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None),
|
||||||
|
),
|
||||||
|
networkSource = StatusSource.ONLY_CACHE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val content = result.zeroBalanceActionsUM as ZeroBalanceActionsUM.Content
|
||||||
|
assertThat(content.receive?.isLoading).isFalse()
|
||||||
|
assertThat(content.receive?.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN row WHEN not clicked THEN no callbacks fire`() {
|
fun `GIVEN row WHEN not clicked THEN no callbacks fire`() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
@ -205,8 +249,12 @@ class UpdateZeroBalanceActionsTransformerTest {
|
||||||
verify(exactly = 0) { clickIntents.onBuyClick(any()) }
|
verify(exactly = 0) { clickIntents.onBuyClick(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTransformer(actions: List<TokenActionsState.ActionState>) = UpdateZeroBalanceActionsTransformer(
|
private fun createTransformer(
|
||||||
|
actions: List<TokenActionsState.ActionState>,
|
||||||
|
networkSource: StatusSource = StatusSource.ACTUAL,
|
||||||
|
) = UpdateZeroBalanceActionsTransformer(
|
||||||
actions = actions,
|
actions = actions,
|
||||||
|
networkSource = networkSource,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue